diff options
author | Tess Avitabile <tess.avitabile@mongodb.com> | 2017-07-27 11:06:55 -0400 |
---|---|---|
committer | Tess Avitabile <tess.avitabile@mongodb.com> | 2017-07-31 14:42:24 -0400 |
commit | 3867d603a00888a4cd16d52380233f585db5c0fb (patch) | |
tree | 1f1f0485c1348700b2f30531e2fc9039473bdf90 /src | |
parent | 0a55ace0a362a5944fedbaec2d95b2d7cda750d6 (diff) | |
download | mongo-3867d603a00888a4cd16d52380233f585db5c0fb.tar.gz |
SERVER-28773 UpdateNode::apply should take a parameter struct
Diffstat (limited to 'src')
46 files changed, 2766 insertions, 9835 deletions
diff --git a/src/mongo/db/update/addtoset_node.cpp b/src/mongo/db/update/addtoset_node.cpp index 893f27ceeb2..70d88ebf411 100644 --- a/src/mongo/db/update/addtoset_node.cpp +++ b/src/mongo/db/update/addtoset_node.cpp @@ -102,7 +102,7 @@ void AddToSetNode::setCollator(const CollatorInterface* collator) { deduplicate(_elements, _collator); } -void AddToSetNode::updateExistingElement(mutablebson::Element* element, bool* noop) const { +bool AddToSetNode::updateExistingElement(mutablebson::Element* element) const { uassert(ErrorCodes::BadValue, str::stream() << "Cannot apply $addToSet to non-array field. Field named '" << element->getFieldName() @@ -127,14 +127,15 @@ void AddToSetNode::updateExistingElement(mutablebson::Element* element, bool* no } if (elementsToAdd.empty()) { - *noop = true; - return; + return false; } for (auto&& elem : elementsToAdd) { auto toAdd = element->getDocument().makeElement(elem); invariantOK(element->pushBack(toAdd)); } + + return true; } void AddToSetNode::setValueForNewElement(mutablebson::Element* element) const { diff --git a/src/mongo/db/update/addtoset_node.h b/src/mongo/db/update/addtoset_node.h index 6f9a2de2e49..c36d9415750 100644 --- a/src/mongo/db/update/addtoset_node.h +++ b/src/mongo/db/update/addtoset_node.h @@ -47,7 +47,7 @@ public: void setCollator(const CollatorInterface* collator) final; protected: - void updateExistingElement(mutablebson::Element* element, bool* noop) const final; + bool updateExistingElement(mutablebson::Element* element) const final; void setValueForNewElement(mutablebson::Element* element) const final; private: diff --git a/src/mongo/db/update/addtoset_node_test.cpp b/src/mongo/db/update/addtoset_node_test.cpp index 3d9fd8aefb0..816cccf4899 100644 --- a/src/mongo/db/update/addtoset_node_test.cpp +++ b/src/mongo/db/update/addtoset_node_test.cpp @@ -34,12 +34,14 @@ #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/db/update/update_node_test_fixture.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" +namespace mongo { namespace { -using namespace mongo; +using AddToSetNodeTest = UpdateNodeTest; using mongo::mutablebson::Document; using mongo::mutablebson::Element; using mongo::mutablebson::countChildren; @@ -101,522 +103,243 @@ TEST(AddToSetNodeTest, InitSucceedsWithScaler) { ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); } -TEST(AddToSetNodeTest, ApplyFailsOnNonArray) { +TEST_F(AddToSetNodeTest, ApplyFailsOnNonArray) { auto update = fromjson("{$addToSet: {a: 1}}"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); Document doc(fromjson("{a: 2}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathTaken("a"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::BadValue, "Cannot apply $addToSet to non-array field. Field named 'a' has non-array type int"); } -TEST(AddToSetNodeTest, ApplyNonEach) { +TEST_F(AddToSetNodeTest, ApplyNonEach) { auto update = fromjson("{$addToSet: {a: 1}}"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); Document doc(fromjson("{a: [0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [0, 1]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [0, 1]}}"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyNonEachArray) { +TEST_F(AddToSetNodeTest, ApplyNonEachArray) { auto update = fromjson("{$addToSet: {a: [1]}}"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); Document doc(fromjson("{a: [0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, [1]]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [0, [1]]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [0, [1]]}}"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyEach) { +TEST_F(AddToSetNodeTest, ApplyEach) { auto update = fromjson("{$addToSet: {a: {$each: [1, 2]}}}"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); Document doc(fromjson("{a: [0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1, 2]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [0, 1, 2]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [0, 1, 2]}}"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyToEmptyArray) { +TEST_F(AddToSetNodeTest, ApplyToEmptyArray) { auto update = fromjson("{$addToSet: {a: {$each: [1, 2]}}}"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); Document doc(fromjson("{a: []}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 2]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [1, 2]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [1, 2]}}"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyDeduplicateElementsToAdd) { +TEST_F(AddToSetNodeTest, ApplyDeduplicateElementsToAdd) { auto update = fromjson("{$addToSet: {a: {$each: [1, 1]}}}"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); Document doc(fromjson("{a: [0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [0, 1]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [0, 1]}}"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyDoNotAddExistingElements) { +TEST_F(AddToSetNodeTest, ApplyDoNotAddExistingElements) { auto update = fromjson("{$addToSet: {a: {$each: [0, 1]}}}"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); Document doc(fromjson("{a: [0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [0, 1]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [0, 1]}}"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyDoNotDeduplicateExistingElements) { +TEST_F(AddToSetNodeTest, ApplyDoNotDeduplicateExistingElements) { auto update = fromjson("{$addToSet: {a: {$each: [1]}}}"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); Document doc(fromjson("{a: [0, 0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 0, 1]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [0, 0, 1]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [0, 0, 1]}}"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyNoElementsToAdd) { +TEST_F(AddToSetNodeTest, ApplyNoElementsToAdd) { auto update = fromjson("{$addToSet: {a: {$each: []}}}"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); Document doc(fromjson("{a: [0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyNoNonDuplicateElementsToAdd) { +TEST_F(AddToSetNodeTest, ApplyNoNonDuplicateElementsToAdd) { auto update = fromjson("{$addToSet: {a: {$each: [0]}}}"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); Document doc(fromjson("{a: [0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyCreateArray) { +TEST_F(AddToSetNodeTest, ApplyCreateArray) { auto update = fromjson("{$addToSet: {a: {$each: [0, 1]}}}"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [0, 1]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [0, 1]}}"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyCreateEmptyArrayIsNotNoop) { +TEST_F(AddToSetNodeTest, ApplyCreateEmptyArrayIsNotNoop) { auto update = fromjson("{$addToSet: {a: {$each: []}}}"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: []}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: []}}"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyDeduplicationOfElementsToAddRespectsCollation) { +TEST_F(AddToSetNodeTest, ApplyDeduplicationOfElementsToAddRespectsCollation) { auto update = fromjson("{$addToSet: {a: {$each: ['abc', 'ABC', 'def', 'abc']}}}"); const CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kToLowerString); AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], &collator)); Document doc(fromjson("{a: []}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['abc', 'def']}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: ['abc', 'def']}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: ['abc', 'def']}}"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyComparisonToExistingElementsRespectsCollation) { +TEST_F(AddToSetNodeTest, ApplyComparisonToExistingElementsRespectsCollation) { auto update = fromjson("{$addToSet: {a: {$each: ['abc', 'def']}}}"); const CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kToLowerString); AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], &collator)); Document doc(fromjson("{a: ['ABC']}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['ABC', 'def']}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: ['ABC', 'def']}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: ['ABC', 'def']}}"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyRespectsCollationFromSetCollator) { +TEST_F(AddToSetNodeTest, ApplyRespectsCollationFromSetCollator) { auto update = fromjson("{$addToSet: {a: {$each: ['abc', 'ABC', 'def', 'abc']}}}"); const CollatorInterface* binaryComparisonCollator = nullptr; AddToSetNode node; @@ -627,34 +350,14 @@ TEST(AddToSetNodeTest, ApplyRespectsCollationFromSetCollator) { node.setCollator(&caseInsensitiveCollator); Document doc(fromjson("{a: []}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['abc', 'def']}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: ['abc', 'def']}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: ['abc', 'def']}}"), getLogDoc()); } DEATH_TEST(AddToSetNodeTest, CannotSetCollatorIfCollatorIsNonNull, "Invariant failure !_collator") { @@ -678,112 +381,54 @@ DEATH_TEST(AddToSetNodeTest, CannotSetCollatorTwice, "Invariant failure !_collat node.setCollator(&caseInsensitiveCollator); } -TEST(AddToSetNodeTest, ApplyNestedArray) { +TEST_F(AddToSetNodeTest, ApplyNestedArray) { auto update = fromjson("{ $addToSet : { 'a.1' : 1 } }"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a.1"], collator)); Document doc(fromjson("{ _id : 1, a : [ 1, [ ] ] }")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.1"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["1"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.1"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["1"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{ _id : 1, a : [ 1, [ 1 ] ] }"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{ $set : { 'a.1' : [ 1 ] } }"), logDoc); + ASSERT_EQUALS(fromjson("{ $set : { 'a.1' : [ 1 ] } }"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyIndexesNotAffected) { +TEST_F(AddToSetNodeTest, ApplyIndexesNotAffected) { auto update = fromjson("{$addToSet: {a: 1}}"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); Document doc(fromjson("{a: [0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("b"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); - ASSERT_EQUALS(fromjson("{a: [0, 1]}"), doc); + setPathTaken("a"); + addIndexedPath("b"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [0, 1]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [0, 1]}}"), getLogDoc()); } -TEST(AddToSetNodeTest, ApplyNoIndexDataOrLogBuilder) { +TEST_F(AddToSetNodeTest, ApplyNoIndexDataOrLogBuilder) { auto update = fromjson("{$addToSet: {a: 1}}"); const CollatorInterface* collator = nullptr; AddToSetNode node; ASSERT_OK(node.init(update["$addToSet"]["a"], collator)); Document doc(fromjson("{a: [0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + setLogBuilderToNull(); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } } // namespace +} // namespace mongo diff --git a/src/mongo/db/update/arithmetic_node.cpp b/src/mongo/db/update/arithmetic_node.cpp index 69c25a87da8..cb122bfa863 100644 --- a/src/mongo/db/update/arithmetic_node.cpp +++ b/src/mongo/db/update/arithmetic_node.cpp @@ -73,7 +73,7 @@ Status ArithmeticNode::init(BSONElement modExpr, const CollatorInterface* collat return Status::OK(); } -void ArithmeticNode::updateExistingElement(mutablebson::Element* element, bool* noop) const { +bool ArithmeticNode::updateExistingElement(mutablebson::Element* element) const { if (!element->isNumeric()) { mutablebson::Element idElem = mutablebson::findFirstChildNamed(element->getDocument().root(), "_id"); @@ -101,11 +101,12 @@ void ArithmeticNode::updateExistingElement(mutablebson::Element* element, bool* // If the updated value is identical to the original value, treat this as a no-op. Caveat: // if the found element is in a deserialized state, we can't do that. if (element->getValue().ok() && valueToSet.isIdentical(originalValue)) { - *noop = true; + return false; } else { // This can fail if 'valueToSet' is not representable as a 64-bit integer. uassertStatusOK(element->setValueSafeNum(valueToSet)); + return true; } } diff --git a/src/mongo/db/update/arithmetic_node.h b/src/mongo/db/update/arithmetic_node.h index e8f0917443d..7004adafcfe 100644 --- a/src/mongo/db/update/arithmetic_node.h +++ b/src/mongo/db/update/arithmetic_node.h @@ -51,7 +51,7 @@ public: void setCollator(const CollatorInterface* collator) final {} protected: - void updateExistingElement(mutablebson::Element* element, bool* noop) const final; + bool updateExistingElement(mutablebson::Element* element) const final; void setValueForNewElement(mutablebson::Element* element) const final; private: diff --git a/src/mongo/db/update/arithmetic_node_test.cpp b/src/mongo/db/update/arithmetic_node_test.cpp index 9626125c29f..76855f66640 100644 --- a/src/mongo/db/update/arithmetic_node_test.cpp +++ b/src/mongo/db/update/arithmetic_node_test.cpp @@ -33,12 +33,14 @@ #include "mongo/bson/mutable/algorithm.h" #include "mongo/bson/mutable/mutable_bson_test_utils.h" #include "mongo/db/json.h" +#include "mongo/db/update/update_node_test_fixture.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" namespace mongo { namespace { +using ArithmeticNodeTest = UpdateNodeTest; using mongo::mutablebson::Document; using mongo::mutablebson::Element; using mongo::mutablebson::countChildren; @@ -108,695 +110,328 @@ TEST(ArithmeticNodeTest, InitFailsForArrayElement) { ASSERT_EQ(result.reason(), "Cannot multiply with non-numeric argument: {a: []}"); } -TEST(ArithmeticNodeTest, ApplyIncNoOp) { +TEST_F(ArithmeticNodeTest, ApplyIncNoOp) { auto update = fromjson("{$inc: {a: 0}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 5}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(ArithmeticNodeTest, ApplyMulNoOp) { +TEST_F(ArithmeticNodeTest, ApplyMulNoOp) { auto update = fromjson("{$mul: {a: 1}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kMultiply); ASSERT_OK(node.init(update["$mul"]["a"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 5}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(ArithmeticNodeTest, ApplyRoundingNoOp) { +TEST_F(ArithmeticNodeTest, ApplyRoundingNoOp) { auto update = fromjson("{$inc: {a: 1.0}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{a: 6.022e23}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 6.022e23}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(ArithmeticNodeTest, ApplyEmptyPathToCreate) { +TEST_F(ArithmeticNodeTest, ApplyEmptyPathToCreate) { auto update = fromjson("{$inc: {a: 6}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 11}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: 11}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 11}}"), getLogDoc()); } -TEST(ArithmeticNodeTest, ApplyCreatePath) { +TEST_F(ArithmeticNodeTest, ApplyCreatePath) { auto update = fromjson("{$inc: {'a.b.c': 6}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.b.c"], collator)); Document doc(fromjson("{a: {d: 5}}")); - FieldRef pathToCreate("b.c"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("b.c"); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {d: 5, b: {c: 6}}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.b.c': 6}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b.c': 6}}"), getLogDoc()); } -TEST(ArithmeticNodeTest, ApplyExtendPath) { +TEST_F(ArithmeticNodeTest, ApplyExtendPath) { auto update = fromjson("{$inc: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.b"], collator)); Document doc(fromjson("{a: {c: 1}}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("b"); + setPathTaken("a"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {c: 1, b: 2}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyCreatePathFromRoot) { +TEST_F(ArithmeticNodeTest, ApplyCreatePathFromRoot) { auto update = fromjson("{$inc: {'a.b': 6}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.b"], collator)); Document doc(fromjson("{c: 5}")); - FieldRef pathToCreate("a.b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{c: 5, a: {b: 6}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.b': 6}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': 6}}"), getLogDoc()); } -TEST(ArithmeticNodeTest, ApplyPositional) { +TEST_F(ArithmeticNodeTest, ApplyPositional) { auto update = fromjson("{$inc: {'a.$': 6}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.$"], collator)); Document doc(fromjson("{a: [0, 1, 2]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.1"); - StringData matchedField("1"); - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["1"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.1"); + setMatchedField("1"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["1"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 7, 2]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.1': 7}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.1': 7}}"), getLogDoc()); } -TEST(ArithmeticNodeTest, ApplyNonViablePathToInc) { +TEST_F(ArithmeticNodeTest, ApplyNonViablePathToInc) { auto update = fromjson("{$inc: {'a.b': 5}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.b"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + setPathToCreate("b"); + setPathTaken("a"); + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::PathNotViable, "Cannot create field 'b' in element {a: 5}"); } -TEST(ArithmeticNodeTest, ApplyNonViablePathToCreateFromReplicationIsNoOp) { +TEST_F(ArithmeticNodeTest, ApplyNonViablePathToCreateFromReplicationIsNoOp) { auto update = fromjson("{$inc: {'a.b': 5}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.b"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("b"); + setPathTaken("a"); + addIndexedPath("a"); + setFromReplication(true); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 5}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(ArithmeticNodeTest, ApplyNoIndexDataNoLogBuilder) { +TEST_F(ArithmeticNodeTest, ApplyNoIndexDataNoLogBuilder) { auto update = fromjson("{$inc: {a: 6}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + setLogBuilderToNull(); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 11}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyDoesNotAffectIndexes) { +TEST_F(ArithmeticNodeTest, ApplyDoesNotAffectIndexes) { auto update = fromjson("{$inc: {a: 6}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("b"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 11}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, IncTypePromotionIsNotANoOp) { +TEST_F(ArithmeticNodeTest, IncTypePromotionIsNotANoOp) { auto update = fromjson("{$inc: {a: NumberLong(0)}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{a: NumberInt(2)}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberLong(2)}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, MulTypePromotionIsNotANoOp) { +TEST_F(ArithmeticNodeTest, MulTypePromotionIsNotANoOp) { auto update = fromjson("{$mul: {a: NumberLong(1)}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kMultiply); ASSERT_OK(node.init(update["$mul"]["a"], collator)); Document doc(fromjson("{a: NumberInt(2)}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberLong(2)}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, TypePromotionFromIntToDecimalIsNotANoOp) { +TEST_F(ArithmeticNodeTest, TypePromotionFromIntToDecimalIsNotANoOp) { auto update = fromjson("{$inc: {a: NumberDecimal(\"0.0\")}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{a: NumberInt(5)}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberDecimal(\"5.0\")}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: NumberDecimal(\"5.0\")}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: NumberDecimal(\"5.0\")}}"), getLogDoc()); } -TEST(ArithmeticNodeTest, TypePromotionFromLongToDecimalIsNotANoOp) { +TEST_F(ArithmeticNodeTest, TypePromotionFromLongToDecimalIsNotANoOp) { auto update = fromjson("{$inc: {a: NumberDecimal(\"0.0\")}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{a: NumberLong(5)}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberDecimal(\"5.0\")}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: NumberDecimal(\"5.0\")}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: NumberDecimal(\"5.0\")}}"), getLogDoc()); } -TEST(ArithmeticNodeTest, TypePromotionFromDoubleToDecimalIsNotANoOp) { +TEST_F(ArithmeticNodeTest, TypePromotionFromDoubleToDecimalIsNotANoOp) { auto update = fromjson("{$inc: {a: NumberDecimal(\"0.0\")}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{a: 5.25}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberDecimal(\"5.25\")}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: NumberDecimal(\"5.25\")}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: NumberDecimal(\"5.25\")}}"), getLogDoc()); } -TEST(ArithmeticNodeTest, ApplyPromoteToFloatingPoint) { +TEST_F(ArithmeticNodeTest, ApplyPromoteToFloatingPoint) { auto update = fromjson("{$inc: {a: 0.2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{a: NumberLong(1)}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1.2}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, IncrementedDecimalStaysDecimal) { +TEST_F(ArithmeticNodeTest, IncrementedDecimalStaysDecimal) { auto update = fromjson("{$inc: {a: 5.25}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{a: NumberDecimal(\"6.25\")}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberDecimal(\"11.5\")}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: NumberDecimal(\"11.5\")}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: NumberDecimal(\"11.5\")}}"), getLogDoc()); } -TEST(ArithmeticNodeTest, OverflowIntToLong) { +TEST_F(ArithmeticNodeTest, OverflowIntToLong) { auto update = fromjson("{$inc: {a: 1}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); @@ -805,37 +440,17 @@ TEST(ArithmeticNodeTest, OverflowIntToLong) { const int initialValue = std::numeric_limits<int>::max(); Document doc(BSON("a" << initialValue)); ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType()); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType()); ASSERT_EQUALS(BSON("a" << static_cast<long long>(initialValue) + 1), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, UnderflowIntToLong) { +TEST_F(ArithmeticNodeTest, UnderflowIntToLong) { auto update = fromjson("{$inc: {a: -1}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); @@ -844,37 +459,17 @@ TEST(ArithmeticNodeTest, UnderflowIntToLong) { const int initialValue = std::numeric_limits<int>::min(); Document doc(BSON("a" << initialValue)); ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType()); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType()); ASSERT_EQUALS(BSON("a" << static_cast<long long>(initialValue) - 1), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, IncModeCanBeReused) { +TEST_F(ArithmeticNodeTest, IncModeCanBeReused) { auto update = fromjson("{$inc: {a: 1}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); @@ -882,1065 +477,492 @@ TEST(ArithmeticNodeTest, IncModeCanBeReused) { Document doc1(fromjson("{a: 1}")); Document doc2(fromjson("{a: 2}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc1.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc1.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc1); ASSERT_TRUE(doc1.isInPlaceModeEnabled()); - indexesAffected = false; - noop = false; - node.apply(doc2.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + resetApplyParams(); + setPathTaken("a"); + addIndexedPath("a"); + result = node.apply(getApplyParams(doc2.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 3}"), doc2); ASSERT_TRUE(doc1.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, CreatedNumberHasSameTypeAsInc) { +TEST_F(ArithmeticNodeTest, CreatedNumberHasSameTypeAsInc) { auto update = fromjson("{$inc: {a: NumberLong(5)}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{b: 6}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 6, a: NumberLong(5)}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, CreatedNumberHasSameTypeAsMul) { +TEST_F(ArithmeticNodeTest, CreatedNumberHasSameTypeAsMul) { auto update = fromjson("{$mul: {a: NumberLong(5)}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kMultiply); ASSERT_OK(node.init(update["$mul"]["a"], collator)); Document doc(fromjson("{b: 6}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 6, a: NumberLong(0)}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyEmptyDocument) { +TEST_F(ArithmeticNodeTest, ApplyEmptyDocument) { auto update = fromjson("{$inc: {a: 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyIncToObjectFails) { +TEST_F(ArithmeticNodeTest, ApplyIncToObjectFails) { auto update = fromjson("{$inc: {a: 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{_id: 'test_object', a: {b: 1}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop), + setPathTaken("a"); + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::TypeMismatch, "Cannot apply $inc to a value of non-numeric type. {_id: " "\"test_object\"} has the field 'a' of non-numeric type object"); } -TEST(ArithmeticNodeTest, ApplyIncToArrayFails) { +TEST_F(ArithmeticNodeTest, ApplyIncToArrayFails) { auto update = fromjson("{$inc: {a: 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{_id: 'test_object', a: []}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop), + setPathTaken("a"); + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::TypeMismatch, "Cannot apply $inc to a value of non-numeric type. {_id: " "\"test_object\"} has the field 'a' of non-numeric type array"); } -TEST(ArithmeticNodeTest, ApplyIncToStringFails) { +TEST_F(ArithmeticNodeTest, ApplyIncToStringFails) { auto update = fromjson("{$inc: {a: 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{_id: 'test_object', a: \"foo\"}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop), + setPathTaken("a"); + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::TypeMismatch, "Cannot apply $inc to a value of non-numeric type. {_id: " "\"test_object\"} has the field 'a' of non-numeric type string"); } -TEST(ArithmeticNodeTest, ApplyNewPath) { +TEST_F(ArithmeticNodeTest, ApplyNewPath) { auto update = fromjson("{$inc: {a: 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{b: 1}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 1, a: 2}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyEmptyIndexData) { +TEST_F(ArithmeticNodeTest, ApplyEmptyIndexData) { auto update = fromjson("{$inc: {a: 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a"], collator)); Document doc(fromjson("{a: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); + setPathTaken("a"); + node.apply(getApplyParams(doc.root()["a"])); ASSERT_EQUALS(fromjson("{a: 3}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {a: 3}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {a: 3}}"), getLogDoc()); } -TEST(ArithmeticNodeTest, ApplyNoOpDottedPath) { +TEST_F(ArithmeticNodeTest, ApplyNoOpDottedPath) { auto update = fromjson("{$inc: {'a.b': 0}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.b"], collator)); Document doc(fromjson("{a: {b: 2}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b : 2}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, TypePromotionOnDottedPathIsNotANoOp) { +TEST_F(ArithmeticNodeTest, TypePromotionOnDottedPathIsNotANoOp) { auto update = fromjson("{$inc: {'a.b': NumberLong(0)}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.b"], collator)); Document doc(fromjson("{a: {b: NumberInt(2)}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b : NumberLong(2)}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyPathNotViableArray) { +TEST_F(ArithmeticNodeTest, ApplyPathNotViableArray) { auto update = fromjson("{$inc: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.b"], collator)); Document doc(fromjson("{a:[{b:1}]}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + setPathToCreate("b"); + setPathTaken("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::PathNotViable, "Cannot create field 'b' in element {a: [ { b: 1 } ]}"); } -TEST(ArithmeticNodeTest, ApplyInPlaceDottedPath) { +TEST_F(ArithmeticNodeTest, ApplyInPlaceDottedPath) { auto update = fromjson("{$inc: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.b"], collator)); Document doc(fromjson("{a: {b: 1}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 3}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyPromotionDottedPath) { +TEST_F(ArithmeticNodeTest, ApplyPromotionDottedPath) { auto update = fromjson("{$inc: {'a.b': NumberLong(2)}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.b"], collator)); Document doc(fromjson("{a: {b: NumberInt(3)}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: NumberLong(5)}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyDottedPathEmptyDoc) { +TEST_F(ArithmeticNodeTest, ApplyDottedPathEmptyDoc) { auto update = fromjson("{$inc: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.b"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a.b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a.b"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyFieldWithDot) { +TEST_F(ArithmeticNodeTest, ApplyFieldWithDot) { auto update = fromjson("{$inc: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.b"], collator)); Document doc(fromjson("{'a.b':4}")); - FieldRef pathToCreate("a.b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a.b"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{'a.b':4, a: {b: 2}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyNoOpArrayIndex) { +TEST_F(ArithmeticNodeTest, ApplyNoOpArrayIndex) { auto update = fromjson("{$inc: {'a.2.b': 0}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.2.b"], collator)); Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.2.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["2"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a.2.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["2"]["b"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, TypePromotionInArrayIsNotANoOp) { +TEST_F(ArithmeticNodeTest, TypePromotionInArrayIsNotANoOp) { auto update = fromjson("{$set: {'a.2.b': NumberLong(0)}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: [{b: NumberInt(0)},{b: NumberInt(1)},{b: NumberInt(2)}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.2.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["2"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.2.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["2"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: NumberLong(2)}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyNonViablePathThroughArray) { +TEST_F(ArithmeticNodeTest, ApplyNonViablePathThroughArray) { auto update = fromjson("{$inc: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.2.b"], collator)); Document doc(fromjson("{a: 0}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + setPathToCreate("2.b"); + setPathTaken("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::PathNotViable, "Cannot create field '2' in element {a: 0}"); } -TEST(ArithmeticNodeTest, ApplyInPlaceArrayIndex) { +TEST_F(ArithmeticNodeTest, ApplyInPlaceArrayIndex) { auto update = fromjson("{$inc: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.2.b"], collator)); Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 1}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.2.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["2"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.2.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["2"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 3}]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyAppendArray) { +TEST_F(ArithmeticNodeTest, ApplyAppendArray) { auto update = fromjson("{$inc: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.2.b"], collator)); Document doc(fromjson("{a: [{b: 0},{b: 1}]}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("2.b"); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyPaddingArray) { +TEST_F(ArithmeticNodeTest, ApplyPaddingArray) { auto update = fromjson("{$inc: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.2.b"], collator)); Document doc(fromjson("{a: [{b: 0}]}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("2.b"); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},null,{b: 2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyNumericObject) { +TEST_F(ArithmeticNodeTest, ApplyNumericObject) { auto update = fromjson("{$inc: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.2.b"], collator)); Document doc(fromjson("{a: {b: 0}}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("2.b"); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 0, '2': {b: 2}}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyNumericField) { +TEST_F(ArithmeticNodeTest, ApplyNumericField) { auto update = fromjson("{$inc: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.2.b"], collator)); Document doc(fromjson("{a: {'2': {b: 1}}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.2.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["2"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.2.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["2"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {'2': {b: 3}}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyExtendNumericField) { +TEST_F(ArithmeticNodeTest, ApplyExtendNumericField) { auto update = fromjson("{$inc: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.2.b"], collator)); Document doc(fromjson("{a: {'2': {c: 1}}}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a.2"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["2"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("b"); + setPathTaken("a.2"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["2"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {'2': {c: 1, b: 2}}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyNumericFieldToEmptyObject) { +TEST_F(ArithmeticNodeTest, ApplyNumericFieldToEmptyObject) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: {}}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("2.b"); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyEmptyArray) { +TEST_F(ArithmeticNodeTest, ApplyEmptyArray) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: []}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("2.b"); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [null, null, {b: 2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(ArithmeticNodeTest, ApplyLogDottedPath) { +TEST_F(ArithmeticNodeTest, ApplyLogDottedPath) { auto update = fromjson("{$inc: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.2.b"], collator)); Document doc(fromjson("{a: [{b:0}, {b:1}]}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); + setPathToCreate("2.b"); + setPathTaken("a"); + node.apply(getApplyParams(doc.root()["a"])); ASSERT_EQUALS(fromjson("{a: [{b:0}, {b:1}, {b:2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), getLogDoc()); } -TEST(ArithmeticNodeTest, LogEmptyArray) { +TEST_F(ArithmeticNodeTest, LogEmptyArray) { auto update = fromjson("{$inc: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.2.b"], collator)); Document doc(fromjson("{a: []}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); + setPathToCreate("2.b"); + setPathTaken("a"); + node.apply(getApplyParams(doc.root()["a"])); ASSERT_EQUALS(fromjson("{a: [null, null, {b:2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), getLogDoc()); } -TEST(ArithmeticNodeTest, LogEmptyObject) { +TEST_F(ArithmeticNodeTest, LogEmptyObject) { auto update = fromjson("{$inc: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kAdd); ASSERT_OK(node.init(update["$inc"]["a.2.b"], collator)); Document doc(fromjson("{a: {}}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); + setPathToCreate("2.b"); + setPathTaken("a"); + node.apply(getApplyParams(doc.root()["a"])); ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), getLogDoc()); } -TEST(ArithmeticNodeTest, ApplyDeserializedDocNotNoOp) { +TEST_F(ArithmeticNodeTest, ApplyDeserializedDocNotNoOp) { auto update = fromjson("{$mul: {b: NumberInt(1)}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kMultiply); @@ -1950,36 +972,16 @@ TEST(ArithmeticNodeTest, ApplyDeserializedDocNotNoOp) { // De-serialize the int. doc.root()["a"].setValueInt(1).transitional_ignore(); - FieldRef pathToCreate("b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("b"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1, b: NumberInt(0)}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {b: NumberInt(0)}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {b: NumberInt(0)}}"), getLogDoc()); } -TEST(ArithmeticNodeTest, ApplyToDeserializedDocNoOp) { +TEST_F(ArithmeticNodeTest, ApplyToDeserializedDocNoOp) { auto update = fromjson("{$mul: {a: NumberInt(1)}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kMultiply); @@ -1989,36 +991,16 @@ TEST(ArithmeticNodeTest, ApplyToDeserializedDocNoOp) { // De-serialize the int. doc.root()["a"].setValueInt(2).transitional_ignore(); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberInt(2)}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(ArithmeticNodeTest, ApplyToDeserializedDocNestedNoop) { +TEST_F(ArithmeticNodeTest, ApplyToDeserializedDocNestedNoop) { auto update = fromjson("{$mul: {'a.b': NumberInt(1)}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kMultiply); @@ -2028,36 +1010,16 @@ TEST(ArithmeticNodeTest, ApplyToDeserializedDocNestedNoop) { // De-serialize the int. doc.root().appendObject("a", BSON("b" << static_cast<int>(1))).transitional_ignore(); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: NumberInt(1)}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(ArithmeticNodeTest, ApplyToDeserializedDocNestedNotNoop) { +TEST_F(ArithmeticNodeTest, ApplyToDeserializedDocNestedNotNoop) { auto update = fromjson("{$mul: {'a.b': 3}}"); const CollatorInterface* collator = nullptr; ArithmeticNode node(ArithmeticNode::ArithmeticOp::kMultiply); @@ -2067,33 +1029,13 @@ TEST(ArithmeticNodeTest, ApplyToDeserializedDocNestedNotNoop) { // De-serialize the int. doc.root().appendObject("a", BSON("b" << static_cast<int>(1))).transitional_ignore(); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 3}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.b': 3}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': 3}}"), getLogDoc()); } } // namespace diff --git a/src/mongo/db/update/array_culling_node.cpp b/src/mongo/db/update/array_culling_node.cpp index 90943c0e288..166c1b21fa5 100644 --- a/src/mongo/db/update/array_culling_node.cpp +++ b/src/mongo/db/update/array_culling_node.cpp @@ -32,37 +32,24 @@ namespace mongo { -void ArrayCullingNode::apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const { - *indexesAffected = false; - *noop = false; - - if (!pathToCreate->empty()) { +UpdateNode::ApplyResult ArrayCullingNode::apply(ApplyParams applyParams) const { + if (!applyParams.pathToCreate->empty()) { // There were path components we could not traverse. We treat this as a no-op, unless it // would have been impossible to create those elements, which we check with // checkViability(). - UpdateLeafNode::checkViability(element, *pathToCreate, *pathTaken); + UpdateLeafNode::checkViability( + applyParams.element, *(applyParams.pathToCreate), *(applyParams.pathTaken)); - *noop = true; - return; + return ApplyResult::noopResult(); } // This operation only applies to arrays uassert(ErrorCodes::BadValue, "Cannot apply $pull to a non-array value", - element.getType() == mongo::Array); + applyParams.element.getType() == mongo::Array); size_t numRemoved = 0; - auto cursor = element.leftChild(); + auto cursor = applyParams.element.leftChild(); while (cursor.ok()) { // Make sure to get the next array element now, because if we remove the 'cursor' element, // the rightSibling pointer will be invalidated. @@ -75,34 +62,39 @@ void ArrayCullingNode::apply(mutablebson::Element element, } if (numRemoved == 0) { - *noop = true; - return; // Skip the index check, immutable path check, and logging steps. + return ApplyResult::noopResult(); // Skip the index check, immutable path check, and + // logging steps. } + ApplyResult applyResult; + // Determine if indexes are affected. - if (indexData && indexData->mightBeIndexed(pathTaken->dottedField())) { - *indexesAffected = true; + if (!applyParams.indexData || + !applyParams.indexData->mightBeIndexed(applyParams.pathTaken->dottedField())) { + applyResult.indexesAffected = false; } // No need to validate for storage, since we cannot have increased the BSON depth or interfered // with a DBRef. // Ensure we are not changing any immutable paths. - for (const auto& immutablePath : immutablePaths) { + for (const auto& immutablePath : applyParams.immutablePaths) { uassert(ErrorCodes::ImmutableField, - str::stream() << "Performing an update on the path '" << pathTaken->dottedField() + str::stream() << "Performing an update on the path '" + << applyParams.pathTaken->dottedField() << "' would modify the immutable field '" << immutablePath->dottedField() << "'", - pathTaken->commonPrefixSize(*immutablePath) < - std::min(pathTaken->numParts(), immutablePath->numParts())); + applyParams.pathTaken->commonPrefixSize(*immutablePath) < + std::min(applyParams.pathTaken->numParts(), immutablePath->numParts())); } - if (logBuilder) { - auto& doc = logBuilder->getDocument(); - auto logElement = doc.makeElementArray(pathTaken->dottedField()); + if (applyParams.logBuilder) { + auto& doc = applyParams.logBuilder->getDocument(); + auto logElement = doc.makeElementArray(applyParams.pathTaken->dottedField()); - for (auto cursor = element.leftChild(); cursor.ok(); cursor = cursor.rightSibling()) { + for (auto cursor = applyParams.element.leftChild(); cursor.ok(); + cursor = cursor.rightSibling()) { dassert(cursor.hasValue()); auto copy = doc.makeElementWithNewFieldName(StringData(), cursor.getValue()); @@ -110,8 +102,10 @@ void ArrayCullingNode::apply(mutablebson::Element element, uassertStatusOK(logElement.pushBack(copy)); } - uassertStatusOK(logBuilder->addToSets(logElement)); + uassertStatusOK(applyParams.logBuilder->addToSets(logElement)); } + + return applyResult; } } // namespace mongo diff --git a/src/mongo/db/update/array_culling_node.h b/src/mongo/db/update/array_culling_node.h index 09705f86482..cad10143f7e 100644 --- a/src/mongo/db/update/array_culling_node.h +++ b/src/mongo/db/update/array_culling_node.h @@ -43,17 +43,7 @@ namespace mongo { */ class ArrayCullingNode : public UpdateLeafNode { public: - void apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const final; + ApplyResult apply(ApplyParams applyParams) const final; void setCollator(const CollatorInterface* collator) final { _matcher->setCollator(collator); diff --git a/src/mongo/db/update/bit_node.cpp b/src/mongo/db/update/bit_node.cpp index 57fbb57aa2b..fa8f0960c83 100644 --- a/src/mongo/db/update/bit_node.cpp +++ b/src/mongo/db/update/bit_node.cpp @@ -89,7 +89,7 @@ Status BitNode::init(BSONElement modExpr, const CollatorInterface* collator) { return Status::OK(); } -void BitNode::updateExistingElement(mutablebson::Element* element, bool* noop) const { +bool BitNode::updateExistingElement(mutablebson::Element* element) const { if (!element->isIntegral()) { mutablebson::Element idElem = mutablebson::findFirstChildNamed(element->getDocument().root(), "_id"); @@ -105,10 +105,10 @@ void BitNode::updateExistingElement(mutablebson::Element* element, bool* noop) c SafeNum value = applyOpList(element->getValueSafeNum()); if (!value.isIdentical(element->getValueSafeNum())) { - *noop = false; invariantOK(element->setValueSafeNum(value)); + return true; } else { - *noop = true; + return false; } } diff --git a/src/mongo/db/update/bit_node.h b/src/mongo/db/update/bit_node.h index 98e89e47aa6..0e5f21f4e06 100644 --- a/src/mongo/db/update/bit_node.h +++ b/src/mongo/db/update/bit_node.h @@ -47,7 +47,7 @@ public: void setCollator(const CollatorInterface* collator) final {} protected: - void updateExistingElement(mutablebson::Element* element, bool* noop) const final; + bool updateExistingElement(mutablebson::Element* element) const final; void setValueForNewElement(mutablebson::Element* element) const final; private: diff --git a/src/mongo/db/update/bit_node_test.cpp b/src/mongo/db/update/bit_node_test.cpp index 518b41ab048..54db7ccdafb 100644 --- a/src/mongo/db/update/bit_node_test.cpp +++ b/src/mongo/db/update/bit_node_test.cpp @@ -33,12 +33,14 @@ #include "mongo/bson/mutable/algorithm.h" #include "mongo/bson/mutable/mutable_bson_test_utils.h" #include "mongo/db/json.h" +#include "mongo/db/update/update_node_test_fixture.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" +namespace mongo { namespace { -using namespace mongo; +using BitNodeTest = UpdateNodeTest; using mongo::mutablebson::Document; using mongo::mutablebson::Element; using mongo::mutablebson::countChildren; @@ -148,252 +150,112 @@ TEST(BitNodeTest, ParsesXorLong) { ASSERT_OK(node.init(update["$bit"]["a"], collator)); } -TEST(BitNodeTest, ApplyAndLogEmptyDocumentAnd) { +TEST_F(BitNodeTest, ApplyAndLogEmptyDocumentAnd) { auto update = fromjson("{$bit: {a: {and: 1}}}"); const CollatorInterface* collator = nullptr; BitNode node; ASSERT_OK(node.init(update["$bit"]["a"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); + setPathToCreate("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: 0}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: 0}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 0}}"), getLogDoc()); } -TEST(BitNodeTest, ApplyAndLogEmptyDocumentOr) { +TEST_F(BitNodeTest, ApplyAndLogEmptyDocumentOr) { auto update = fromjson("{$bit: {a: {or: 1}}}"); const CollatorInterface* collator = nullptr; BitNode node; ASSERT_OK(node.init(update["$bit"]["a"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); + setPathToCreate("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: 1}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: 1}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 1}}"), getLogDoc()); } -TEST(BitNodeTest, ApplyAndLogEmptyDocumentXor) { +TEST_F(BitNodeTest, ApplyAndLogEmptyDocumentXor) { auto update = fromjson("{$bit: {a: {xor: 1}}}"); const CollatorInterface* collator = nullptr; BitNode node; ASSERT_OK(node.init(update["$bit"]["a"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); + setPathToCreate("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: 1}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: 1}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 1}}"), getLogDoc()); } -TEST(BitNodeTest, ApplyAndLogSimpleDocumentAnd) { +TEST_F(BitNodeTest, ApplyAndLogSimpleDocumentAnd) { auto update = BSON("$bit" << BSON("a" << BSON("and" << 0b0110))); const CollatorInterface* collator = nullptr; BitNode node; ASSERT_OK(node.init(update["$bit"]["a"], collator)); Document doc(BSON("a" << 0b0101)); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); + setPathTaken("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(BSON("a" << 0b0100), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b0100)), logDoc); + ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b0100)), getLogDoc()); } -TEST(BitNodeTest, ApplyAndLogSimpleDocumentOr) { +TEST_F(BitNodeTest, ApplyAndLogSimpleDocumentOr) { auto update = BSON("$bit" << BSON("a" << BSON("or" << 0b0110))); const CollatorInterface* collator = nullptr; BitNode node; ASSERT_OK(node.init(update["$bit"]["a"], collator)); Document doc(BSON("a" << 0b0101)); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); + setPathTaken("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(BSON("a" << 0b0111), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b0111)), logDoc); + ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b0111)), getLogDoc()); } -TEST(BitNodeTest, ApplyAndLogSimpleDocumentXor) { +TEST_F(BitNodeTest, ApplyAndLogSimpleDocumentXor) { auto update = BSON("$bit" << BSON("a" << BSON("xor" << 0b0110))); const CollatorInterface* collator = nullptr; BitNode node; ASSERT_OK(node.init(update["$bit"]["a"], collator)); Document doc(BSON("a" << 0b0101)); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); + setPathTaken("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(BSON("a" << 0b0011), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b0011)), logDoc); + ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b0011)), getLogDoc()); } -TEST(BitNodeTest, ApplyShouldReportNoOp) { +TEST_F(BitNodeTest, ApplyShouldReportNoOp) { auto update = BSON("$bit" << BSON("a" << BSON("and" << static_cast<int>(1)))); const CollatorInterface* collator = nullptr; BitNode node; ASSERT_OK(node.init(update["$bit"]["a"], collator)); Document doc(BSON("a" << 1)); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); + setPathTaken("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); ASSERT_EQUALS(BSON("a" << static_cast<int>(1)), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(BitNodeTest, ApplyMultipleBitOps) { +TEST_F(BitNodeTest, ApplyMultipleBitOps) { // End-of-line comments help clang-format break up this line more readably. auto update = BSON("$bit" << BSON("a" << BSON("and" << 0b1111000011110000 // << // @@ -405,67 +267,28 @@ TEST(BitNodeTest, ApplyMultipleBitOps) { ASSERT_OK(node.init(update["$bit"]["a"], collator)); Document doc(BSON("a" << 0b1111111100000000)); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); + setPathTaken("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(BSON("a" << 0b0101011001100110), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b0101011001100110)), logDoc); + ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b0101011001100110)), getLogDoc()); } -TEST(BitNodeTest, ApplyRepeatedBitOps) { +TEST_F(BitNodeTest, ApplyRepeatedBitOps) { auto update = BSON("$bit" << BSON("a" << BSON("xor" << 0b11001100 << "xor" << 0b10101010))); const CollatorInterface* collator = nullptr; BitNode node; ASSERT_OK(node.init(update["$bit"]["a"], collator)); Document doc(BSON("a" << 0b11110000)); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); + setPathTaken("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(BSON("a" << 0b10010110), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b10010110)), logDoc); + ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b10010110)), getLogDoc()); } } // namespace +} // namepace mongo diff --git a/src/mongo/db/update/compare_node.cpp b/src/mongo/db/update/compare_node.cpp index d42d8be621f..c41dbe886b1 100644 --- a/src/mongo/db/update/compare_node.cpp +++ b/src/mongo/db/update/compare_node.cpp @@ -46,13 +46,13 @@ void CompareNode::setCollator(const CollatorInterface* collator) { _collator = collator; } -void CompareNode::updateExistingElement(mutablebson::Element* element, bool* noop) const { +bool CompareNode::updateExistingElement(mutablebson::Element* element) const { const auto compareVal = element->compareWithBSONElement(_val, _collator, false); if ((compareVal == 0) || ((_mode == CompareMode::kMax) ? (compareVal > 0) : (compareVal < 0))) { - *noop = true; + return false; } else { - *noop = false; invariantOK(element->setValueBSONElement(_val)); + return true; } } diff --git a/src/mongo/db/update/compare_node.h b/src/mongo/db/update/compare_node.h index 0a4cfac3379..eb071652d5d 100644 --- a/src/mongo/db/update/compare_node.h +++ b/src/mongo/db/update/compare_node.h @@ -51,7 +51,7 @@ public: void setCollator(const CollatorInterface* collator) final; protected: - void updateExistingElement(mutablebson::Element* element, bool* noop) const final; + bool updateExistingElement(mutablebson::Element* element) const final; void setValueForNewElement(mutablebson::Element* element) const final; private: diff --git a/src/mongo/db/update/compare_node_test.cpp b/src/mongo/db/update/compare_node_test.cpp index 1f0a9c40c96..d69ac1c5d8b 100644 --- a/src/mongo/db/update/compare_node_test.cpp +++ b/src/mongo/db/update/compare_node_test.cpp @@ -34,12 +34,14 @@ #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/db/update/update_node_test_fixture.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" +namespace mongo { namespace { -using namespace mongo; +using CompareNodeTest = UpdateNodeTest; using mongo::mutablebson::Document; using mongo::mutablebson::Element; using mongo::mutablebson::countChildren; @@ -51,562 +53,262 @@ DEATH_TEST(CompareNodeTest, InitFailsForEmptyElement, "Invariant failure modExpr node.init(update["$max"].embeddedObject().firstElement(), collator).ignore(); } -TEST(CompareNodeTest, ApplyMaxSameNumber) { +TEST_F(CompareNodeTest, ApplyMaxSameNumber) { auto update = fromjson("{$max: {a: 1}}"); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMax); ASSERT_OK(node.init(update["$max"]["a"], collator)); Document doc(fromjson("{a: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyMinSameNumber) { +TEST_F(CompareNodeTest, ApplyMinSameNumber) { auto update = fromjson("{$min: {a: 1}}"); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMin); ASSERT_OK(node.init(update["$min"]["a"], collator)); Document doc(fromjson("{a: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyMaxNumberIsLess) { +TEST_F(CompareNodeTest, ApplyMaxNumberIsLess) { auto update = fromjson("{$max: {a: 0}}"); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMax); ASSERT_OK(node.init(update["$max"]["a"], collator)); Document doc(fromjson("{a: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyMinNumberIsMore) { +TEST_F(CompareNodeTest, ApplyMinNumberIsMore) { auto update = fromjson("{$min: {a: 2}}"); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMin); ASSERT_OK(node.init(update["$min"]["a"], collator)); Document doc(fromjson("{a: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyMaxSameValInt) { +TEST_F(CompareNodeTest, ApplyMaxSameValInt) { auto update = BSON("$max" << BSON("a" << 1LL)); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMax); ASSERT_OK(node.init(update["$max"]["a"], collator)); Document doc(fromjson("{a: 1.0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1.0}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyMaxSameValIntZero) { +TEST_F(CompareNodeTest, ApplyMaxSameValIntZero) { auto update = BSON("$max" << BSON("a" << 0LL)); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMax); ASSERT_OK(node.init(update["$max"]["a"], collator)); Document doc(fromjson("{a: 0.0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 0.0}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyMinSameValIntZero) { +TEST_F(CompareNodeTest, ApplyMinSameValIntZero) { auto update = BSON("$min" << BSON("a" << 0LL)); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMin); ASSERT_OK(node.init(update["$min"]["a"], collator)); Document doc(fromjson("{a: 0.0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 0.0}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyMissingFieldMinNumber) { +TEST_F(CompareNodeTest, ApplyMissingFieldMinNumber) { auto update = fromjson("{$min: {a: 0}}"); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMin); ASSERT_OK(node.init(update["$min"]["a"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 0}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: 0}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 0}}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyExistingNumberMinNumber) { +TEST_F(CompareNodeTest, ApplyExistingNumberMinNumber) { auto update = fromjson("{$min: {a: 0}}"); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMin); ASSERT_OK(node.init(update["$min"]["a"], collator)); Document doc(fromjson("{a: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 0}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: 0}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 0}}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyMissingFieldMaxNumber) { +TEST_F(CompareNodeTest, ApplyMissingFieldMaxNumber) { auto update = fromjson("{$max: {a: 0}}"); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMax); ASSERT_OK(node.init(update["$max"]["a"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 0}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: 0}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 0}}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyExistingNumberMaxNumber) { +TEST_F(CompareNodeTest, ApplyExistingNumberMaxNumber) { auto update = fromjson("{$max: {a: 2}}"); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMax); ASSERT_OK(node.init(update["$max"]["a"], collator)); Document doc(fromjson("{a: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: 2}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 2}}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyExistingDateMaxDate) { +TEST_F(CompareNodeTest, ApplyExistingDateMaxDate) { auto update = fromjson("{$max: {a: {$date: 123123123}}}"); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMax); ASSERT_OK(node.init(update["$max"]["a"], collator)); Document doc(fromjson("{a: {$date: 0}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {$date: 123123123}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: {$date: 123123123}}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: {$date: 123123123}}}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyExistingEmbeddedDocMaxDoc) { +TEST_F(CompareNodeTest, ApplyExistingEmbeddedDocMaxDoc) { auto update = fromjson("{$max: {a: {b: 3}}}"); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMax); ASSERT_OK(node.init(update["$max"]["a"], collator)); Document doc(fromjson("{a: {b: 2}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 3}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: {b: 3}}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: {b: 3}}}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyExistingEmbeddedDocMaxNumber) { +TEST_F(CompareNodeTest, ApplyExistingEmbeddedDocMaxNumber) { auto update = fromjson("{$max: {a: 3}}"); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMax); ASSERT_OK(node.init(update["$max"]["a"], collator)); Document doc(fromjson("{a: {b: 2}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyMinRespectsCollation) { +TEST_F(CompareNodeTest, ApplyMinRespectsCollation) { auto update = fromjson("{$min: {a: 'dba'}}"); const CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); CompareNode node(CompareNode::CompareMode::kMin); ASSERT_OK(node.init(update["$min"]["a"], &collator)); Document doc(fromjson("{a: 'cbc'}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 'dba'}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: 'dba'}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 'dba'}}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyMinRespectsCollationFromSetCollator) { +TEST_F(CompareNodeTest, ApplyMinRespectsCollationFromSetCollator) { auto update = fromjson("{$min: {a: 'dba'}}"); const CollatorInterface* binaryComparisonCollator = nullptr; CompareNode node(CompareNode::CompareMode::kMin); @@ -617,37 +319,17 @@ TEST(CompareNodeTest, ApplyMinRespectsCollationFromSetCollator) { node.setCollator(&reverseStringCollator); Document doc(fromjson("{a: 'cbc'}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 'dba'}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: 'dba'}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 'dba'}}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyMaxRespectsCollationFromSetCollator) { +TEST_F(CompareNodeTest, ApplyMaxRespectsCollationFromSetCollator) { auto update = fromjson("{$max: {a: 'abd'}}"); const CollatorInterface* binaryComparisonCollator = nullptr; CompareNode node(CompareNode::CompareMode::kMax); @@ -658,34 +340,14 @@ TEST(CompareNodeTest, ApplyMaxRespectsCollationFromSetCollator) { node.setCollator(&reverseStringCollator); Document doc(fromjson("{a: 'cbc'}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 'abd'}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: 'abd'}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 'abd'}}"), getLogDoc()); } DEATH_TEST(CompareNodeTest, CannotSetCollatorIfCollatorIsNonNull, "Invariant failure !_collator") { @@ -709,75 +371,38 @@ DEATH_TEST(CompareNodeTest, CannotSetCollatorTwice, "Invariant failure !_collato node.setCollator(&caseInsensitiveCollator); } -TEST(CompareNodeTest, ApplyIndexesNotAffected) { +TEST_F(CompareNodeTest, ApplyIndexesNotAffected) { auto update = fromjson("{$max: {a: 1}}"); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMax); ASSERT_OK(node.init(update["$max"]["a"], collator)); Document doc(fromjson("{a: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("b"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("b"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: 1}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 1}}"), getLogDoc()); } -TEST(CompareNodeTest, ApplyNoIndexDataOrLogBuilder) { +TEST_F(CompareNodeTest, ApplyNoIndexDataOrLogBuilder) { auto update = fromjson("{$max: {a: 1}}"); const CollatorInterface* collator = nullptr; CompareNode node(CompareNode::CompareMode::kMax); ASSERT_OK(node.init(update["$max"]["a"], collator)); Document doc(fromjson("{a: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + setLogBuilderToNull(); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } } // namespace +} // namespace mongo diff --git a/src/mongo/db/update/conflict_placeholder_node.h b/src/mongo/db/update/conflict_placeholder_node.h index 4684226e409..ceb88d69e20 100644 --- a/src/mongo/db/update/conflict_placeholder_node.h +++ b/src/mongo/db/update/conflict_placeholder_node.h @@ -54,17 +54,9 @@ public: void setCollator(const CollatorInterface* collator) final {} - void apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const final {} + ApplyResult apply(ApplyParams applyParams) const final { + return ApplyResult::noopResult(); + } }; } // namespace mongo diff --git a/src/mongo/db/update/current_date_node.cpp b/src/mongo/db/update/current_date_node.cpp index acca32d4cb8..0224fc9ba33 100644 --- a/src/mongo/db/update/current_date_node.cpp +++ b/src/mongo/db/update/current_date_node.cpp @@ -93,9 +93,9 @@ Status CurrentDateNode::init(BSONElement modExpr, const CollatorInterface* colla return Status::OK(); } -void CurrentDateNode::updateExistingElement(mutablebson::Element* element, bool* noop) const { - *noop = false; +bool CurrentDateNode::updateExistingElement(mutablebson::Element* element) const { setValue(element, _typeIsDate); + return true; } void CurrentDateNode::setValueForNewElement(mutablebson::Element* element) const { diff --git a/src/mongo/db/update/current_date_node.h b/src/mongo/db/update/current_date_node.h index c6881d27fb3..c30fd5b1974 100644 --- a/src/mongo/db/update/current_date_node.h +++ b/src/mongo/db/update/current_date_node.h @@ -47,7 +47,7 @@ public: void setCollator(const CollatorInterface* collator) final {} protected: - void updateExistingElement(mutablebson::Element* element, bool* noop) const final; + bool updateExistingElement(mutablebson::Element* element) const final; void setValueForNewElement(mutablebson::Element* element) const final; private: diff --git a/src/mongo/db/update/current_date_node_test.cpp b/src/mongo/db/update/current_date_node_test.cpp index 8efb36e79c7..7a5005b83d8 100644 --- a/src/mongo/db/update/current_date_node_test.cpp +++ b/src/mongo/db/update/current_date_node_test.cpp @@ -33,31 +33,18 @@ #include "mongo/bson/mutable/algorithm.h" #include "mongo/bson/mutable/mutable_bson_test_utils.h" #include "mongo/db/json.h" -#include "mongo/db/logical_clock.h" -#include "mongo/db/service_context_noop.h" +#include "mongo/db/update/update_node_test_fixture.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" +namespace mongo { namespace { -using namespace mongo; +using CurrentDateNodeTest = UpdateNodeTest; using mongo::mutablebson::Document; using mongo::mutablebson::Element; using mongo::mutablebson::countChildren; -class CurrentDateNodeTest : public mongo::unittest::Test { -public: - ~CurrentDateNodeTest() override = default; - -protected: - void setUp() override { - auto service = mongo::getGlobalServiceContext(); - auto logicalClock = mongo::stdx::make_unique<mongo::LogicalClock>(service); - mongo::LogicalClock::set(service, std::move(logicalClock)); - } - void tearDown() override{}; -}; - DEATH_TEST(CurrentDateNodeTest, InitFailsForEmptyElement, "Invariant failure modExpr.ok()") { auto update = fromjson("{$currentDate: {}}"); const CollatorInterface* collator = nullptr; @@ -142,41 +129,21 @@ TEST_F(CurrentDateNodeTest, ApplyTrue) { ASSERT_OK(node.init(update["$currentDate"]["a"], collator)); Document doc(fromjson("{a: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(doc.root().countChildren(), 1U); ASSERT_TRUE(doc.root()["a"].ok()); ASSERT_EQUALS(doc.root()["a"].getType(), BSONType::Date); - ASSERT_EQUALS(logDoc.root().countChildren(), 1U); - ASSERT_TRUE(logDoc.root()["$set"].ok()); - ASSERT_EQUALS(logDoc.root()["$set"].countChildren(), 1U); - ASSERT_TRUE(logDoc.root()["$set"]["a"].ok()); - ASSERT_EQUALS(logDoc.root()["$set"]["a"].getType(), BSONType::Date); + ASSERT_EQUALS(getLogDoc().root().countChildren(), 1U); + ASSERT_TRUE(getLogDoc().root()["$set"].ok()); + ASSERT_EQUALS(getLogDoc().root()["$set"].countChildren(), 1U); + ASSERT_TRUE(getLogDoc().root()["$set"]["a"].ok()); + ASSERT_EQUALS(getLogDoc().root()["$set"]["a"].getType(), BSONType::Date); } TEST_F(CurrentDateNodeTest, ApplyFalse) { @@ -186,41 +153,21 @@ TEST_F(CurrentDateNodeTest, ApplyFalse) { ASSERT_OK(node.init(update["$currentDate"]["a"], collator)); Document doc(fromjson("{a: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(doc.root().countChildren(), 1U); ASSERT_TRUE(doc.root()["a"].ok()); ASSERT_EQUALS(doc.root()["a"].getType(), BSONType::Date); - ASSERT_EQUALS(logDoc.root().countChildren(), 1U); - ASSERT_TRUE(logDoc.root()["$set"].ok()); - ASSERT_EQUALS(logDoc.root()["$set"].countChildren(), 1U); - ASSERT_TRUE(logDoc.root()["$set"]["a"].ok()); - ASSERT_EQUALS(logDoc.root()["$set"]["a"].getType(), BSONType::Date); + ASSERT_EQUALS(getLogDoc().root().countChildren(), 1U); + ASSERT_TRUE(getLogDoc().root()["$set"].ok()); + ASSERT_EQUALS(getLogDoc().root()["$set"].countChildren(), 1U); + ASSERT_TRUE(getLogDoc().root()["$set"]["a"].ok()); + ASSERT_EQUALS(getLogDoc().root()["$set"]["a"].getType(), BSONType::Date); } TEST_F(CurrentDateNodeTest, ApplyDate) { @@ -230,41 +177,21 @@ TEST_F(CurrentDateNodeTest, ApplyDate) { ASSERT_OK(node.init(update["$currentDate"]["a"], collator)); Document doc(fromjson("{a: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(doc.root().countChildren(), 1U); ASSERT_TRUE(doc.root()["a"].ok()); ASSERT_EQUALS(doc.root()["a"].getType(), BSONType::Date); - ASSERT_EQUALS(logDoc.root().countChildren(), 1U); - ASSERT_TRUE(logDoc.root()["$set"].ok()); - ASSERT_EQUALS(logDoc.root()["$set"].countChildren(), 1U); - ASSERT_TRUE(logDoc.root()["$set"]["a"].ok()); - ASSERT_EQUALS(logDoc.root()["$set"]["a"].getType(), BSONType::Date); + ASSERT_EQUALS(getLogDoc().root().countChildren(), 1U); + ASSERT_TRUE(getLogDoc().root()["$set"].ok()); + ASSERT_EQUALS(getLogDoc().root()["$set"].countChildren(), 1U); + ASSERT_TRUE(getLogDoc().root()["$set"]["a"].ok()); + ASSERT_EQUALS(getLogDoc().root()["$set"]["a"].getType(), BSONType::Date); } TEST_F(CurrentDateNodeTest, ApplyTimestamp) { @@ -274,41 +201,21 @@ TEST_F(CurrentDateNodeTest, ApplyTimestamp) { ASSERT_OK(node.init(update["$currentDate"]["a"], collator)); Document doc(fromjson("{a: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(doc.root().countChildren(), 1U); ASSERT_TRUE(doc.root()["a"].ok()); ASSERT_EQUALS(doc.root()["a"].getType(), BSONType::bsonTimestamp); - ASSERT_EQUALS(logDoc.root().countChildren(), 1U); - ASSERT_TRUE(logDoc.root()["$set"].ok()); - ASSERT_EQUALS(logDoc.root()["$set"].countChildren(), 1U); - ASSERT_TRUE(logDoc.root()["$set"]["a"].ok()); - ASSERT_EQUALS(logDoc.root()["$set"]["a"].getType(), BSONType::bsonTimestamp); + ASSERT_EQUALS(getLogDoc().root().countChildren(), 1U); + ASSERT_TRUE(getLogDoc().root()["$set"].ok()); + ASSERT_EQUALS(getLogDoc().root()["$set"].countChildren(), 1U); + ASSERT_TRUE(getLogDoc().root()["$set"]["a"].ok()); + ASSERT_EQUALS(getLogDoc().root()["$set"]["a"].getType(), BSONType::bsonTimestamp); } TEST_F(CurrentDateNodeTest, ApplyFieldDoesNotExist) { @@ -318,41 +225,21 @@ TEST_F(CurrentDateNodeTest, ApplyFieldDoesNotExist) { ASSERT_OK(node.init(update["$currentDate"]["a"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(doc.root().countChildren(), 1U); ASSERT_TRUE(doc.root()["a"].ok()); ASSERT_EQUALS(doc.root()["a"].getType(), BSONType::Date); - ASSERT_EQUALS(logDoc.root().countChildren(), 1U); - ASSERT_TRUE(logDoc.root()["$set"].ok()); - ASSERT_EQUALS(logDoc.root()["$set"].countChildren(), 1U); - ASSERT_TRUE(logDoc.root()["$set"]["a"].ok()); - ASSERT_EQUALS(logDoc.root()["$set"]["a"].getType(), BSONType::Date); + ASSERT_EQUALS(getLogDoc().root().countChildren(), 1U); + ASSERT_TRUE(getLogDoc().root()["$set"].ok()); + ASSERT_EQUALS(getLogDoc().root()["$set"].countChildren(), 1U); + ASSERT_TRUE(getLogDoc().root()["$set"]["a"].ok()); + ASSERT_EQUALS(getLogDoc().root()["$set"]["a"].getType(), BSONType::Date); } TEST_F(CurrentDateNodeTest, ApplyIndexesNotAffected) { @@ -362,41 +249,21 @@ TEST_F(CurrentDateNodeTest, ApplyIndexesNotAffected) { ASSERT_OK(node.init(update["$currentDate"]["a"], collator)); Document doc(fromjson("{a: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("b"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("b"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(doc.root().countChildren(), 1U); ASSERT_TRUE(doc.root()["a"].ok()); ASSERT_EQUALS(doc.root()["a"].getType(), BSONType::Date); - ASSERT_EQUALS(logDoc.root().countChildren(), 1U); - ASSERT_TRUE(logDoc.root()["$set"].ok()); - ASSERT_EQUALS(logDoc.root()["$set"].countChildren(), 1U); - ASSERT_TRUE(logDoc.root()["$set"]["a"].ok()); - ASSERT_EQUALS(logDoc.root()["$set"]["a"].getType(), BSONType::Date); + ASSERT_EQUALS(getLogDoc().root().countChildren(), 1U); + ASSERT_TRUE(getLogDoc().root()["$set"].ok()); + ASSERT_EQUALS(getLogDoc().root()["$set"].countChildren(), 1U); + ASSERT_TRUE(getLogDoc().root()["$set"]["a"].ok()); + ASSERT_EQUALS(getLogDoc().root()["$set"]["a"].getType(), BSONType::Date); } TEST_F(CurrentDateNodeTest, ApplyNoIndexDataOrLogBuilder) { @@ -406,29 +273,11 @@ TEST_F(CurrentDateNodeTest, ApplyNoIndexDataOrLogBuilder) { ASSERT_OK(node.init(update["$currentDate"]["a"], collator)); Document doc(fromjson("{a: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + setLogBuilderToNull(); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(doc.root().countChildren(), 1U); ASSERT_TRUE(doc.root()["a"].ok()); @@ -436,3 +285,4 @@ TEST_F(CurrentDateNodeTest, ApplyNoIndexDataOrLogBuilder) { } } // namespace +} // namespace diff --git a/src/mongo/db/update/object_replace_node.cpp b/src/mongo/db/update/object_replace_node.cpp index 974e4d59a3e..151db825dba 100644 --- a/src/mongo/db/update/object_replace_node.cpp +++ b/src/mongo/db/update/object_replace_node.cpp @@ -69,34 +69,19 @@ ObjectReplaceNode::ObjectReplaceNode(BSONObj val) } } -void ObjectReplaceNode::apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const { - invariant(pathToCreate->empty()); - invariant(pathTaken->empty()); - - auto original = element.getDocument().getObject(); +UpdateNode::ApplyResult ObjectReplaceNode::apply(ApplyParams applyParams) const { + invariant(applyParams.pathToCreate->empty()); + invariant(applyParams.pathTaken->empty()); + + auto original = applyParams.element.getDocument().getObject(); // Check for noop. if (original.binaryEqual(_val)) { - *indexesAffected = false; - *noop = true; - return; + return ApplyResult::noopResult(); } - *indexesAffected = true; - *noop = false; - // Remove the contents of the provided document. - auto current = element.leftChild(); + auto current = applyParams.element.leftChild(); while (current.ok()) { // Keep the _id if the replacement document does not have one. @@ -112,19 +97,20 @@ void ObjectReplaceNode::apply(mutablebson::Element element, // Insert the provided contents instead. for (auto&& elem : _val) { - invariantOK(element.appendElement(elem)); + invariantOK(applyParams.element.appendElement(elem)); } // Validate for storage. - if (validateForStorage) { - storage_validation::storageValid(element.getDocument()); + if (applyParams.validateForStorage) { + storage_validation::storageValid(applyParams.element.getDocument()); } // Check immutable paths. - for (auto path = immutablePaths.begin(); path != immutablePaths.end(); ++path) { + for (auto path = applyParams.immutablePaths.begin(); path != applyParams.immutablePaths.end(); + ++path) { // Find the updated field in the updated document. - auto newElem = element; + auto newElem = applyParams.element; for (size_t i = 0; i < (*path)->numParts(); ++i) { newElem = newElem[(*path)->getPart(i)]; if (!newElem.ok()) { @@ -156,13 +142,16 @@ void ObjectReplaceNode::apply(mutablebson::Element element, } } - if (logBuilder) { - auto replacementObject = logBuilder->getDocument().end(); - invariantOK(logBuilder->getReplacementObject(&replacementObject)); - for (auto current = element.leftChild(); current.ok(); current = current.rightSibling()) { + if (applyParams.logBuilder) { + auto replacementObject = applyParams.logBuilder->getDocument().end(); + invariantOK(applyParams.logBuilder->getReplacementObject(&replacementObject)); + for (auto current = applyParams.element.leftChild(); current.ok(); + current = current.rightSibling()) { invariantOK(replacementObject.appendElement(current.getValue())); } } + + return ApplyResult(); } } // namespace mongo diff --git a/src/mongo/db/update/object_replace_node.h b/src/mongo/db/update/object_replace_node.h index c85371828a0..bc626fd0cf3 100644 --- a/src/mongo/db/update/object_replace_node.h +++ b/src/mongo/db/update/object_replace_node.h @@ -52,25 +52,13 @@ public: void setCollator(const CollatorInterface* collator) final {} /** - * Replaces the document that 'element' belongs to with '_val'. If '_val' does not contain an - * _id, the _id from the original document is preserved. 'element' must be the root of the - * document. 'pathToCreate' and 'pathTaken' must be empty. If 'validateForStorage' is true, the - * modified document is validated for storage. Throws if any path in 'immutablePaths' is - * modified (but it may be created if it did not yet exist). Logs the update as a - * replacement-style update. Always outputs that indexes are affected when the replacement is - * not a noop. + * Replaces the document that 'applyParams.element' belongs to with '_val'. If '_val' does not + * contain an _id, the _id from the original document is preserved. 'applyParams.element' must + * be the root of the document. 'applyParams.pathToCreate' and 'applyParams.pathTaken' must be + * empty. Always returns a result stating that indexes are affected when the replacement is not + * a noop. */ - void apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const final; + ApplyResult apply(ApplyParams applyParams) const final; private: // Object to replace with. diff --git a/src/mongo/db/update/object_replace_node_test.cpp b/src/mongo/db/update/object_replace_node_test.cpp index d567c730bce..69c3bbf6070 100644 --- a/src/mongo/db/update/object_replace_node_test.cpp +++ b/src/mongo/db/update/object_replace_node_test.cpp @@ -35,60 +35,28 @@ #include "mongo/db/json.h" #include "mongo/db/logical_clock.h" #include "mongo/db/service_context_noop.h" +#include "mongo/db/update/update_node_test_fixture.h" #include "mongo/unittest/unittest.h" namespace mongo { namespace { +using ObjectReplaceNodeTest = UpdateNodeTest; using mongo::mutablebson::Document; using mongo::mutablebson::Element; using mongo::mutablebson::countChildren; -class ObjectReplaceNodeTest : public mongo::unittest::Test { -public: - ~ObjectReplaceNodeTest() override = default; - -protected: - void setUp() override { - auto service = mongo::getGlobalServiceContext(); - auto logicalClock = mongo::stdx::make_unique<mongo::LogicalClock>(service); - mongo::LogicalClock::set(service, std::move(logicalClock)); - } - void tearDown() override{}; -}; - TEST_F(ObjectReplaceNodeTest, Noop) { auto obj = fromjson("{a: 1, b: 2}"); ObjectReplaceNode node(obj); Document doc(fromjson("{a: 1, b: 2}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1, b: 2}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } TEST_F(ObjectReplaceNodeTest, ShouldNotCreateIdIfNoIdExistsAndNoneIsSpecified) { @@ -96,33 +64,12 @@ TEST_F(ObjectReplaceNodeTest, ShouldNotCreateIdIfNoIdExistsAndNoneIsSpecified) { ObjectReplaceNode node(obj); Document doc(fromjson("{c: 1, d: 2}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1, b: 2}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{a: 1, b: 2}"), logDoc); + ASSERT_EQUALS(fromjson("{a: 1, b: 2}"), getLogDoc()); } TEST_F(ObjectReplaceNodeTest, ShouldPreserveIdOfExistingDocumentIfIdNotSpecified) { @@ -130,33 +77,12 @@ TEST_F(ObjectReplaceNodeTest, ShouldPreserveIdOfExistingDocumentIfIdNotSpecified ObjectReplaceNode node(obj); Document doc(fromjson("{_id: 0, c: 1, d: 2}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 0, a: 1, b: 2}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{_id: 0, a: 1, b: 2}"), logDoc); + ASSERT_EQUALS(fromjson("{_id: 0, a: 1, b: 2}"), getLogDoc()); } TEST_F(ObjectReplaceNodeTest, ShouldSucceedWhenImmutableIdIsNotModified) { @@ -164,35 +90,13 @@ TEST_F(ObjectReplaceNodeTest, ShouldSucceedWhenImmutableIdIsNotModified) { ObjectReplaceNode node(obj); Document doc(fromjson("{_id: 0, c: 1, d: 2}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("_id"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + addImmutablePath("_id"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 0, a: 1, b: 2}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{_id: 0, a: 1, b: 2}"), logDoc); + ASSERT_EQUALS(fromjson("{_id: 0, a: 1, b: 2}"), getLogDoc()); } TEST_F(ObjectReplaceNodeTest, IdTimestampNotModified) { @@ -200,33 +104,12 @@ TEST_F(ObjectReplaceNodeTest, IdTimestampNotModified) { ObjectReplaceNode node(obj); Document doc(fromjson("{}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: Timestamp(0,0)}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{_id: Timestamp(0,0)}"), logDoc); + ASSERT_EQUALS(fromjson("{_id: Timestamp(0,0)}"), getLogDoc()); } TEST_F(ObjectReplaceNodeTest, NonIdTimestampsModified) { @@ -234,30 +117,9 @@ TEST_F(ObjectReplaceNodeTest, NonIdTimestampsModified) { ObjectReplaceNode node(obj); Document doc(fromjson("{}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(doc.root().countChildren(), 2U); @@ -274,7 +136,7 @@ TEST_F(ObjectReplaceNodeTest, NonIdTimestampsModified) { ASSERT_NOT_EQUALS(0U, elemB.getValueTimestamp().getInc()); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(doc, logDoc); + ASSERT_EQUALS(doc, getLogDoc()); } TEST_F(ObjectReplaceNodeTest, ComplexDoc) { @@ -282,33 +144,12 @@ TEST_F(ObjectReplaceNodeTest, ComplexDoc) { ObjectReplaceNode node(obj); Document doc(fromjson("{a: 1, b: [0, 2, 2], e: []}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1, b: [0, 1, 2], c: {d: 1}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{a: 1, b: [0, 1, 2], c: {d: 1}}"), logDoc); + ASSERT_EQUALS(fromjson("{a: 1, b: [0, 1, 2], c: {d: 1}}"), getLogDoc()); } TEST_F(ObjectReplaceNodeTest, CannotRemoveImmutablePath) { @@ -316,30 +157,8 @@ TEST_F(ObjectReplaceNodeTest, CannotRemoveImmutablePath) { ObjectReplaceNode node(obj); Document doc(fromjson("{_id: 0, a: {b: 1}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + addImmutablePath("a.b"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::ImmutableField, "After applying the update, the 'a.b' (required and immutable) " @@ -351,35 +170,13 @@ TEST_F(ObjectReplaceNodeTest, IdFieldIsNotRemoved) { ObjectReplaceNode node(obj); Document doc(fromjson("{_id: 0, b: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("_id"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + addImmutablePath("_id"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 0, a: 1}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{_id: 0, a: 1}"), logDoc); + ASSERT_EQUALS(fromjson("{_id: 0, a: 1}"), getLogDoc()); } TEST_F(ObjectReplaceNodeTest, CannotReplaceImmutablePathWithArrayField) { @@ -387,30 +184,8 @@ TEST_F(ObjectReplaceNodeTest, CannotReplaceImmutablePathWithArrayField) { ObjectReplaceNode node(obj); Document doc(fromjson("{_id: 0, a: {b: 1}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + addImmutablePath("a.b"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::NotSingleValueField, "After applying the update to the document, the (immutable) field " @@ -422,30 +197,8 @@ TEST_F(ObjectReplaceNodeTest, CannotMakeImmutablePathArrayDescendant) { ObjectReplaceNode node(obj); Document doc(fromjson("{_id: 0, a: {'0': 1}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.0"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + addImmutablePath("a.0"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::NotSingleValueField, "After applying the update to the document, the (immutable) field " @@ -457,30 +210,8 @@ TEST_F(ObjectReplaceNodeTest, CannotModifyImmutablePath) { ObjectReplaceNode node(obj); Document doc(fromjson("{_id: 0, a: {b: 1}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + addImmutablePath("a.b"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::ImmutableField, "After applying the update, the (immutable) field 'a.b' was found " @@ -492,30 +223,8 @@ TEST_F(ObjectReplaceNodeTest, CannotModifyImmutableId) { ObjectReplaceNode node(obj); Document doc(fromjson("{_id: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("_id"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + addImmutablePath("_id"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::ImmutableField, "After applying the update, the (immutable) field '_id' was found " @@ -527,35 +236,13 @@ TEST_F(ObjectReplaceNodeTest, CanAddImmutableField) { ObjectReplaceNode node(obj); Document doc(fromjson("{c: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + addImmutablePath("a.b"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 1}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{a: {b: 1}}"), logDoc); + ASSERT_EQUALS(fromjson("{a: {b: 1}}"), getLogDoc()); } TEST_F(ObjectReplaceNodeTest, CanAddImmutableId) { @@ -563,35 +250,13 @@ TEST_F(ObjectReplaceNodeTest, CanAddImmutableId) { ObjectReplaceNode node(obj); Document doc(fromjson("{c: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("_id"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + addImmutablePath("_id"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 0}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{_id: 0}"), logDoc); + ASSERT_EQUALS(fromjson("{_id: 0}"), getLogDoc()); } TEST_F(ObjectReplaceNodeTest, CannotCreateDollarPrefixedNameWhenValidateForStorageIsTrue) { @@ -599,29 +264,8 @@ TEST_F(ObjectReplaceNodeTest, CannotCreateDollarPrefixedNameWhenValidateForStora ObjectReplaceNode node(obj); Document doc(fromjson("{}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::DollarPrefixedFieldName, "The dollar ($) prefixed field '$bad' in 'a.$bad' is not valid for storage."); @@ -632,33 +276,13 @@ TEST_F(ObjectReplaceNodeTest, CanCreateDollarPrefixedNameWhenValidateForStorageI ObjectReplaceNode node(obj); Document doc(fromjson("{}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = false; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setValidateForStorage(false); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 1, $bad: 1}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{a: {b: 1, $bad: 1}}"), logDoc); + ASSERT_EQUALS(fromjson("{a: {b: 1, $bad: 1}}"), getLogDoc()); } TEST_F(ObjectReplaceNodeTest, NoLogBuilder) { @@ -666,29 +290,10 @@ TEST_F(ObjectReplaceNodeTest, NoLogBuilder) { ObjectReplaceNode node(obj); Document doc(fromjson("{b: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setLogBuilderToNull(); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } diff --git a/src/mongo/db/update/path_creating_node.cpp b/src/mongo/db/update/path_creating_node.cpp index 97feb3fb17e..396ffdaf247 100644 --- a/src/mongo/db/update/path_creating_node.cpp +++ b/src/mongo/db/update/path_creating_node.cpp @@ -107,60 +107,49 @@ void checkImmutablePathsNotModified(mutablebson::Element element, } // namespace -void PathCreatingNode::apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const { - *indexesAffected = false; - *noop = false; - +UpdateNode::ApplyResult PathCreatingNode::apply(ApplyParams applyParams) const { // The value in this Element gets used to create a logging entry (if we have a LogBuilder). - mutablebson::Element valueToLog = element.getDocument().end(); + mutablebson::Element valueToLog = applyParams.element.getDocument().end(); - if (pathToCreate->empty()) { + if (applyParams.pathToCreate->empty()) { // If 'pathTaken' is a strict prefix of any immutable path, store the original document to // ensure the immutable path does not change. BSONObj original; - for (auto immutablePath = immutablePaths.begin(); immutablePath != immutablePaths.end(); + for (auto immutablePath = applyParams.immutablePaths.begin(); + immutablePath != applyParams.immutablePaths.end(); ++immutablePath) { - if (pathTaken->isPrefixOf(**immutablePath)) { - original = element.getDocument().getObject(); + if (applyParams.pathTaken->isPrefixOf(**immutablePath)) { + original = applyParams.element.getDocument().getObject(); break; } } // We found an existing element at the update path. - updateExistingElement(&element, noop); - if (*noop) { - return; // Successful no-op update. + if (!updateExistingElement(&applyParams.element)) { + return ApplyResult::noopResult(); // Successful no-op update. } - if (validateForStorage) { + if (applyParams.validateForStorage) { const bool doRecursiveCheck = true; - const uint32_t recursionLevel = pathTaken->numParts(); - storage_validation::storageValid(element, doRecursiveCheck, recursionLevel); + const uint32_t recursionLevel = applyParams.pathTaken->numParts(); + storage_validation::storageValid(applyParams.element, doRecursiveCheck, recursionLevel); } - checkImmutablePathsNotModified(element, pathTaken, immutablePaths, original); + checkImmutablePathsNotModified( + applyParams.element, applyParams.pathTaken.get(), applyParams.immutablePaths, original); - valueToLog = element; + valueToLog = applyParams.element; } else { // We did not find an element at the update path. Create one. - auto newElementFieldName = pathToCreate->getPart(pathToCreate->numParts() - 1); - auto newElement = element.getDocument().makeElementNull(newElementFieldName); + auto newElementFieldName = + applyParams.pathToCreate->getPart(applyParams.pathToCreate->numParts() - 1); + auto newElement = applyParams.element.getDocument().makeElementNull(newElementFieldName); setValueForNewElement(&newElement); invariant(newElement.ok()); - auto statusWithFirstCreatedElem = - pathsupport::createPathAt(*pathToCreate, 0, element, newElement); + auto statusWithFirstCreatedElem = pathsupport::createPathAt( + *(applyParams.pathToCreate), 0, applyParams.element, newElement); if (!statusWithFirstCreatedElem.isOK()) { // $sets on non-viable paths are ignored when the update came from replication. We do // not error because idempotency requires that any other update modifiers must still be @@ -174,33 +163,35 @@ void PathCreatingNode::apply(mutablebson::Element element, // replication, so we are not concerned with their behavior when "fromReplication" is // true.) if (statusWithFirstCreatedElem.getStatus().code() == ErrorCodes::PathNotViable && - fromReplication) { - *noop = true; - return; + applyParams.fromReplication) { + return ApplyResult::noopResult(); } uassertStatusOK(statusWithFirstCreatedElem); MONGO_UNREACHABLE; // The previous uassertStatusOK should always throw. } - if (validateForStorage) { + if (applyParams.validateForStorage) { const bool doRecursiveCheck = true; - const uint32_t recursionLevel = pathTaken->numParts() + 1; + const uint32_t recursionLevel = applyParams.pathTaken->numParts() + 1; storage_validation::storageValid( statusWithFirstCreatedElem.getValue(), doRecursiveCheck, recursionLevel); } - for (auto immutablePath = immutablePaths.begin(); immutablePath != immutablePaths.end(); + for (auto immutablePath = applyParams.immutablePaths.begin(); + immutablePath != applyParams.immutablePaths.end(); ++immutablePath) { // If 'immutablePath' is a (strict or non-strict) prefix of 'pathTaken', then we are // modifying 'immutablePath'. For example, adding '_id.x' will illegally modify '_id'. uassert(ErrorCodes::ImmutableField, - str::stream() << "Updating the path '" << pathTaken->dottedField() << "' to " - << element.toString() + str::stream() << "Updating the path '" << applyParams.pathTaken->dottedField() + << "' to " + << applyParams.element.toString() << " would modify the immutable field '" << (*immutablePath)->dottedField() << "'", - pathTaken->commonPrefixSize(**immutablePath) != (*immutablePath)->numParts()); + applyParams.pathTaken->commonPrefixSize(**immutablePath) != + (*immutablePath)->numParts()); } valueToLog = newElement; @@ -208,24 +199,28 @@ void PathCreatingNode::apply(mutablebson::Element element, // Create full field path of set element. StringBuilder builder; - builder << pathTaken->dottedField(); - if (!pathTaken->empty() && !pathToCreate->empty()) { + builder << applyParams.pathTaken->dottedField(); + if (!applyParams.pathTaken->empty() && !applyParams.pathToCreate->empty()) { builder << "."; } - builder << pathToCreate->dottedField(); + builder << applyParams.pathToCreate->dottedField(); auto fullPath = builder.str(); + ApplyResult applyResult; + // Determine if indexes are affected. - if (indexData && indexData->mightBeIndexed(fullPath)) { - *indexesAffected = true; + if (!applyParams.indexData || !applyParams.indexData->mightBeIndexed(fullPath)) { + applyResult.indexesAffected = false; } // Log the operation. - if (logBuilder) { + if (applyParams.logBuilder) { auto logElement = - logBuilder->getDocument().makeElementWithNewFieldName(fullPath, valueToLog); + applyParams.logBuilder->getDocument().makeElementWithNewFieldName(fullPath, valueToLog); invariant(logElement.ok()); - uassertStatusOK(logBuilder->addToSets(logElement)); + uassertStatusOK(applyParams.logBuilder->addToSets(logElement)); } + + return applyResult; } } // namespace mongo diff --git a/src/mongo/db/update/path_creating_node.h b/src/mongo/db/update/path_creating_node.h index adbf770022c..22c7b6a44ad 100644 --- a/src/mongo/db/update/path_creating_node.h +++ b/src/mongo/db/update/path_creating_node.h @@ -41,25 +41,15 @@ namespace mongo { */ class PathCreatingNode : public UpdateLeafNode { public: - void apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const final; + ApplyResult apply(ApplyParams applyParams) const final; protected: /** * PathCreatingNode::apply() calls the updateExistingElement() method when applying its update * to an existing path. The child's implementation of this method is responsible for either - * updating the given Element or setting *noop to true to indicate that no update is necessary. + * updating the given Element or returning false to indicate that no update is necessary. */ - virtual void updateExistingElement(mutablebson::Element* element, bool* noop) const = 0; + virtual bool updateExistingElement(mutablebson::Element* element) const = 0; /** * PathCreatingNode::apply() calls the setValueForNewElement() method when it must materialize a diff --git a/src/mongo/db/update/pop_node.cpp b/src/mongo/db/update/pop_node.cpp index 4b3f6273db3..3a862413853 100644 --- a/src/mongo/db/update/pop_node.cpp +++ b/src/mongo/db/update/pop_node.cpp @@ -47,79 +47,73 @@ Status PopNode::init(BSONElement modExpr, const CollatorInterface* collator) { return Status::OK(); } -void PopNode::apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const { - *indexesAffected = false; - *noop = false; - - if (pathTaken->empty()) { +UpdateNode::ApplyResult PopNode::apply(ApplyParams applyParams) const { + if (applyParams.pathTaken->empty()) { // No components of the path existed. The pop is treated as a no-op in this case. - *noop = true; - return; + return ApplyResult::noopResult(); } - if (!pathToCreate->empty()) { + if (!applyParams.pathToCreate->empty()) { // There were path components we could not traverse. We treat this as a no-op, unless it // would have been impossible to create those elements, which we check with // checkViability(). - UpdateLeafNode::checkViability(element, *pathToCreate, *pathTaken); + UpdateLeafNode::checkViability( + applyParams.element, *(applyParams.pathToCreate), *(applyParams.pathTaken)); - *noop = true; - return; + return ApplyResult::noopResult(); } - invariant(!pathTaken->empty()); - invariant(pathToCreate->empty()); + invariant(!applyParams.pathTaken->empty()); + invariant(applyParams.pathToCreate->empty()); // The full path existed, but we must fail if the element at that path is not an array. - invariant(element.ok()); + invariant(applyParams.element.ok()); uassert(ErrorCodes::TypeMismatch, - str::stream() << "Path '" << pathTaken->dottedField() + str::stream() << "Path '" << applyParams.pathTaken->dottedField() << "' contains an element of non-array type '" - << typeName(element.getType()) + << typeName(applyParams.element.getType()) << "'", - element.getType() == BSONType::Array); + applyParams.element.getType() == BSONType::Array); - if (!element.hasChildren()) { + if (!applyParams.element.hasChildren()) { // The path exists and contains an array, but the array is empty. - *noop = true; - return; + return ApplyResult::noopResult(); } - if (indexData && indexData->mightBeIndexed(pathTaken->dottedField())) { - *indexesAffected = true; + ApplyResult applyResult; + + if (!applyParams.indexData || + !applyParams.indexData->mightBeIndexed(applyParams.pathTaken->dottedField())) { + applyResult.indexesAffected = false; } - auto elementToRemove = _popFromFront ? element.leftChild() : element.rightChild(); + auto elementToRemove = + _popFromFront ? applyParams.element.leftChild() : applyParams.element.rightChild(); invariantOK(elementToRemove.remove()); // No need to validate for storage, since we cannot have increased the BSON depth or interfered // with a DBRef. // Ensure we are not changing any immutable paths. - for (auto immutablePath = immutablePaths.begin(); immutablePath != immutablePaths.end(); + for (auto immutablePath = applyParams.immutablePaths.begin(); + immutablePath != applyParams.immutablePaths.end(); ++immutablePath) { uassert(ErrorCodes::ImmutableField, - str::stream() << "Performing a $pop on the path '" << pathTaken->dottedField() + str::stream() << "Performing a $pop on the path '" + << applyParams.pathTaken->dottedField() << "' would modify the immutable field '" << (*immutablePath)->dottedField() << "'", - pathTaken->commonPrefixSize(**immutablePath) < - std::min(pathTaken->numParts(), (*immutablePath)->numParts())); + applyParams.pathTaken->commonPrefixSize(**immutablePath) < + std::min(applyParams.pathTaken->numParts(), (*immutablePath)->numParts())); } - if (logBuilder) { - uassertStatusOK(logBuilder->addToSetsWithNewFieldName(pathTaken->dottedField(), element)); + if (applyParams.logBuilder) { + uassertStatusOK(applyParams.logBuilder->addToSetsWithNewFieldName( + applyParams.pathTaken->dottedField(), applyParams.element)); } + + return applyResult; } } // namespace mongo diff --git a/src/mongo/db/update/pop_node.h b/src/mongo/db/update/pop_node.h index 7d7a2d13f14..1cbdac4b1bc 100644 --- a/src/mongo/db/update/pop_node.h +++ b/src/mongo/db/update/pop_node.h @@ -37,17 +37,7 @@ 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, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const final; + ApplyResult apply(ApplyParams applyParams) const final; std::unique_ptr<UpdateNode> clone() const final { return stdx::make_unique<PopNode>(*this); diff --git a/src/mongo/db/update/pop_node_test.cpp b/src/mongo/db/update/pop_node_test.cpp index 9442544af47..4cb14505988 100644 --- a/src/mongo/db/update/pop_node_test.cpp +++ b/src/mongo/db/update/pop_node_test.cpp @@ -32,6 +32,7 @@ #include "mongo/bson/json.h" #include "mongo/bson/mutable/mutable_bson_test_utils.h" +#include "mongo/db/update/update_node_test_fixture.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" @@ -39,6 +40,7 @@ namespace mongo { namespace { namespace mmb = mongo::mutablebson; +using PopNodeTest = UpdateNodeTest; TEST(PopNodeTest, InitSucceedsPositiveOne) { auto update = fromjson("{$pop: {a: 1}}"); @@ -98,254 +100,119 @@ TEST(PopNodeTest, InitFailsBool) { ASSERT_EQ(ErrorCodes::FailedToParse, popNode.init(update["$pop"]["a"], collator)); } -TEST(PopNodeTest, NoopWhenFirstPathComponentDoesNotExist) { +TEST_F(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; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("a.b"); + addIndexedPath("a.b"); + auto result = popNode.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: [1, 2, 3]}"), doc); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(PopNodeTest, NoopWhenPathPartiallyExists) { +TEST_F(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; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("b.c"); + setPathTaken("a"); + addIndexedPath("a.b.c"); + auto result = popNode.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {}}"), doc); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(PopNodeTest, NoopWhenNumericalPathComponentExceedsArrayLength) { +TEST_F(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; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("0"); + setPathTaken("a"); + addIndexedPath("a.0"); + auto result = popNode.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(PopNodeTest, ThrowsWhenPathIsBlockedByAScalar) { +TEST_F(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; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - mmb::Document logDoc; - LogBuilder logBuilder(logDoc.root()); - bool indexesAffected; - bool noop; + setPathToCreate("b"); + setPathTaken("a"); + addIndexedPath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - popNode.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + popNode.apply(getApplyParams(doc.root()["a"])), 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()") { +DEATH_TEST_F(PopNodeTest, + NonOkElementWhenPathExistsIsFatal, + "Invariant failure applyParams.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; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); + setPathTaken("a.b"); + addIndexedPath("a.b"); + popNode.apply(getApplyParams(doc.end())); } -TEST(PopNodeTest, ThrowsWhenPathExistsButDoesNotContainAnArray) { +TEST_F(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; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + setPathTaken("a.b"); + addIndexedPath("a.b"); + ASSERT_THROWS_CODE_AND_WHAT(popNode.apply(getApplyParams(doc.root()["a"]["b"])), UserException, ErrorCodes::TypeMismatch, "Path 'a.b' contains an element of non-array type 'string'"); } -TEST(PopNodeTest, NoopWhenPathContainsAnEmptyArray) { +TEST_F(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; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a.b"); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: []}}"), doc); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(PopNodeTest, PopsSingleElementFromTheBack) { +TEST_F(PopNodeTest, PopsSingleElementFromTheBack) { auto update = fromjson("{$pop: {'a.b': 1}}"); const CollatorInterface* collator = nullptr; PopNode popNode; @@ -353,36 +220,16 @@ TEST(PopNodeTest, PopsSingleElementFromTheBack) { ASSERT_FALSE(popNode.popFromFront()); mmb::Document doc(fromjson("{a: {b: [1]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a.b"); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: []}}"), doc); - ASSERT_EQUALS(fromjson("{$set: {'a.b': []}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': []}}"), getLogDoc()); } -TEST(PopNodeTest, PopsSingleElementFromTheFront) { +TEST_F(PopNodeTest, PopsSingleElementFromTheFront) { auto update = fromjson("{$pop: {'a.b': -1}}"); const CollatorInterface* collator = nullptr; PopNode popNode; @@ -390,36 +237,16 @@ TEST(PopNodeTest, PopsSingleElementFromTheFront) { ASSERT_TRUE(popNode.popFromFront()); mmb::Document doc(fromjson("{a: {b: [[1]]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a"); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: []}}"), doc); - ASSERT_EQUALS(fromjson("{$set: {'a.b': []}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': []}}"), getLogDoc()); } -TEST(PopNodeTest, PopsFromTheBackOfMultiElementArray) { +TEST_F(PopNodeTest, PopsFromTheBackOfMultiElementArray) { auto update = fromjson("{$pop: {'a.b': 1}}"); const CollatorInterface* collator = nullptr; PopNode popNode; @@ -427,36 +254,16 @@ TEST(PopNodeTest, PopsFromTheBackOfMultiElementArray) { ASSERT_FALSE(popNode.popFromFront()); mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a.b.c"); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [1, 2]}}"), doc); - ASSERT_EQUALS(fromjson("{$set: {'a.b': [1, 2]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': [1, 2]}}"), getLogDoc()); } -TEST(PopNodeTest, PopsFromTheFrontOfMultiElementArray) { +TEST_F(PopNodeTest, PopsFromTheFrontOfMultiElementArray) { auto update = fromjson("{$pop: {'a.b': -1}}"); const CollatorInterface* collator = nullptr; PopNode popNode; @@ -464,36 +271,16 @@ TEST(PopNodeTest, PopsFromTheFrontOfMultiElementArray) { ASSERT_TRUE(popNode.popFromFront()); mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a.b"); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [2, 3]}}"), doc); - ASSERT_EQUALS(fromjson("{$set: {'a.b': [2, 3]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': [2, 3]}}"), getLogDoc()); } -TEST(PopNodeTest, PopsFromTheFrontOfMultiElementArrayWithoutAffectingIndexes) { +TEST_F(PopNodeTest, PopsFromTheFrontOfMultiElementArrayWithoutAffectingIndexes) { auto update = fromjson("{$pop: {'a.b': -1}}"); const CollatorInterface* collator = nullptr; PopNode popNode; @@ -501,36 +288,16 @@ TEST(PopNodeTest, PopsFromTheFrontOfMultiElementArrayWithoutAffectingIndexes) { ASSERT_TRUE(popNode.popFromFront()); mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("unrelated.path"); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [2, 3]}}"), doc); - ASSERT_EQUALS(fromjson("{$set: {'a.b': [2, 3]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': [2, 3]}}"), getLogDoc()); } -TEST(PopNodeTest, SucceedsWithNullUpdateIndexData) { +TEST_F(PopNodeTest, SucceedsWithNullUpdateIndexData) { auto update = fromjson("{$pop: {'a.b': 1}}"); const CollatorInterface* collator = nullptr; PopNode popNode; @@ -538,34 +305,15 @@ TEST(PopNodeTest, SucceedsWithNullUpdateIndexData) { ASSERT_FALSE(popNode.popFromFront()); mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - mmb::Document logDoc; - LogBuilder logBuilder(logDoc.root()); - bool indexesAffected; - bool noop; - popNode.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - nullptr, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a.b"); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [1, 2]}}"), doc); - ASSERT_EQUALS(fromjson("{$set: {'a.b': [1, 2]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': [1, 2]}}"), getLogDoc()); } -TEST(PopNodeTest, SucceedsWithNullLogBuilder) { +TEST_F(PopNodeTest, SucceedsWithNullLogBuilder) { auto update = fromjson("{$pop: {'a.b': 1}}"); const CollatorInterface* collator = nullptr; PopNode popNode; @@ -573,71 +321,33 @@ TEST(PopNodeTest, SucceedsWithNullLogBuilder) { ASSERT_FALSE(popNode.popFromFront()); mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b.c"); - bool indexesAffected; - bool noop; - popNode.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - nullptr, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a.b.c"); + setLogBuilderToNull(); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [1, 2]}}"), doc); } -TEST(PopNodeTest, ThrowsWhenPathIsImmutable) { +TEST_F(PopNodeTest, ThrowsWhenPathIsImmutable) { 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: [0]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - UpdateIndexData indexData; - indexData.addPath("a.b"); - mmb::Document logDoc; - LogBuilder logBuilder(logDoc.root()); - bool indexesAffected; - bool noop; + setPathTaken("a.b"); + addImmutablePath("a.b"); + addIndexedPath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - popNode.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + popNode.apply(getApplyParams(doc.root()["a"]["b"])), UserException, ErrorCodes::ImmutableField, "Performing a $pop on the path 'a.b' would modify the immutable field 'a.b'"); } -TEST(PopNodeTest, ThrowsWhenPathIsPrefixOfImmutable) { +TEST_F(PopNodeTest, ThrowsWhenPathIsPrefixOfImmutable) { // This is only possible for an upsert, since it is not legal to have an array in an immutable // path. If this update did not fail, we would fail later for storing an immutable path with an @@ -649,111 +359,48 @@ TEST(PopNodeTest, ThrowsWhenPathIsPrefixOfImmutable) { ASSERT_OK(popNode.init(update["$pop"]["a"], collator)); mmb::Document doc(fromjson("{a: [0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.0"); - immutablePaths.insert(&path); - UpdateIndexData indexData; - indexData.addPath("a"); - mmb::Document logDoc; - LogBuilder logBuilder(logDoc.root()); - bool indexesAffected; - bool noop; + setPathTaken("a"); + addImmutablePath("a.0"); + addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT( - popNode.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + popNode.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::ImmutableField, "Performing a $pop on the path 'a' would modify the immutable field 'a.0'"); } -TEST(PopNodeTest, ThrowsWhenPathIsSuffixOfImmutable) { +TEST_F(PopNodeTest, ThrowsWhenPathIsSuffixOfImmutable) { 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: [0]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a"); - immutablePaths.insert(&path); - UpdateIndexData indexData; - indexData.addPath("a.b"); - mmb::Document logDoc; - LogBuilder logBuilder(logDoc.root()); - bool indexesAffected; - bool noop; + setPathTaken("a.b"); + addImmutablePath("a"); + addIndexedPath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - popNode.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + popNode.apply(getApplyParams(doc.root()["a"]["b"])), UserException, ErrorCodes::ImmutableField, "Performing a $pop on the path 'a.b' would modify the immutable field 'a'"); } -TEST(PopNodeTest, NoopOnImmutablePathSucceeds) { +TEST_F(PopNodeTest, NoopOnImmutablePathSucceeds) { 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; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a.b"); + addImmutablePath("a.b"); + addIndexedPath("a.b"); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: []}}"), doc); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } } // namespace diff --git a/src/mongo/db/update/pull_node_test.cpp b/src/mongo/db/update/pull_node_test.cpp index b00a4fbd36d..f158b1c0678 100644 --- a/src/mongo/db/update/pull_node_test.cpp +++ b/src/mongo/db/update/pull_node_test.cpp @@ -34,12 +34,14 @@ #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/db/update/update_node_test_fixture.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" +namespace mongo { namespace { -using namespace mongo; +using PullNodeTest = UpdateNodeTest; using mongo::mutablebson::Document; using mongo::mutablebson::Element; using mongo::mutablebson::countChildren; @@ -62,406 +64,190 @@ TEST(PullNodeTest, InitWithBadTopLevelOperatorFails) { ASSERT_EQUALS(ErrorCodes::BadValue, status); } -TEST(PullNodeTest, TargetNotFound) { +TEST_F(PullNodeTest, TargetNotFound) { auto update = fromjson("{$pull : {a: {$lt: 1}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(PullNodeTest, ApplyToStringFails) { +TEST_F(PullNodeTest, ApplyToStringFails) { auto update = fromjson("{$pull : {a: {$lt: 1}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], collator)); Document doc(fromjson("{a: 'foo'}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + setPathTaken("a"); + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::BadValue, "Cannot apply $pull to a non-array value"); } -TEST(PullNodeTest, ApplyToObjectFails) { +TEST_F(PullNodeTest, ApplyToObjectFails) { auto update = fromjson("{$pull : {a: {$lt: 1}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], collator)); Document doc(fromjson("{a: {foo: 'bar'}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + setPathTaken("a"); + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::BadValue, "Cannot apply $pull to a non-array value"); } -TEST(PullNodeTest, ApplyToNonViablePathFails) { +TEST_F(PullNodeTest, ApplyToNonViablePathFails) { auto update = fromjson("{$pull : {'a.b': {$lt: 1}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a.b"], collator)); Document doc(fromjson("{a: 1}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; + setPathToCreate("b"); + setPathTaken("a"); + addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::PathNotViable, "Cannot use the part (b) of (a.b) to traverse the element ({a: 1})"); } -TEST(PullNodeTest, ApplyToMissingElement) { +TEST_F(PullNodeTest, ApplyToMissingElement) { auto update = fromjson("{$pull: {'a.b.c.d': {$lt: 1}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a.b.c.d"], collator)); Document doc(fromjson("{a: {b: {c: {}}}}")); - FieldRef pathToCreate("d"); - FieldRef pathTaken("a.b.c"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"]["c"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("d"); + setPathTaken("a.b.c"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: {c: {}}}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(PullNodeTest, ApplyToEmptyArray) { +TEST_F(PullNodeTest, ApplyToEmptyArray) { auto update = fromjson("{$pull : {a: {$lt: 1}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], collator)); Document doc(fromjson("{a: []}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(PullNodeTest, ApplyToArrayMatchingNone) { +TEST_F(PullNodeTest, ApplyToArrayMatchingNone) { auto update = fromjson("{$pull : {a: {$lt: 1}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], collator)); Document doc(fromjson("{a: [2, 3, 4, 5]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [2, 3, 4, 5]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(PullNodeTest, ApplyToArrayMatchingOne) { +TEST_F(PullNodeTest, ApplyToArrayMatchingOne) { auto update = fromjson("{$pull : {a: {$lt: 1}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], collator)); Document doc(fromjson("{a: [0, 1, 2, 3]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 2, 3]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [1, 2, 3]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [1, 2, 3]}}"), getLogDoc()); } -TEST(PullNodeTest, ApplyToArrayMatchingSeveral) { +TEST_F(PullNodeTest, ApplyToArrayMatchingSeveral) { auto update = fromjson("{$pull : {a: {$lt: 1}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], collator)); Document doc(fromjson("{a: [0, 1, 0, 2, 0, 3, 0, 4, 0, 5]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 2, 3, 4, 5]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [1, 2, 3, 4, 5]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [1, 2, 3, 4, 5]}}"), getLogDoc()); } -TEST(PullNodeTest, ApplyToArrayMatchingAll) { +TEST_F(PullNodeTest, ApplyToArrayMatchingAll) { auto update = fromjson("{$pull : {a: {$lt: 1}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], collator)); Document doc(fromjson("{a: [0, -1, -2, -3, -4, -5]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: []}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: []}}"), getLogDoc()); } -TEST(PullNodeTest, ApplyNoIndexDataNoLogBuilder) { +TEST_F(PullNodeTest, ApplyNoIndexDataNoLogBuilder) { auto update = fromjson("{$pull : {a: {$lt: 1}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], collator)); Document doc(fromjson("{a: [0, 1, 2, 3]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + setLogBuilderToNull(); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 2, 3]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(PullNodeTest, ApplyWithCollation) { +TEST_F(PullNodeTest, ApplyWithCollation) { // With the collation, this update will pull any string whose reverse is greater than the // reverse of the "abc" string. auto update = fromjson("{$pull : {a: {$gt: 'abc'}}}"); @@ -470,185 +256,85 @@ TEST(PullNodeTest, ApplyWithCollation) { ASSERT_OK(node.init(update["$pull"]["a"], &collator)); Document doc(fromjson("{a: ['zaa', 'zcc', 'zbb', 'zee']}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['zaa', 'zbb']}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: ['zaa', 'zbb']}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: ['zaa', 'zbb']}}"), getLogDoc()); } -TEST(PullNodeTest, ApplyWithCollationDoesNotAffectNonStringMatches) { +TEST_F(PullNodeTest, ApplyWithCollationDoesNotAffectNonStringMatches) { auto update = fromjson("{$pull : {a: {$lt: 1}}}"); CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], &collator)); Document doc(fromjson("{a: [2, 1, 0, -1, -2, -3]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [2, 1]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [2, 1]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [2, 1]}}"), getLogDoc()); } -TEST(PullNodeTest, ApplyWithCollationDoesNotAffectRegexMatches) { +TEST_F(PullNodeTest, ApplyWithCollationDoesNotAffectRegexMatches) { auto update = fromjson("{$pull : {a: /a/}}"); CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], &collator)); Document doc(fromjson("{a: ['b', 'a', 'aab', 'cb', 'bba']}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['b', 'cb']}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: ['b', 'cb']}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: ['b', 'cb']}}"), getLogDoc()); } -TEST(PullNodeTest, ApplyStringLiteralMatchWithCollation) { +TEST_F(PullNodeTest, ApplyStringLiteralMatchWithCollation) { auto update = fromjson("{$pull : {a: 'c'}}"); CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], &collator)); Document doc(fromjson("{a: ['b', 'a', 'aab', 'cb', 'bba']}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: []}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: []}}"), getLogDoc()); } -TEST(PullNodeTest, ApplyCollationDoesNotAffectNumberLiteralMatches) { +TEST_F(PullNodeTest, ApplyCollationDoesNotAffectNumberLiteralMatches) { auto update = fromjson("{$pull : {a: 99}}"); CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], &collator)); Document doc(fromjson("{a: ['a', 99, 'b', 2, 'c', 99, 'd']}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['a', 'b', 2, 'c', 'd']}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: ['a', 'b', 2, 'c', 'd']}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: ['a', 'b', 2, 'c', 'd']}}"), getLogDoc()); } -TEST(PullNodeTest, ApplyStringMatchAfterSetCollator) { +TEST_F(PullNodeTest, ApplyStringMatchAfterSetCollator) { auto update = fromjson("{$pull : {a: 'c'}}"); PullNode node; const CollatorInterface* collator = nullptr; @@ -656,56 +342,27 @@ TEST(PullNodeTest, ApplyStringMatchAfterSetCollator) { // First without a collator. Document doc(fromjson("{ a : ['a', 'b', 'c', 'd'] }")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['a', 'b', 'd']}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); // Now with a collator. CollatorInterfaceMock mockCollator(CollatorInterfaceMock::MockType::kAlwaysEqual); node.setCollator(&mockCollator); - indexesAffected = false; - noop = false; Document doc2(fromjson("{ a : ['a', 'b', 'c', 'd'] }")); - node.apply(doc2.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + resetApplyParams(); + setPathTaken("a"); + result = node.apply(getApplyParams(doc2.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc2); ASSERT_FALSE(doc2.isInPlaceModeEnabled()); } -TEST(PullNodeTest, SetCollatorDoesNotAffectClone) { +TEST_F(PullNodeTest, SetCollatorDoesNotAffectClone) { auto update = fromjson("{$pull : {a: 'c'}}"); PullNode node; const CollatorInterface* collator = nullptr; @@ -718,54 +375,25 @@ TEST(PullNodeTest, SetCollatorDoesNotAffectClone) { // The original node should now have collation. Document doc(fromjson("{ a : ['a', 'b', 'c', 'd'] }")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); // The clone should have exact string matches (no collation). - indexesAffected = false; - noop = false; Document doc2(fromjson("{ a : ['a', 'b', 'c', 'd'] }")); - cloneNode->apply(doc2.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + resetApplyParams(); + setPathTaken("a"); + result = cloneNode->apply(getApplyParams(doc2.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['a', 'b', 'd']}"), doc2); ASSERT_FALSE(doc2.isInPlaceModeEnabled()); } -TEST(PullNodeTest, ApplyComplexDocAndMatching1) { +TEST_F(PullNodeTest, ApplyComplexDocAndMatching1) { auto update = fromjson( "{$pull: {'a.b': {$or: [" " {'y': {$exists: true }}," @@ -776,440 +404,202 @@ TEST(PullNodeTest, ApplyComplexDocAndMatching1) { ASSERT_OK(node.init(update["$pull"]["a.b"], collator)); Document doc(fromjson("{a: {b: [{x: 1}, {y: 'y'}, {x: 2}, {z: 'z'}]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [{x: 1}, {x: 2}]}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.b': [{x: 1}, {x: 2}]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': [{x: 1}, {x: 2}]}}"), getLogDoc()); } -TEST(PullNodeTest, ApplyComplexDocAndMatching2) { +TEST_F(PullNodeTest, ApplyComplexDocAndMatching2) { auto update = fromjson("{$pull: {'a.b': {'y': {$exists: true}}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a.b"], collator)); Document doc(fromjson("{a: {b: [{x: 1}, {y: 'y'}, {x: 2}, {z: 'z'}]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [{x: 1}, {x: 2}, {z: 'z'}]}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.b': [{x: 1}, {x: 2}, {z: 'z'}]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': [{x: 1}, {x: 2}, {z: 'z'}]}}"), getLogDoc()); } -TEST(PullNodeTest, ApplyComplexDocAndMatching3) { +TEST_F(PullNodeTest, ApplyComplexDocAndMatching3) { auto update = fromjson("{$pull: {'a.b': {$in: [{x: 1}, {y: 'y'}]}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a.b"], collator)); Document doc(fromjson("{a: {b: [{x: 1}, {y: 'y'}, {x: 2}, {z: 'z'}]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [{x: 2}, {z: 'z'}]}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.b': [{x: 2}, {z: 'z'}]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': [{x: 2}, {z: 'z'}]}}"), getLogDoc()); } -TEST(PullNodeTest, ApplyFullPredicateWithCollation) { +TEST_F(PullNodeTest, ApplyFullPredicateWithCollation) { auto update = fromjson("{$pull: {'a.b': {x: 'blah'}}}"); CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); PullNode node; ASSERT_OK(node.init(update["$pull"]["a.b"], &collator)); Document doc(fromjson("{a: {b: [{x: 'foo', y: 1}, {x: 'bar', y: 2}, {x: 'baz', y: 3}]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: []}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.b': []}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': []}}"), getLogDoc()); } -TEST(PullNodeTest, ApplyScalarValueMod) { +TEST_F(PullNodeTest, ApplyScalarValueMod) { auto update = fromjson("{$pull: {a: 1}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], collator)); Document doc(fromjson("{a: [1, 2, 1, 2, 1, 2]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [2, 2, 2]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [2, 2, 2]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [2, 2, 2]}}"), getLogDoc()); } -TEST(PullNodeTest, ApplyObjectValueMod) { +TEST_F(PullNodeTest, ApplyObjectValueMod) { auto update = fromjson("{$pull: {a: {y: 2}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], collator)); Document doc(fromjson("{a: [{x: 1}, {y: 2}, {x: 1}, {y: 2}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{x: 1}, {x: 1}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [{x: 1}, {x: 1}]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [{x: 1}, {x: 1}]}}"), getLogDoc()); } -TEST(PullNodeTest, DocumentationExample1) { +TEST_F(PullNodeTest, DocumentationExample1) { auto update = fromjson("{$pull: {flags: 'msr'}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["flags"], collator)); Document doc(fromjson("{flags: ['vme', 'de', 'pse', 'tsc', 'msr', 'pae', 'mce']}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("flags"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["flags"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("flags"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["flags"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{flags: ['vme', 'de', 'pse', 'tsc', 'pae', 'mce']}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {flags: ['vme', 'de', 'pse', 'tsc', 'pae', 'mce']}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {flags: ['vme', 'de', 'pse', 'tsc', 'pae', 'mce']}}"), + getLogDoc()); } -TEST(PullNodeTest, DocumentationExample2a) { +TEST_F(PullNodeTest, DocumentationExample2a) { auto update = fromjson("{$pull: {votes: 7}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["votes"], collator)); Document doc(fromjson("{votes: [3, 5, 6, 7, 7, 8]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("votes"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["votes"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("votes"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["votes"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{votes: [3, 5, 6, 8]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {votes: [3, 5, 6, 8]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {votes: [3, 5, 6, 8]}}"), getLogDoc()); } -TEST(PullNodeTest, DocumentationExample2b) { +TEST_F(PullNodeTest, DocumentationExample2b) { auto update = fromjson("{$pull: {votes: {$gt: 6}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["votes"], collator)); Document doc(fromjson("{votes: [3, 5, 6, 7, 7, 8]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("votes"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["votes"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("votes"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["votes"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{votes: [3, 5, 6]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {votes: [3, 5, 6]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {votes: [3, 5, 6]}}"), getLogDoc()); } -TEST(PullNodeTest, ApplyPullWithObjectValueToArrayWithNonObjectValue) { +TEST_F(PullNodeTest, ApplyPullWithObjectValueToArrayWithNonObjectValue) { auto update = fromjson("{$pull: {a: {x: 1}}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["a"], collator)); Document doc(fromjson("{a: [{x: 1}, 2]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [2]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [2]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [2]}}"), getLogDoc()); } -TEST(PullNodeTest, CannotModifyImmutableField) { +TEST_F(PullNodeTest, CannotModifyImmutableField) { auto update = fromjson("{$pull: {'_id.a': 1}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["_id.a"], collator)); Document doc(fromjson("{_id: {a: [0, 1, 2]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("_id.a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("_id"); - immutablePaths.insert(&path); - UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathTaken("_id.a"); + addImmutablePath("_id"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root()["_id"]["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root()["_id"]["a"])), UserException, ErrorCodes::ImmutableField, "Performing an update on the path '_id.a' would modify the immutable field '_id'"); } -TEST(PullNodeTest, SERVER_3988) { +TEST_F(PullNodeTest, SERVER_3988) { auto update = fromjson("{$pull: {y: /yz/}}"); const CollatorInterface* collator = nullptr; PullNode node; ASSERT_OK(node.init(update["$pull"]["y"], collator)); Document doc(fromjson("{x: 1, y: [2, 3, 4, 'abc', 'xyz']}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("y"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["y"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("y"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["y"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{x: 1, y: [2, 3, 4, 'abc']}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {y: [2, 3, 4, 'abc']}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {y: [2, 3, 4, 'abc']}}"), getLogDoc()); } +} // namespace } // namespace mongo diff --git a/src/mongo/db/update/pullall_node_test.cpp b/src/mongo/db/update/pullall_node_test.cpp index 38c1c7ff2fb..1928b5f6223 100644 --- a/src/mongo/db/update/pullall_node_test.cpp +++ b/src/mongo/db/update/pullall_node_test.cpp @@ -34,12 +34,14 @@ #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/db/update/update_node_test_fixture.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" +namespace mongo { namespace { -using namespace mongo; +using PullAllNodeTest = UpdateNodeTest; using mongo::mutablebson::Document; using mongo::mutablebson::Element; using mongo::mutablebson::countChildren; @@ -80,409 +82,192 @@ TEST(PullAllNodeTest, InitWithBoolFails) { ASSERT_EQUALS(ErrorCodes::BadValue, status); } -TEST(PullAllNodeTest, TargetNotFound) { +TEST_F(PullAllNodeTest, TargetNotFound) { auto update = fromjson("{$pullAll : {b: [1]}}"); const CollatorInterface* collator = nullptr; PullAllNode node; ASSERT_OK(node.init(update["$pullAll"]["b"], collator)); Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 'a', {r: 1, b: 2}]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(PullAllNodeTest, TargetArrayElementNotFound) { +TEST_F(PullAllNodeTest, TargetArrayElementNotFound) { auto update = fromjson("{$pullAll : {'a.2': [1]}}"); const CollatorInterface* collator = nullptr; PullAllNode node; ASSERT_OK(node.init(update["$pullAll"]["a.2"], collator)); Document doc(fromjson("{a: [1, 2]}")); - FieldRef pathToCreate("2"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("2"); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 2]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(PullAllNodeTest, ApplyToNonArrayFails) { +TEST_F(PullAllNodeTest, ApplyToNonArrayFails) { auto update = fromjson("{$pullAll : {'a.0': [1, 2]}}"); const CollatorInterface* collator = nullptr; PullAllNode node; ASSERT_OK(node.init(update["$pullAll"]["a.0"], collator)); Document doc(fromjson("{a: [1, 2]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.0"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"][0], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + setPathTaken("a.0"); + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"][0])), UserException, ErrorCodes::BadValue, "Cannot apply $pull to a non-array value"); } -TEST(PullAllNodeTest, ApplyWithSingleNumber) { +TEST_F(PullAllNodeTest, ApplyWithSingleNumber) { auto update = fromjson("{$pullAll : {a: [1]}}"); const CollatorInterface* collator = nullptr; PullAllNode node; ASSERT_OK(node.init(update["$pullAll"]["a"], collator)); Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['a', {r: 1, b: 2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: ['a', {r: 1, b: 2}]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: ['a', {r: 1, b: 2}]}}"), getLogDoc()); } -TEST(PullAllNodeTest, ApplyNoIndexDataNoLogBuilder) { +TEST_F(PullAllNodeTest, ApplyNoIndexDataNoLogBuilder) { auto update = fromjson("{$pullAll : {a: [1]}}"); const CollatorInterface* collator = nullptr; PullAllNode node; ASSERT_OK(node.init(update["$pullAll"]["a"], collator)); Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + setLogBuilderToNull(); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['a', {r: 1, b: 2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(PullAllNodeTest, ApplyWithElementNotPresentInArray) { +TEST_F(PullAllNodeTest, ApplyWithElementNotPresentInArray) { auto update = fromjson("{$pullAll : {a: ['r']}}"); const CollatorInterface* collator = nullptr; PullAllNode node; ASSERT_OK(node.init(update["$pullAll"]["a"], collator)); Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 'a', {r: 1, b: 2}]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(PullAllNodeTest, ApplyWithWithTwoElements) { +TEST_F(PullAllNodeTest, ApplyWithWithTwoElements) { auto update = fromjson("{$pullAll : {a: [1, 'a']}}"); const CollatorInterface* collator = nullptr; PullAllNode node; ASSERT_OK(node.init(update["$pullAll"]["a"], collator)); Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{r: 1, b: 2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [{r: 1, b: 2}]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [{r: 1, b: 2}]}}"), getLogDoc()); } -TEST(PullAllNodeTest, ApplyWithAllArrayElements) { +TEST_F(PullAllNodeTest, ApplyWithAllArrayElements) { auto update = fromjson("{$pullAll : {a: [1, 'a', {r: 1, b: 2}]}}"); const CollatorInterface* collator = nullptr; PullAllNode node; ASSERT_OK(node.init(update["$pullAll"]["a"], collator)); Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: []}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: []}}"), getLogDoc()); } -TEST(PullAllNodeTest, ApplyWithAllArrayElementsButOutOfOrder) { +TEST_F(PullAllNodeTest, ApplyWithAllArrayElementsButOutOfOrder) { auto update = fromjson("{$pullAll : {a: [{r: 1, b: 2}, 1, 'a']}}"); const CollatorInterface* collator = nullptr; PullAllNode node; ASSERT_OK(node.init(update["$pullAll"]["a"], collator)); Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: []}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: []}}"), getLogDoc()); } -TEST(PullAllNodeTest, ApplyWithAllArrayElementsAndThenSome) { +TEST_F(PullAllNodeTest, ApplyWithAllArrayElementsAndThenSome) { auto update = fromjson("{$pullAll : {a: [2, 3, 1, 'r', {r: 1, b: 2}, 'a']}}"); const CollatorInterface* collator = nullptr; PullAllNode node; ASSERT_OK(node.init(update["$pullAll"]["a"], collator)); Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: []}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: []}}"), getLogDoc()); } -TEST(PullAllNodeTest, ApplyWithCollator) { +TEST_F(PullAllNodeTest, ApplyWithCollator) { auto update = fromjson("{$pullAll : {a: ['FOO', 'BAR']}}"); CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kToLowerString); PullAllNode node; ASSERT_OK(node.init(update["$pullAll"]["a"], &collator)); Document doc(fromjson("{a: ['foo', 'bar', 'baz']}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['baz']}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: ['baz']}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: ['baz']}}"), getLogDoc()); } -TEST(PullAllNodeTest, ApplyAfterSetCollator) { +TEST_F(PullAllNodeTest, ApplyAfterSetCollator) { auto update = fromjson("{$pullAll : {a: ['FOO', 'BAR']}}"); const CollatorInterface* collator = nullptr; PullAllNode node; @@ -490,52 +275,24 @@ TEST(PullAllNodeTest, ApplyAfterSetCollator) { // First without a collator. Document doc(fromjson("{a: ['foo', 'bar', 'baz']}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); + setPathTaken("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); ASSERT_EQUALS(fromjson("{a: ['foo', 'bar', 'baz']}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); // Now with a collator. CollatorInterfaceMock mockCollator(CollatorInterfaceMock::MockType::kToLowerString); node.setCollator(&mockCollator); - indexesAffected = false; - noop = false; Document doc2(fromjson("{a: ['foo', 'bar', 'baz']}")); - node.apply(doc2.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + resetApplyParams(); + setPathTaken("a"); + result = node.apply(getApplyParams(doc2.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['baz']}"), doc2); ASSERT_FALSE(doc2.isInPlaceModeEnabled()); } +} // namespace } // namespace mongo diff --git a/src/mongo/db/update/rename_node.cpp b/src/mongo/db/update/rename_node.cpp index b12810e2636..bf108c54aa2 100644 --- a/src/mongo/db/update/rename_node.cpp +++ b/src/mongo/db/update/rename_node.cpp @@ -63,7 +63,7 @@ public: return Status::OK(); } - void updateExistingElement(mutablebson::Element* element, bool* noop) const final { + bool updateExistingElement(mutablebson::Element* element) const final { // In the case of a $rename where the source and destination have the same value, (e.g., we // are applying {$rename: {a: b}} to the document {a: "foo", b: "foo"}), there's no need to // modify the destination element. However, the source and destination values must be @@ -71,10 +71,10 @@ public: StringData::ComparatorInterface* comparator = nullptr; auto considerFieldName = false; if (_elemToSet.compareWithElement(*element, comparator, considerFieldName) != 0) { - *noop = false; invariantOK(element->setValueElement(_elemToSet)); + return true; } else { - *noop = true; + return false; } } @@ -138,26 +138,13 @@ Status RenameNode::init(BSONElement modExpr, const CollatorInterface* collator) return Status::OK(); } -void RenameNode::apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const { - *indexesAffected = false; - *noop = false; - +UpdateNode::ApplyResult RenameNode::apply(ApplyParams applyParams) const { // It would make sense to store fromFieldRef and toFieldRef as members during // RenameNode::init(), but FieldRef is not copyable. FieldRef fromFieldRef(_val.fieldName()); FieldRef toFieldRef(_val.valueStringData()); - mutablebson::Document& document = element.getDocument(); + mutablebson::Document& document = applyParams.element.getDocument(); size_t fromIdxFound; mutablebson::Element fromElement(document.end()); @@ -175,8 +162,7 @@ void RenameNode::apply(mutablebson::Element element, // The element we want to rename does not exist. When that happens, we treat the operation // as a no-op. - *noop = true; - return; + return ApplyResult::noopResult(); } // Renaming through an array is prohibited. Check that our source path does not contain an @@ -200,7 +186,8 @@ void RenameNode::apply(mutablebson::Element element, // Check that our destination path does not contain an array. (If the rename will overwrite an // existing element, that element may be an array. Iff pathToCreate is empty, "element" // represents an element that we are going to overwrite.) - for (auto currentElement = pathToCreate->empty() ? element.parent() : element; + for (auto currentElement = applyParams.pathToCreate->empty() ? applyParams.element.parent() + : applyParams.element; currentElement != document.root(); currentElement = currentElement.parent()) { invariant(currentElement.ok()); @@ -217,20 +204,8 @@ void RenameNode::apply(mutablebson::Element element, } } - bool setAffectedIndexes = false; - bool setWasNoop = false; SetElementNode setElement(fromElement); - setElement.apply(element, - pathToCreate, - pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &setAffectedIndexes, - &setWasNoop); + auto setElementApplyResult = setElement.apply(applyParams); auto leftSibling = fromElement.leftSibling(); auto rightSibling = fromElement.rightSibling(); @@ -238,15 +213,15 @@ void RenameNode::apply(mutablebson::Element element, invariant(fromElement.parent().ok()); invariantOK(fromElement.remove()); - if (setAffectedIndexes) { - *indexesAffected = true; - } else if (indexData && indexData->mightBeIndexed(fromFieldRef.dottedField())) { - *indexesAffected = true; - } else { - // *indexedAffected remains false + ApplyResult applyResult; + + if (!applyParams.indexData || + (!setElementApplyResult.indexesAffected && + !applyParams.indexData->mightBeIndexed(fromFieldRef.dottedField()))) { + applyResult.indexesAffected = false; } - if (validateForStorage) { + if (applyParams.validateForStorage) { // Validate the left and right sibling, in case this element was part of a DBRef. if (leftSibling.ok()) { @@ -263,7 +238,8 @@ void RenameNode::apply(mutablebson::Element element, } // Ensure we are not changing any immutable paths. - for (auto immutablePath = immutablePaths.begin(); immutablePath != immutablePaths.end(); + for (auto immutablePath = applyParams.immutablePaths.begin(); + immutablePath != applyParams.immutablePaths.end(); ++immutablePath) { uassert(ErrorCodes::ImmutableField, str::stream() << "Unsetting the path '" << fromFieldRef.dottedField() @@ -275,9 +251,11 @@ void RenameNode::apply(mutablebson::Element element, } // Log the $unset. The $set was already logged by SetElementNode::apply(). - if (logBuilder) { - uassertStatusOK(logBuilder->addToUnsets(fromFieldRef.dottedField())); + if (applyParams.logBuilder) { + uassertStatusOK(applyParams.logBuilder->addToUnsets(fromFieldRef.dottedField())); } + + return applyResult; } } // namespace mongo diff --git a/src/mongo/db/update/rename_node.h b/src/mongo/db/update/rename_node.h index 096740e1907..240ac32ebd4 100644 --- a/src/mongo/db/update/rename_node.h +++ b/src/mongo/db/update/rename_node.h @@ -51,17 +51,7 @@ public: void setCollator(const CollatorInterface* collator) final {} - void apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const final; + ApplyResult apply(ApplyParams applyParams) const final; private: BSONElement _val; diff --git a/src/mongo/db/update/rename_node_test.cpp b/src/mongo/db/update/rename_node_test.cpp index 95a6d337d4a..84f46f0651f 100644 --- a/src/mongo/db/update/rename_node_test.cpp +++ b/src/mongo/db/update/rename_node_test.cpp @@ -33,12 +33,14 @@ #include "mongo/bson/mutable/algorithm.h" #include "mongo/bson/mutable/mutable_bson_test_utils.h" #include "mongo/db/json.h" +#include "mongo/db/update/update_node_test_fixture.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" namespace mongo { namespace { +using RenameNodeTest = UpdateNodeTest; using mongo::mutablebson::Document; using mongo::mutablebson::Element; using mongo::mutablebson::countChildren; @@ -107,972 +109,439 @@ TEST(RenameNodeTest, MoveToSelfNotAllowed) { ASSERT_EQUALS(ErrorCodes::BadValue, status); } -TEST(RenameNodeTest, SimpleNumberAtRoot) { +TEST_F(RenameNodeTest, SimpleNumberAtRoot) { auto update = fromjson("{$rename: {'a': 'b'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a"], collator)); Document doc(fromjson("{a: 2}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 2}"), doc); - ASSERT_EQUALS(fromjson("{$set: {b: 2}, $unset: {a: true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {b: 2}, $unset: {a: true}}"), getLogDoc()); } -TEST(RenameNodeTest, ToExistsAtSameLevel) { +TEST_F(RenameNodeTest, ToExistsAtSameLevel) { auto update = fromjson("{$rename: {'a': 'b'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a"], collator)); Document doc(fromjson("{a: 2, b: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 2}"), doc); - ASSERT_EQUALS(fromjson("{$set: {b: 2}, $unset: {a: true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {b: 2}, $unset: {a: true}}"), getLogDoc()); } -TEST(RenameNodeTest, ToAndFromHaveSameValue) { +TEST_F(RenameNodeTest, ToAndFromHaveSameValue) { auto update = fromjson("{$rename: {'a': 'b'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a"], collator)); Document doc(fromjson("{a: 2, b: 2}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 2}"), doc); - ASSERT_EQUALS(fromjson("{$unset: {a: true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$unset: {a: true}}"), getLogDoc()); } -TEST(RenameNodeTest, FromDottedElement) { +TEST_F(RenameNodeTest, FromDottedElement) { auto update = fromjson("{$rename: {'a.c': 'b'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a.c"], collator)); Document doc(fromjson("{a: {c: {d: 6}}, b: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {}, b: {d: 6}}"), doc); - ASSERT_EQUALS(fromjson("{$set: {b: {d: 6}}, $unset: {'a.c': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {b: {d: 6}}, $unset: {'a.c': true}}"), getLogDoc()); } -TEST(RenameNodeTest, RenameToExistingNestedFieldDoesNotReorderFields) { +TEST_F(RenameNodeTest, RenameToExistingNestedFieldDoesNotReorderFields) { auto update = fromjson("{$rename: {'c.d': 'a.b.c'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["c.d"], collator)); Document doc(fromjson("{a: {b: {c: 1, d: 2}}, b: 3, c: {d: 4}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b.c"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"]["c"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b.c"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: {c: 4, d: 2}}, b: 3, c: {}}"), doc); - ASSERT_EQUALS(fromjson("{$set: {'a.b.c': 4}, $unset: {'c.d': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b.c': 4}, $unset: {'c.d': true}}"), getLogDoc()); } -TEST(RenameNodeTest, MissingCompleteTo) { +TEST_F(RenameNodeTest, MissingCompleteTo) { auto update = fromjson("{$rename: {a: 'c.r.d'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a"], collator)); Document doc(fromjson("{a: 2, b: 1, c: {}}")); - FieldRef pathToCreate("r.d"); - FieldRef pathTaken("c"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["c"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("r.d"); + setPathTaken("c"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["c"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 1, c: {r: {d: 2}}}"), doc); - ASSERT_EQUALS(fromjson("{$set: {'c.r.d': 2}, $unset: {'a': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'c.r.d': 2}, $unset: {'a': true}}"), getLogDoc()); } -TEST(RenameNodeTest, ToIsCompletelyMissing) { +TEST_F(RenameNodeTest, ToIsCompletelyMissing) { auto update = fromjson("{$rename: {a: 'b.c.d'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a"], collator)); Document doc(fromjson("{a: 2}")); - FieldRef pathToCreate("b.c.d"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("b.c.d"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: {c: {d: 2}}}"), doc); - ASSERT_EQUALS(fromjson("{$set: {'b.c.d': 2}, $unset: {'a': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'b.c.d': 2}, $unset: {'a': true}}"), getLogDoc()); } -TEST(RenameNodeTest, ToMissingDottedField) { +TEST_F(RenameNodeTest, ToMissingDottedField) { auto update = fromjson("{$rename: {a: 'b.c.d'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a"], collator)); Document doc(fromjson("{a: [{a:2, b:1}]}")); - FieldRef pathToCreate("b.c.d"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("b.c.d"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: {c: {d: [{a:2, b:1}]}}}"), doc); - ASSERT_EQUALS(fromjson("{$set: {'b.c.d': [{a:2, b:1}]}, $unset: {'a': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'b.c.d': [{a:2, b:1}]}, $unset: {'a': true}}"), getLogDoc()); } -TEST(RenameNodeTest, MoveIntoArray) { +TEST_F(RenameNodeTest, MoveIntoArray) { auto update = fromjson("{$rename: {b: 'a.2'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["b"], collator)); Document doc(fromjson("{_id: 'test_object', a: [1, 2], b: 2}")); - FieldRef pathToCreate("2"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + setPathToCreate("2"); + setPathTaken("a"); + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::BadValue, "The destination field cannot be an array element, 'a.2' in doc " "with _id: \"test_object\" has an array field called 'a'"); } -TEST(RenameNodeTest, MoveIntoArrayNoId) { +TEST_F(RenameNodeTest, MoveIntoArrayNoId) { auto update = fromjson("{$rename: {b: 'a.2'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["b"], collator)); Document doc(fromjson("{a: [1, 2], b: 2}")); - FieldRef pathToCreate("2"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + setPathToCreate("2"); + setPathTaken("a"); + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::BadValue, "The destination field cannot be an array element, 'a.2' in doc " "with no id has an array field called 'a'"); } -TEST(RenameNodeTest, MoveToArrayElement) { +TEST_F(RenameNodeTest, MoveToArrayElement) { auto update = fromjson("{$rename: {b: 'a.1'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["b"], collator)); Document doc(fromjson("{_id: 'test_object', a: [1, 2], b: 2}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.1"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"]["1"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + setPathTaken("a.1"); + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"]["1"])), UserException, ErrorCodes::BadValue, "The destination field cannot be an array element, 'a.1' in doc " "with _id: \"test_object\" has an array field called 'a'"); } -TEST(RenameNodeTest, MoveOutOfArray) { +TEST_F(RenameNodeTest, MoveOutOfArray) { auto update = fromjson("{$rename: {'a.0': 'b'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a.0"], collator)); Document doc(fromjson("{_id: 'test_object', a: [1, 2]}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + setPathToCreate("b"); + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::BadValue, "The source field cannot be an array element, 'a.0' in doc with " "_id: \"test_object\" has an array field called 'a'"); } -TEST(RenameNodeTest, MoveNonexistentEmbeddedFieldOut) { +TEST_F(RenameNodeTest, MoveNonexistentEmbeddedFieldOut) { auto update = fromjson("{$rename: {'a.a': 'b'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a.a"], collator)); Document doc(fromjson("{a: [{a: 1}, {b: 2}]}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; + setPathToCreate("b"); + addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::PathNotViable, "cannot use the part (a of a.a) to traverse the element ({a: [ { a: 1 }, { b: 2 } ]})"); } -TEST(RenameNodeTest, MoveEmbeddedFieldOutWithElementNumber) { +TEST_F(RenameNodeTest, MoveEmbeddedFieldOutWithElementNumber) { auto update = fromjson("{$rename: {'a.0.a': 'b'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a.0.a"], collator)); Document doc(fromjson("{_id: 'test_object', a: [{a: 1}, {b: 2}]}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + setPathToCreate("b"); + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::BadValue, "The source field cannot be an array element, 'a.0.a' in doc with " "_id: \"test_object\" has an array field called 'a'"); } -TEST(RenameNodeTest, ReplaceArrayField) { +TEST_F(RenameNodeTest, ReplaceArrayField) { auto update = fromjson("{$rename: {a: 'b'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a"], collator)); Document doc(fromjson("{a: 2, b: []}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 2}"), doc); - ASSERT_EQUALS(fromjson("{$set: {b: 2}, $unset: {a: true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {b: 2}, $unset: {a: true}}"), getLogDoc()); } -TEST(RenameNodeTest, ReplaceWithArrayField) { +TEST_F(RenameNodeTest, ReplaceWithArrayField) { auto update = fromjson("{$rename: {a: 'b'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a"], collator)); Document doc(fromjson("{a: [], b: 2}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: []}"), doc); - ASSERT_EQUALS(fromjson("{$set: {b: []}, $unset: {a: true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {b: []}, $unset: {a: true}}"), getLogDoc()); } -TEST(RenameNodeTest, CanRenameFromInvalidFieldName) { +TEST_F(RenameNodeTest, CanRenameFromInvalidFieldName) { auto update = fromjson("{$rename: {'$a': 'a'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["$a"], collator)); Document doc(fromjson("{$a: 2}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); - ASSERT_EQUALS(fromjson("{$set: {a: 2}, $unset: {'$a': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 2}, $unset: {'$a': true}}"), getLogDoc()); } -TEST(RenameNodeTest, RenameWithoutLogBuilderOrIndexData) { +TEST_F(RenameNodeTest, RenameWithoutLogBuilderOrIndexData) { auto update = fromjson("{$rename: {'a': 'b'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a"], collator)); Document doc(fromjson("{a: 2}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); + setPathToCreate("b"); + setLogBuilderToNull(); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{b: 2}"), doc); } -TEST(RenameNodeTest, RenameFromNonExistentPathIsNoOp) { +TEST_F(RenameNodeTest, RenameFromNonExistentPathIsNoOp) { auto update = fromjson("{$rename: {'a': 'b'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a"], collator)); Document doc(fromjson("{b: 2}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["b"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 2}"), doc); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(RenameNodeTest, ApplyCannotRemoveRequiredPartOfDBRef) { +TEST_F(RenameNodeTest, ApplyCannotRemoveRequiredPartOfDBRef) { auto update = fromjson("{$rename: {'a.$id': 'b'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a.$id"], collator)); Document doc(fromjson("{a: {$ref: 'c', $id: 0}}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + setPathToCreate("b"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::InvalidDBRef, "The DBRef $ref field must be followed by a $id field"); } -TEST(RenameNodeTest, ApplyCanRemoveRequiredPartOfDBRefIfValidateForStorageIsFalse) { +TEST_F(RenameNodeTest, ApplyCanRemoveRequiredPartOfDBRefIfValidateForStorageIsFalse) { auto update = fromjson("{$rename: {'a.$id': 'b'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a.$id"], collator)); Document doc(fromjson("{a: {$ref: 'c', $id: 0}}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = false; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("b"); + addIndexedPath("a"); + setValidateForStorage(false); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); auto updated = BSON("a" << BSON("$ref" << "c") << "b" << 0); ASSERT_EQUALS(updated, doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'b': 0}, $unset: {'a.$id': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'b': 0}, $unset: {'a.$id': true}}"), getLogDoc()); } -TEST(RenameNodeTest, ApplyCannotRemoveImmutablePath) { +TEST_F(RenameNodeTest, ApplyCannotRemoveImmutablePath) { auto update = fromjson("{$rename: {'a.b': 'c'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a.b"], collator)); Document doc(fromjson("{a: {b: 1}}")); - FieldRef pathToCreate("c"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathToCreate("c"); + addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::ImmutableField, "Unsetting the path 'a.b' using $rename would modify the immutable field 'a.b'"); } -TEST(RenameNodeTest, ApplyCannotRemovePrefixOfImmutablePath) { +TEST_F(RenameNodeTest, ApplyCannotRemovePrefixOfImmutablePath) { auto update = fromjson("{$rename: {a: 'c'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a"], collator)); Document doc(fromjson("{a: {b: 1}}")); - FieldRef pathToCreate("c"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathToCreate("c"); + addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::ImmutableField, "Unsetting the path 'a' using $rename would modify the immutable field 'a.b'"); } -TEST(RenameNodeTest, ApplyCannotRemoveSuffixOfImmutablePath) { +TEST_F(RenameNodeTest, ApplyCannotRemoveSuffixOfImmutablePath) { auto update = fromjson("{$rename: {'a.b.c': 'd'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a.b.c"], collator)); Document doc(fromjson("{a: {b: {c: 1}}}")); - FieldRef pathToCreate("d"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathToCreate("d"); + addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::ImmutableField, "Unsetting the path 'a.b.c' using $rename would modify the immutable field 'a.b'"); } -TEST(RenameNodeTest, ApplyCanRemoveImmutablePathIfNoop) { +TEST_F(RenameNodeTest, ApplyCanRemoveImmutablePathIfNoop) { auto update = fromjson("{$rename: {'a.b.c': 'd'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a.b.c"], collator)); Document doc(fromjson("{a: {b: {}}}")); - FieldRef pathToCreate("d"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = false; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("d"); + addImmutablePath("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: {}}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(RenameNodeTest, ApplyCannotCreateDollarPrefixedField) { +TEST_F(RenameNodeTest, ApplyCannotCreateDollarPrefixedField) { auto update = fromjson("{$rename: {a: '$bad'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a"], collator)); Document doc(fromjson("{a: 0}")); - FieldRef pathToCreate("$bad"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathToCreate("$bad"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::DollarPrefixedFieldName, "The dollar ($) prefixed field '$bad' in '$bad' is not valid for storage."); } -TEST(RenameNodeTest, ApplyCannotOverwriteImmutablePath) { +TEST_F(RenameNodeTest, ApplyCannotOverwriteImmutablePath) { auto update = fromjson("{$rename: {a: 'b'}}"); const CollatorInterface* collator = nullptr; RenameNode node; ASSERT_OK(node.init(update["$rename"]["a"], collator)); Document doc(fromjson("{a: 0, b: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathTaken("b"); + addImmutablePath("b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root()["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root()["b"])), UserException, ErrorCodes::ImmutableField, "Updating the path 'b' to b: 0 would modify the immutable field 'b'"); diff --git a/src/mongo/db/update/set_node.cpp b/src/mongo/db/update/set_node.cpp index 8980247c8d6..7d824c044fc 100644 --- a/src/mongo/db/update/set_node.cpp +++ b/src/mongo/db/update/set_node.cpp @@ -42,14 +42,14 @@ Status SetNode::init(BSONElement modExpr, const CollatorInterface* collator) { return Status::OK(); } -void SetNode::updateExistingElement(mutablebson::Element* element, bool* noop) const { +bool SetNode::updateExistingElement(mutablebson::Element* element) const { // If 'element' is deserialized, then element.getValue() will be EOO, which will never equal // _val. if (element->getValue().binaryEqualValues(_val)) { - *noop = true; + return false; } else { - *noop = false; invariantOK(element->setValueBSONElement(_val)); + return true; } } diff --git a/src/mongo/db/update/set_node.h b/src/mongo/db/update/set_node.h index 915b214b9b5..626d47be7ed 100644 --- a/src/mongo/db/update/set_node.h +++ b/src/mongo/db/update/set_node.h @@ -47,7 +47,7 @@ public: void setCollator(const CollatorInterface* collator) final {} protected: - void updateExistingElement(mutablebson::Element* element, bool* noop) const final; + bool updateExistingElement(mutablebson::Element* element) const final; void setValueForNewElement(mutablebson::Element* element) const final; private: diff --git a/src/mongo/db/update/set_node_test.cpp b/src/mongo/db/update/set_node_test.cpp index 0df17390b0b..932798e61e7 100644 --- a/src/mongo/db/update/set_node_test.cpp +++ b/src/mongo/db/update/set_node_test.cpp @@ -33,12 +33,14 @@ #include "mongo/bson/mutable/algorithm.h" #include "mongo/bson/mutable/mutable_bson_test_utils.h" #include "mongo/db/json.h" +#include "mongo/db/update/update_node_test_fixture.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" +namespace mongo { namespace { -using namespace mongo; +using SetNodeTest = UpdateNodeTest; using mongo::mutablebson::Document; using mongo::mutablebson::Element; using mongo::mutablebson::countChildren; @@ -57,368 +59,177 @@ TEST(SetNodeTest, InitSucceedsForNonemptyElement) { ASSERT_OK(node.init(update["$set"]["a"], collator)); } -TEST(SetNodeTest, ApplyNoOp) { +TEST_F(SetNodeTest, ApplyNoOp) { auto update = fromjson("{$set: {a: 5}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 5}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(SetNodeTest, ApplyEmptyPathToCreate) { +TEST_F(SetNodeTest, ApplyEmptyPathToCreate) { auto update = fromjson("{$set: {a: 6}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 6}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: 6}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 6}}"), getLogDoc()); } -TEST(SetNodeTest, ApplyCreatePath) { +TEST_F(SetNodeTest, ApplyCreatePath) { auto update = fromjson("{$set: {'a.b.c': 6}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b.c"], collator)); Document doc(fromjson("{a: {d: 5}}")); - FieldRef pathToCreate("b.c"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("b.c"); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {d: 5, b: {c: 6}}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.b.c': 6}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b.c': 6}}"), getLogDoc()); } -TEST(SetNodeTest, ApplyCreatePathFromRoot) { +TEST_F(SetNodeTest, ApplyCreatePathFromRoot) { auto update = fromjson("{$set: {'a.b': 6}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{c: 5}")); - FieldRef pathToCreate("a.b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{c: 5, a: {b: 6}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.b': 6}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.b': 6}}"), getLogDoc()); } -TEST(SetNodeTest, ApplyPositional) { +TEST_F(SetNodeTest, ApplyPositional) { auto update = fromjson("{$set: {'a.$': 6}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.$"], collator)); Document doc(fromjson("{a: [0, 1, 2]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.1"); - StringData matchedField("1"); - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["1"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.1"); + setMatchedField("1"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["1"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 6, 2]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.1': 6}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.1': 6}}"), getLogDoc()); } -TEST(SetNodeTest, ApplyNonViablePathToCreate) { +TEST_F(SetNodeTest, ApplyNonViablePathToCreate) { auto update = fromjson("{$set: {'a.b': 5}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + setPathToCreate("b"); + setPathTaken("a"); + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::PathNotViable, "Cannot create field 'b' in element {a: 5}"); } -TEST(SetNodeTest, ApplyNonViablePathToCreateFromReplicationIsNoOp) { +TEST_F(SetNodeTest, ApplyNonViablePathToCreateFromReplicationIsNoOp) { auto update = fromjson("{$set: {'a.b': 5}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("b"); + setPathTaken("a"); + addIndexedPath("a"); + setFromReplication(true); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 5}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(SetNodeTest, ApplyNoIndexDataNoLogBuilder) { +TEST_F(SetNodeTest, ApplyNoIndexDataNoLogBuilder) { auto update = fromjson("{$set: {a: 6}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + setLogBuilderToNull(); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 6}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyDoesNotAffectIndexes) { +TEST_F(SetNodeTest, ApplyDoesNotAffectIndexes) { auto update = fromjson("{$set: {a: 6}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("b"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 6}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, TypeChangeIsNotANoop) { +TEST_F(SetNodeTest, TypeChangeIsNotANoop) { auto update = fromjson("{$set: {a: NumberLong(2)}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: NumberInt(2)}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberLong(2)}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, IdentityOpOnDeserializedIsNotANoOp) { +TEST_F(SetNodeTest, IdentityOpOnDeserializedIsNotANoOp) { // Apply an op that would be a no-op. auto update = fromjson("{$set: {a: {b : NumberInt(2)}}}"); const CollatorInterface* collator = nullptr; @@ -429,1503 +240,707 @@ TEST(SetNodeTest, IdentityOpOnDeserializedIsNotANoOp) { // Apply a mutation to the document that will make it non-serialized. doc.root()["a"]["b"].setValueInt(2).transitional_ignore(); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b : NumberInt(2)}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyEmptyDocument) { +TEST_F(SetNodeTest, ApplyEmptyDocument) { auto update = fromjson("{$set: {a: 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyInPlace) { +TEST_F(SetNodeTest, ApplyInPlace) { auto update = fromjson("{$set: {a: 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyOverridePath) { +TEST_F(SetNodeTest, ApplyOverridePath) { auto update = fromjson("{$set: {a: 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: {b: 1}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyChangeType) { +TEST_F(SetNodeTest, ApplyChangeType) { auto update = fromjson("{$set: {a: 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: 'str'}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyNewPath) { +TEST_F(SetNodeTest, ApplyNewPath) { auto update = fromjson("{$set: {a: 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{b: 1}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 1, a: 2}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyLog) { +TEST_F(SetNodeTest, ApplyLog) { auto update = fromjson("{$set: {a: 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); + setPathTaken("a"); + node.apply(getApplyParams(doc.root()["a"])); ASSERT_EQUALS(fromjson("{a: 2}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {a: 2}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {a: 2}}"), getLogDoc()); } -TEST(SetNodeTest, ApplyNoOpDottedPath) { +TEST_F(SetNodeTest, ApplyNoOpDottedPath) { auto update = fromjson("{$set: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{a: {b: 2}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b : 2}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, TypeChangeOnDottedPathIsNotANoOp) { +TEST_F(SetNodeTest, TypeChangeOnDottedPathIsNotANoOp) { auto update = fromjson("{$set: {'a.b': NumberInt(2)}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{a: {b: NumberLong(2)}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b : NumberLong(2)}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyPathNotViable) { +TEST_F(SetNodeTest, ApplyPathNotViable) { auto update = fromjson("{$set: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{a:1}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + setPathToCreate("b"); + setPathTaken("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::PathNotViable, "Cannot create field 'b' in element {a: 1}"); } -TEST(SetNodeTest, ApplyPathNotViableArrray) { +TEST_F(SetNodeTest, ApplyPathNotViableArrray) { auto update = fromjson("{$set: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{a:[{b:1}]}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + setPathToCreate("b"); + setPathTaken("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::PathNotViable, "Cannot create field 'b' in element {a: [ { b: 1 } ]}"); } -TEST(SetNodeTest, ApplyInPlaceDottedPath) { +TEST_F(SetNodeTest, ApplyInPlaceDottedPath) { auto update = fromjson("{$set: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{a: {b: 1}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyChangeTypeDottedPath) { +TEST_F(SetNodeTest, ApplyChangeTypeDottedPath) { auto update = fromjson("{$set: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{a: {b: 'str'}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyChangePath) { +TEST_F(SetNodeTest, ApplyChangePath) { auto update = fromjson("{$set: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{a: {b: {c: 1}}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyExtendPath) { +TEST_F(SetNodeTest, ApplyExtendPath) { auto update = fromjson("{$set: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{a: {c: 1}}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("b"); + setPathTaken("a"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {c: 1, b: 2}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyNewDottedPath) { +TEST_F(SetNodeTest, ApplyNewDottedPath) { auto update = fromjson("{$set: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{c: 1}")); - FieldRef pathToCreate("a.b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a.b"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{c: 1, a: {b: 2}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyEmptyDoc) { +TEST_F(SetNodeTest, ApplyEmptyDoc) { auto update = fromjson("{$set: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a.b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a.b"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyFieldWithDot) { +TEST_F(SetNodeTest, ApplyFieldWithDot) { auto update = fromjson("{$set: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{'a.b':4}")); - FieldRef pathToCreate("a.b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a.b"); + addIndexedPath("a.b"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{'a.b':4, a: {b: 2}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyNoOpArrayIndex) { +TEST_F(SetNodeTest, ApplyNoOpArrayIndex) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.2.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.2.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["2"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a.2.b"); + addIndexedPath("a.2.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["2"]["b"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, TypeChangeInArrayIsNotANoOp) { +TEST_F(SetNodeTest, TypeChangeInArrayIsNotANoOp) { auto update = fromjson("{$set: {'a.2.b': NumberInt(2)}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 2.0}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.2.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.2.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["2"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.2.b"); + addIndexedPath("a.2.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["2"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: NumberInt(2)}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyNonViablePath) { +TEST_F(SetNodeTest, ApplyNonViablePath) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: 0}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + setPathToCreate("2.b"); + setPathTaken("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::PathNotViable, "Cannot create field '2' in element {a: 0}"); } -TEST(SetNodeTest, ApplyInPlaceArrayIndex) { +TEST_F(SetNodeTest, ApplyInPlaceArrayIndex) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 1}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.2.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.2.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["2"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.2.b"); + addIndexedPath("a.2.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["2"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyNormalArray) { +TEST_F(SetNodeTest, ApplyNormalArray) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: [{b: 0},{b: 1}]}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.2.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("2.b"); + setPathTaken("a"); + addIndexedPath("a.2.b"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyPaddingArray) { +TEST_F(SetNodeTest, ApplyPaddingArray) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: [{b: 0}]}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.2.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("2.b"); + setPathTaken("a"); + addIndexedPath("a.2.b"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},null,{b: 2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyNumericObject) { +TEST_F(SetNodeTest, ApplyNumericObject) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: {b: 0}}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.2.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("2.b"); + setPathTaken("a"); + addIndexedPath("a.2.b"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 0, '2': {b: 2}}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyNumericField) { +TEST_F(SetNodeTest, ApplyNumericField) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: {'2': {b: 1}}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.2.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.2.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["2"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.2.b"); + addIndexedPath("a.2.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["2"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyExtendNumericField) { +TEST_F(SetNodeTest, ApplyExtendNumericField) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: {'2': {c: 1}}}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a.2"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.2.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["2"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("b"); + setPathTaken("a.2"); + addIndexedPath("a.2.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["2"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {'2': {c: 1, b: 2}}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyEmptyObject) { +TEST_F(SetNodeTest, ApplyEmptyObject) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: {}}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.2.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("2.b"); + setPathTaken("a"); + addIndexedPath("a.2.b"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyEmptyArray) { +TEST_F(SetNodeTest, ApplyEmptyArray) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: []}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.2.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("2.b"); + setPathTaken("a"); + addIndexedPath("a.2.b"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [null, null, {b: 2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyLogDottedPath) { +TEST_F(SetNodeTest, ApplyLogDottedPath) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: [{b:0}, {b:1}]}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); + setPathToCreate("2.b"); + setPathTaken("a"); + node.apply(getApplyParams(doc.root()["a"])); ASSERT_EQUALS(fromjson("{a: [{b:0}, {b:1}, {b:2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), getLogDoc()); } -TEST(SetNodeTest, LogEmptyArray) { +TEST_F(SetNodeTest, LogEmptyArray) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: []}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); + setPathToCreate("2.b"); + setPathTaken("a"); + node.apply(getApplyParams(doc.root()["a"])); ASSERT_EQUALS(fromjson("{a: [null, null, {b:2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), getLogDoc()); } -TEST(SetNodeTest, LogEmptyObject) { +TEST_F(SetNodeTest, LogEmptyObject) { auto update = fromjson("{$set: {'a.2.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.2.b"], collator)); Document doc(fromjson("{a: {}}")); - FieldRef pathToCreate("2.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); + setPathToCreate("2.b"); + setPathTaken("a"); + node.apply(getApplyParams(doc.root()["a"])); ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), getLogDoc()); } -TEST(SetNodeTest, ApplyNoOpComplex) { +TEST_F(SetNodeTest, ApplyNoOpComplex) { auto update = fromjson("{$set: {'a.1.b': {c: 1, d: 1}}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.1.b"], collator)); Document doc(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, d: 1}}]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.1.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.1.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["1"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a.1.b"); + addIndexedPath("a.1.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["1"]["b"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, d: 1}}]}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplySameStructure) { +TEST_F(SetNodeTest, ApplySameStructure) { auto update = fromjson("{$set: {'a.1.b': {c: 1, d: 1}}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.1.b"], collator)); Document doc(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, xxx: 1}}]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.1.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.1.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["1"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.1.b"); + addIndexedPath("a.1.b"); + auto result = node.apply(getApplyParams(doc.root()["a"]["1"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, d: 1}}]}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, NonViablePathWithoutRepl) { +TEST_F(SetNodeTest, NonViablePathWithoutRepl) { auto update = fromjson("{$set: {'a.1.b': 1}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.1.b"], collator)); Document doc(fromjson("{a: 1}")); - FieldRef pathToCreate("1.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + setPathToCreate("1.b"); + setPathTaken("a"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::PathNotViable, "Cannot create field '1' in element {a: 1}"); } -TEST(SetNodeTest, SingleFieldFromReplication) { +TEST_F(SetNodeTest, SingleFieldFromReplication) { auto update = fromjson("{$set: {'a.1.b': 1}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.1.b"], collator)); Document doc(fromjson("{_id:1, a: 1}")); - FieldRef pathToCreate("1.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.1.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("1.b"); + setPathTaken("a"); + addIndexedPath("a.1.b"); + setFromReplication(true); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id:1, a: 1}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, SingleFieldNoIdFromReplication) { +TEST_F(SetNodeTest, SingleFieldNoIdFromReplication) { auto update = fromjson("{$set: {'a.1.b': 1}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.1.b"], collator)); Document doc(fromjson("{a: 1}")); - FieldRef pathToCreate("1.b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.1.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("1.b"); + setPathTaken("a"); + addIndexedPath("a.1.b"); + setFromReplication(true); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, NestedFieldFromReplication) { +TEST_F(SetNodeTest, NestedFieldFromReplication) { auto update = fromjson("{$set: {'a.a.1.b': 1}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.a.1.b"], collator)); Document doc(fromjson("{_id:1, a: {a: 1}}")); - FieldRef pathToCreate("1.b"); - FieldRef pathTaken("a.a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.a.1.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("1.b"); + setPathTaken("a.a"); + addIndexedPath("a.a.1.b"); + setFromReplication(true); + auto result = node.apply(getApplyParams(doc.root()["a"]["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id:1, a: {a: 1}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, DoubleNestedFieldFromReplication) { +TEST_F(SetNodeTest, DoubleNestedFieldFromReplication) { auto update = fromjson("{$set: {'a.b.c.d': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b.c.d"], collator)); Document doc(fromjson("{_id:1, a: {b: {c: 1}}}")); - FieldRef pathToCreate("d"); - FieldRef pathTaken("a.b.c"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.b.c.d"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"]["c"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("d"); + setPathTaken("a.b.c"); + addIndexedPath("a.b.c.d"); + setFromReplication(true); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id:1, a: {b: {c: 1}}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, NestedFieldNoIdFromReplication) { +TEST_F(SetNodeTest, NestedFieldNoIdFromReplication) { auto update = fromjson("{$set: {'a.a.1.b': 1}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.a.1.b"], collator)); Document doc(fromjson("{a: {a: 1}}")); - FieldRef pathToCreate("1.b"); - FieldRef pathTaken("a.a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.a.1.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("1.b"); + setPathTaken("a.a"); + addIndexedPath("a.a.1.b"); + setFromReplication(true); + auto result = node.apply(getApplyParams(doc.root()["a"]["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {a: 1}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ReplayArrayFieldNotAppendedIntermediateFromReplication) { +TEST_F(SetNodeTest, ReplayArrayFieldNotAppendedIntermediateFromReplication) { auto update = fromjson("{$set: {'a.0.b': [0,2]}}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.0.b"], collator)); Document doc(fromjson("{_id: 0, a: [1, {b: [1]}]}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a.0"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.0.b"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["0"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("b"); + setPathTaken("a.0"); + addIndexedPath("a.1.b"); + setFromReplication(true); + auto result = node.apply(getApplyParams(doc.root()["a"]["0"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 0, a: [1, {b: [1]}]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, Set6) { +TEST_F(SetNodeTest, Set6) { auto update = fromjson("{$set: {'r.a': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["r.a"], collator)); Document doc(fromjson("{_id: 1, r: {a:1, b:2}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("r.a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("r.a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["r"]["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("r.a"); + addIndexedPath("r.a"); + auto result = node.apply(getApplyParams(doc.root()["r"]["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 1, r: {a:2, b:2}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {'r.a': 2}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {'r.a': 2}}"), getLogDoc()); } -TEST(SetNodeTest, Set6FromRepl) { +TEST_F(SetNodeTest, Set6FromRepl) { auto update = fromjson("{$set: { 'r.a': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["r.a"], collator)); Document doc(fromjson("{_id: 1, r: {a:1, b:2}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("r.a"); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("r.a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["r"]["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("r.a"); + addIndexedPath("r.a"); + setFromReplication(true); + auto result = node.apply(getApplyParams(doc.root()["r"]["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 1, r: {a:2, b:2} }"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {'r.a': 2}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {'r.a': 2}}"), getLogDoc()); } -TEST(SetNodeTest, ApplySetModToEphemeralDocument) { +TEST_F(SetNodeTest, ApplySetModToEphemeralDocument) { // The following mod when applied to a document constructed node by node exposed a // latent debug only defect in mutable BSON, so this is more a test of mutable than // $set. @@ -1940,702 +955,325 @@ TEST(SetNodeTest, ApplySetModToEphemeralDocument) { Element a = doc.makeElementInt("a", 100); x.pushBack(a).transitional_ignore(); - FieldRef pathToCreate(""); - FieldRef pathTaken("x"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("x"); - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["x"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("x"); + addIndexedPath("x"); + auto result = node.apply(getApplyParams(doc.root()["x"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{ x : { a : 100, b : 2 } }"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(SetNodeTest, ApplyCannotCreateDollarPrefixedFieldInsideSetElement) { +TEST_F(SetNodeTest, ApplyCannotCreateDollarPrefixedFieldInsideSetElement) { auto update = fromjson("{$set: {a: {$bad: 1}}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathTaken("a"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::DollarPrefixedFieldName, "The dollar ($) prefixed field '$bad' in 'a.$bad' is not valid for storage."); } -TEST(SetNodeTest, ApplyCannotCreateDollarPrefixedFieldAtStartOfPath) { +TEST_F(SetNodeTest, ApplyCannotCreateDollarPrefixedFieldAtStartOfPath) { auto update = fromjson("{$set: {'$bad.a': 1}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["$bad.a"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("$bad.a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathToCreate("$bad.a"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::DollarPrefixedFieldName, "The dollar ($) prefixed field '$bad' in '$bad' is not valid for storage."); } -TEST(SetNodeTest, ApplyCannotCreateDollarPrefixedFieldInMiddleOfPath) { +TEST_F(SetNodeTest, ApplyCannotCreateDollarPrefixedFieldInMiddleOfPath) { auto update = fromjson("{$set: {'a.$bad.b': 1}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.$bad.b"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a.$bad.b"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathToCreate("a.$bad.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::DollarPrefixedFieldName, "The dollar ($) prefixed field '$bad' in 'a.$bad' is not valid for storage."); } -TEST(SetNodeTest, ApplyCannotCreateDollarPrefixedFieldAtEndOfPath) { +TEST_F(SetNodeTest, ApplyCannotCreateDollarPrefixedFieldAtEndOfPath) { auto update = fromjson("{$set: {'a.$bad': 1}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.$bad"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a.$bad"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathToCreate("a.$bad"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root())), UserException, ErrorCodes::DollarPrefixedFieldName, "The dollar ($) prefixed field '$bad' in 'a.$bad' is not valid for storage."); } -TEST(SetNodeTest, ApplyCanCreateDollarPrefixedFieldNameWhenValidateForStorageIsFalse) { +TEST_F(SetNodeTest, ApplyCanCreateDollarPrefixedFieldNameWhenValidateForStorageIsFalse) { auto update = fromjson("{$set: {$bad: 1}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["$bad"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("$bad"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = false; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("$bad"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("$bad"); + addIndexedPath("$bad"); + setValidateForStorage(false); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{$bad: 1}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {$bad: 1}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {$bad: 1}}"), getLogDoc()); } -TEST(SetNodeTest, ApplyCannotOverwriteImmutablePath) { +TEST_F(SetNodeTest, ApplyCannotOverwriteImmutablePath) { auto update = fromjson("{$set: {'a.b': 1}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{a: {b: 2}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathTaken("a.b"); + addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root()["a"]["b"])), UserException, ErrorCodes::ImmutableField, "Updating the path 'a.b' to b: 1 would modify the immutable field 'a.b'"); } -TEST(SetNodeTest, ApplyCanPerformNoopOnImmutablePath) { +TEST_F(SetNodeTest, ApplyCanPerformNoopOnImmutablePath) { auto update = fromjson("{$set: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{a: {b: 2}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a.b"); + addImmutablePath("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 0u); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 0u); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(SetNodeTest, ApplyCannotOverwritePrefixToRemoveImmutablePath) { +TEST_F(SetNodeTest, ApplyCannotOverwritePrefixToRemoveImmutablePath) { auto update = fromjson("{$set: {a: 1}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: {b: 2}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathTaken("a"); + addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::ImmutableField, "After applying the update, the immutable field 'a.b' was found to have been removed."); } -TEST(SetNodeTest, ApplyCannotOverwritePrefixToModifyImmutablePath) { +TEST_F(SetNodeTest, ApplyCannotOverwritePrefixToModifyImmutablePath) { auto update = fromjson("{$set: {a: {b: 1}}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: {b: 2}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + setPathTaken("a"); + addImmutablePath("a.b"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::ImmutableField, "After applying the update, the immutable field 'a.b' was found to " "have been altered to b: 1"); } -TEST(SetNodeTest, ApplyCanPerformNoopOnPrefixOfImmutablePath) { +TEST_F(SetNodeTest, ApplyCanPerformNoopOnPrefixOfImmutablePath) { auto update = fromjson("{$set: {a: {b: 2}}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: {b: 2}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addImmutablePath("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 0u); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 0u); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(SetNodeTest, ApplyCanOverwritePrefixToCreateImmutablePath) { +TEST_F(SetNodeTest, ApplyCanOverwritePrefixToCreateImmutablePath) { auto update = fromjson("{$set: {a: {b: 2}}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: 1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addImmutablePath("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {a: {b: 2}}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {a: {b: 2}}}"), getLogDoc()); } -TEST(SetNodeTest, ApplyCanOverwritePrefixOfImmutablePathIfNoopOnImmutablePath) { +TEST_F(SetNodeTest, ApplyCanOverwritePrefixOfImmutablePathIfNoopOnImmutablePath) { auto update = fromjson("{$set: {a: {b: 2, c: 3}}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{a: {b: 2}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addImmutablePath("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2, c: 3}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {a: {b: 2, c: 3}}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {a: {b: 2, c: 3}}}"), getLogDoc()); } -TEST(SetNodeTest, ApplyCannotOverwriteSuffixOfImmutablePath) { +TEST_F(SetNodeTest, ApplyCannotOverwriteSuffixOfImmutablePath) { auto update = fromjson("{$set: {'a.b.c': 1}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b.c"], collator)); Document doc(fromjson("{a: {b: {c: 2}}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b.c"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathTaken("a.b.c"); + addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root()["a"]["b"]["c"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root()["a"]["b"]["c"])), UserException, ErrorCodes::ImmutableField, "Updating the path 'a.b.c' to c: 1 would modify the immutable field 'a.b'"); } -TEST(SetNodeTest, ApplyCanPerformNoopOnSuffixOfImmutablePath) { +TEST_F(SetNodeTest, ApplyCanPerformNoopOnSuffixOfImmutablePath) { auto update = fromjson("{$set: {'a.b.c': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b.c"], collator)); Document doc(fromjson("{a: {b: {c: 2}}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b.c"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"]["c"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a.b.c"); + addImmutablePath("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: {c: 2}}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 0u); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 0u); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(SetNodeTest, ApplyCannotCreateFieldAtEndOfImmutablePath) { +TEST_F(SetNodeTest, ApplyCannotCreateFieldAtEndOfImmutablePath) { auto update = fromjson("{$set: {'a.b.c': 1}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b.c"], collator)); Document doc(fromjson("{a: {b: {}}}")); - FieldRef pathToCreate("c"); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathToCreate("c"); + setPathTaken("a.b"); + addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root()["a"]["b"])), UserException, ErrorCodes::ImmutableField, "Updating the path 'a.b' to b: { c: 1 } would modify the immutable field 'a.b'"); } -TEST(SetNodeTest, ApplyCannotCreateFieldBeyondEndOfImmutablePath) { +TEST_F(SetNodeTest, ApplyCannotCreateFieldBeyondEndOfImmutablePath) { auto update = fromjson("{$set: {'a.b.c': 1}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b.c"], collator)); Document doc(fromjson("{a: {b: {}}}")); - FieldRef pathToCreate("c"); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathToCreate("c"); + setPathTaken("a.b"); + addImmutablePath("a"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root()["a"]["b"])), UserException, ErrorCodes::ImmutableField, "Updating the path 'a.b' to b: { c: 1 } would modify the immutable field 'a'"); } -TEST(SetNodeTest, ApplyCanCreateImmutablePath) { +TEST_F(SetNodeTest, ApplyCanCreateImmutablePath) { auto update = fromjson("{$set: {'a.b': 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a.b"], collator)); Document doc(fromjson("{a: {}}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("b"); + setPathTaken("a"); + addImmutablePath("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {'a.b': 2}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {'a.b': 2}}"), getLogDoc()); } -TEST(SetNodeTest, ApplyCanCreatePrefixOfImmutablePath) { +TEST_F(SetNodeTest, ApplyCanCreatePrefixOfImmutablePath) { auto update = fromjson("{$set: {a: 2}}"); const CollatorInterface* collator = nullptr; SetNode node; ASSERT_OK(node.init(update["$set"]["a"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathToCreate("a"); + addImmutablePath("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(countChildren(logDoc.root()), 1u); - ASSERT_EQUALS(fromjson("{$set: {a: 2}}"), logDoc); + ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); + ASSERT_EQUALS(fromjson("{$set: {a: 2}}"), getLogDoc()); } } // namespace +} // namespace mongo diff --git a/src/mongo/db/update/unset_node.cpp b/src/mongo/db/update/unset_node.cpp index c7266ecb2d0..243e3918027 100644 --- a/src/mongo/db/update/unset_node.cpp +++ b/src/mongo/db/update/unset_node.cpp @@ -40,47 +40,36 @@ Status UnsetNode::init(BSONElement modExpr, const CollatorInterface* collator) { return Status::OK(); } -void UnsetNode::apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const { - *indexesAffected = false; - *noop = false; - - if (!pathToCreate->empty()) { +UpdateNode::ApplyResult UnsetNode::apply(ApplyParams applyParams) const { + if (!applyParams.pathToCreate->empty()) { // A non-empty "pathToCreate" implies that our search did not find the field that we wanted // to delete. We employ a simple and efficient strategy for deleting fields that don't yet // exist. - *noop = true; - return; + return ApplyResult::noopResult(); } + ApplyResult applyResult; + // Determine if indexes are affected. - if (indexData && indexData->mightBeIndexed(pathTaken->dottedField())) { - *indexesAffected = true; + if (!applyParams.indexData || + !applyParams.indexData->mightBeIndexed(applyParams.pathTaken->dottedField())) { + applyResult.indexesAffected = false; } - auto parent = element.parent(); - auto leftSibling = element.leftSibling(); - auto rightSibling = element.rightSibling(); + auto parent = applyParams.element.parent(); + auto leftSibling = applyParams.element.leftSibling(); + auto rightSibling = applyParams.element.rightSibling(); invariant(parent.ok()); if (!parent.isType(BSONType::Array)) { - invariantOK(element.remove()); + invariantOK(applyParams.element.remove()); } else { // Special case: An $unset on an array element sets it to null instead of removing it from // the array. - invariantOK(element.setValueNull()); + invariantOK(applyParams.element.setValueNull()); } - if (validateForStorage) { + if (applyParams.validateForStorage) { // Validate the left and right sibling, in case this element was part of a DBRef. if (leftSibling.ok()) { @@ -97,21 +86,24 @@ void UnsetNode::apply(mutablebson::Element element, } // Ensure we are not changing any immutable paths. - for (auto immutablePath = immutablePaths.begin(); immutablePath != immutablePaths.end(); + for (auto immutablePath = applyParams.immutablePaths.begin(); + immutablePath != applyParams.immutablePaths.end(); ++immutablePath) { uassert(ErrorCodes::ImmutableField, - str::stream() << "Unsetting the path '" << pathTaken->dottedField() + str::stream() << "Unsetting the path '" << applyParams.pathTaken->dottedField() << "' would modify the immutable field '" << (*immutablePath)->dottedField() << "'", - pathTaken->commonPrefixSize(**immutablePath) < - std::min(pathTaken->numParts(), (*immutablePath)->numParts())); + applyParams.pathTaken->commonPrefixSize(**immutablePath) < + std::min(applyParams.pathTaken->numParts(), (*immutablePath)->numParts())); } // Log the unset. - if (logBuilder) { - uassertStatusOK(logBuilder->addToUnsets(pathTaken->dottedField())); + if (applyParams.logBuilder) { + uassertStatusOK(applyParams.logBuilder->addToUnsets(applyParams.pathTaken->dottedField())); } + + return applyResult; } } // namespace mongo diff --git a/src/mongo/db/update/unset_node.h b/src/mongo/db/update/unset_node.h index 973eb300bef..a8d19f9ade8 100644 --- a/src/mongo/db/update/unset_node.h +++ b/src/mongo/db/update/unset_node.h @@ -46,17 +46,7 @@ public: void setCollator(const CollatorInterface* collator) final {} - void apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const final; + ApplyResult apply(ApplyParams applyParams) const final; }; } // namespace mongo diff --git a/src/mongo/db/update/unset_node_test.cpp b/src/mongo/db/update/unset_node_test.cpp index 8c64cf5201c..ad8f373e943 100644 --- a/src/mongo/db/update/unset_node_test.cpp +++ b/src/mongo/db/update/unset_node_test.cpp @@ -33,12 +33,14 @@ #include "mongo/bson/mutable/algorithm.h" #include "mongo/bson/mutable/mutable_bson_test_utils.h" #include "mongo/db/json.h" +#include "mongo/db/update/update_node_test_fixture.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" +namespace mongo { namespace { -using namespace mongo; +using UnsetNodeTest = UpdateNodeTest; using mongo::mutablebson::Document; using mongo::mutablebson::Element; using mongo::mutablebson::countChildren; @@ -50,34 +52,14 @@ DEATH_TEST(UnsetNodeTest, InitFailsForEmptyElement, "Invariant failure modExpr.o node.init(update["$unset"].embeddedObject().firstElement(), collator).transitional_ignore(); } -DEATH_TEST(UnsetNodeTest, ApplyToRootFails, "Invariant failure parent.ok()") { +DEATH_TEST_F(UnsetNodeTest, ApplyToRootFails, "Invariant failure parent.ok()") { auto update = fromjson("{$unset: {}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); + node.apply(getApplyParams(doc.root())); } TEST(UnsetNodeTest, InitSucceedsForNonemptyElement) { @@ -88,773 +70,361 @@ TEST(UnsetNodeTest, InitSucceedsForNonemptyElement) { } /* This is a no-op because we are unsetting a field that does not exit. */ -TEST(UnsetNodeTest, UnsetNoOp) { +TEST_F(UnsetNodeTest, UnsetNoOp) { auto update = fromjson("{$unset: {a: 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a"], collator)); Document doc(fromjson("{b: 5}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 5}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(UnsetNodeTest, UnsetNoOpDottedPath) { +TEST_F(UnsetNodeTest, UnsetNoOpDottedPath) { auto update = fromjson("{$unset: {'a.b': 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.b"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("b"); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 5}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(UnsetNodeTest, UnsetNoOpThroughArray) { +TEST_F(UnsetNodeTest, UnsetNoOpThroughArray) { auto update = fromjson("{$unset: {'a.b': 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.b"], collator)); Document doc(fromjson("{a:[{b:1}]}")); - FieldRef pathToCreate("b"); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("b"); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a:[{b:1}]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(UnsetNodeTest, UnsetNoOpEmptyDoc) { +TEST_F(UnsetNodeTest, UnsetNoOpEmptyDoc) { auto update = fromjson("{$unset: {a: 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a"], collator)); Document doc(fromjson("{}")); - FieldRef pathToCreate("a"); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(UnsetNodeTest, UnsetTopLevelPath) { +TEST_F(UnsetNodeTest, UnsetTopLevelPath) { auto update = fromjson("{$unset: {a: 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$unset: {a: true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$unset: {a: true}}"), getLogDoc()); } -TEST(UnsetNodeTest, UnsetNestedPath) { +TEST_F(UnsetNodeTest, UnsetNestedPath) { auto update = fromjson("{$unset: {'a.b.c': 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.b.c"], collator)); Document doc(fromjson("{a: {b: {c: 6}}}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b.c"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"]["c"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b.c"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: {}}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$unset: {'a.b.c': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$unset: {'a.b.c': true}}"), getLogDoc()); } -TEST(UnsetNodeTest, UnsetObject) { +TEST_F(UnsetNodeTest, UnsetObject) { auto update = fromjson("{$unset: {'a.b': 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.b"], collator)); Document doc(fromjson("{a: {b: {c: 6}}}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$unset: {'a.b': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$unset: {'a.b': true}}"), getLogDoc()); } -TEST(UnsetNodeTest, UnsetArrayElement) { +TEST_F(UnsetNodeTest, UnsetArrayElement) { auto update = fromjson("{$unset: {'a.0': 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.0"], collator)); Document doc(fromjson("{a:[1], b:1}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.0"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["0"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.0"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["0"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a:[null], b:1}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$unset: {'a.0': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$unset: {'a.0': true}}"), getLogDoc()); } -TEST(UnsetNodeTest, UnsetPositional) { +TEST_F(UnsetNodeTest, UnsetPositional) { auto update = fromjson("{$unset: {'a.$': 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.$"], collator)); Document doc(fromjson("{a: [0, 1, 2]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.1"); - StringData matchedField = "1"; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["1"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.1"); + setMatchedField("1"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["1"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, null, 2]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$unset: {'a.1': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$unset: {'a.1': true}}"), getLogDoc()); } -TEST(UnsetNodeTest, UnsetEntireArray) { +TEST_F(UnsetNodeTest, UnsetEntireArray) { auto update = fromjson("{$unset: {'a': 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a"], collator)); Document doc(fromjson("{a: [0, 1, 2]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$unset: {a: true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$unset: {a: true}}"), getLogDoc()); } -TEST(UnsetNodeTest, UnsetFromObjectInArray) { +TEST_F(UnsetNodeTest, UnsetFromObjectInArray) { auto update = fromjson("{$unset: {'a.0.b': 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.0.b"], collator)); Document doc(fromjson("{a: [{b: 1}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.0.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["0"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.0.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["0"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a:[{}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$unset: {'a.0.b': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$unset: {'a.0.b': true}}"), getLogDoc()); } -TEST(UnsetNodeTest, CanUnsetInvalidField) { +TEST_F(UnsetNodeTest, CanUnsetInvalidField) { auto update = fromjson("{$unset: {'a.$.$b': true}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.$.$b"], collator)); Document doc(fromjson("{b: 1, a: [{$b: 1}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.0.$b"); - StringData matchedField = "0"; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["0"]["$b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.0.$b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["0"]["$b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 1, a: [{}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$unset: {'a.0.$b': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$unset: {'a.0.$b': true}}"), getLogDoc()); } -TEST(UnsetNodeTest, ApplyNoIndexDataNoLogBuilder) { +TEST_F(UnsetNodeTest, ApplyNoIndexDataNoLogBuilder) { auto update = fromjson("{$unset: {a: 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + setLogBuilderToNull(); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); } -TEST(UnsetNodeTest, ApplyDoesNotAffectIndexes) { +TEST_F(UnsetNodeTest, ApplyDoesNotAffectIndexes) { auto update = fromjson("{$unset: {a: 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a"], collator)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("b"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + setPathTaken("a"); + addIndexedPath("b"); + auto result = node.apply(getApplyParams(doc.root()["a"])); + ASSERT_FALSE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$unset: {a: true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$unset: {a: true}}"), getLogDoc()); } -TEST(UnsetNodeTest, ApplyFieldWithDot) { +TEST_F(UnsetNodeTest, ApplyFieldWithDot) { auto update = fromjson("{$unset: {'a.b': 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.b"], collator)); Document doc(fromjson("{'a.b':4, a: {b: 2}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{'a.b':4, a: {}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$unset: {'a.b': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$unset: {'a.b': true}}"), getLogDoc()); } -TEST(UnsetNodeTest, ApplyCannotRemoveRequiredPartOfDBRef) { +TEST_F(UnsetNodeTest, ApplyCannotRemoveRequiredPartOfDBRef) { auto update = fromjson("{$unset: {'a.$id': true}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.$id"], collator)); Document doc(fromjson("{a: {$ref: 'c', $id: 0}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.$id"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"]["$id"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + setPathTaken("a.$id"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"]["$id"])), UserException, ErrorCodes::InvalidDBRef, "The DBRef $ref field must be followed by a $id field"); } -TEST(UnsetNodeTest, ApplyCanRemoveRequiredPartOfDBRefIfValidateForStorageIsFalse) { +TEST_F(UnsetNodeTest, ApplyCanRemoveRequiredPartOfDBRefIfValidateForStorageIsFalse) { auto update = fromjson("{$unset: {'a.$id': true}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.$id"], collator)); Document doc(fromjson("{a: {$ref: 'c', $id: 0}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.$id"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = false; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["$id"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_TRUE(indexesAffected); + setPathTaken("a.$id"); + addIndexedPath("a"); + setValidateForStorage(false); + auto result = node.apply(getApplyParams(doc.root()["a"]["$id"])); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); auto updated = BSON("a" << BSON("$ref" << "c")); ASSERT_EQUALS(updated, doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$unset: {'a.$id': true}}"), logDoc); + ASSERT_EQUALS(fromjson("{$unset: {'a.$id': true}}"), getLogDoc()); } -TEST(UnsetNodeTest, ApplyCannotRemoveImmutablePath) { +TEST_F(UnsetNodeTest, ApplyCannotRemoveImmutablePath) { auto update = fromjson("{$unset: {'a.b': true}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.b"], collator)); Document doc(fromjson("{a: {b: 1}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + setPathTaken("a.b"); + addImmutablePath("a.b"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"]["b"])), UserException, ErrorCodes::ImmutableField, "Unsetting the path 'a.b' would modify the immutable field 'a.b'"); } -TEST(UnsetNodeTest, ApplyCannotRemovePrefixOfImmutablePath) { +TEST_F(UnsetNodeTest, ApplyCannotRemovePrefixOfImmutablePath) { auto update = fromjson("{$unset: {a: true}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a"], collator)); Document doc(fromjson("{a: {b: 1}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - ASSERT_THROWS_CODE_AND_WHAT(node.apply(doc.root()["a"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + setPathTaken("a"); + addImmutablePath("a.b"); + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), UserException, ErrorCodes::ImmutableField, "Unsetting the path 'a' would modify the immutable field 'a.b'"); } -TEST(UnsetNodeTest, ApplyCannotRemoveSuffixOfImmutablePath) { +TEST_F(UnsetNodeTest, ApplyCannotRemoveSuffixOfImmutablePath) { auto update = fromjson("{$unset: {'a.b.c': true}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.b.c"], collator)); Document doc(fromjson("{a: {b: {c: 1}}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken("a.b.c"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; + setPathTaken("a.b.c"); + addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(doc.root()["a"]["b"]["c"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop), + node.apply(getApplyParams(doc.root()["a"]["b"]["c"])), UserException, ErrorCodes::ImmutableField, "Unsetting the path 'a.b.c' would modify the immutable field 'a.b'"); } -TEST(UnsetNodeTest, ApplyCanRemoveImmutablePathIfNoop) { +TEST_F(UnsetNodeTest, ApplyCanRemoveImmutablePathIfNoop) { auto update = fromjson("{$unset: {'a.b.c': true}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.b.c"], collator)); Document doc(fromjson("{a: {b: 1}}")); - FieldRef pathToCreate("c"); - FieldRef pathTaken("a.b"); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = false; - FieldRefSet immutablePaths; - FieldRef path("a.b"); - immutablePaths.insert(&path); - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - node.apply(doc.root()["a"]["b"], - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(noop); - ASSERT_FALSE(indexesAffected); + setPathToCreate("c"); + setPathTaken("a.b"); + addImmutablePath("a.b"); + addIndexedPath("a"); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 1}}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } } // namespace +} // namespace mongo diff --git a/src/mongo/db/update/update_array_node.cpp b/src/mongo/db/update/update_array_node.cpp index e82e5d8aef3..02f904d77b2 100644 --- a/src/mongo/db/update/update_array_node.cpp +++ b/src/mongo/db/update/update_array_node.cpp @@ -46,33 +46,20 @@ std::unique_ptr<UpdateNode> UpdateArrayNode::createUpdateNodeByMerging( return std::move(mergedNode); } -void UpdateArrayNode::apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const { - *indexesAffected = false; - *noop = true; - - if (!pathToCreate->empty()) { - for (size_t i = 0; i < pathToCreate->numParts(); ++i) { - pathTaken->appendPart(pathToCreate->getPart(i)); +UpdateNode::ApplyResult UpdateArrayNode::apply(ApplyParams applyParams) const { + if (!applyParams.pathToCreate->empty()) { + for (size_t i = 0; i < applyParams.pathToCreate->numParts(); ++i) { + applyParams.pathTaken->appendPart(applyParams.pathToCreate->getPart(i)); } uasserted(ErrorCodes::BadValue, - str::stream() << "The path '" << pathTaken->dottedField() + str::stream() << "The path '" << applyParams.pathTaken->dottedField() << "' must exist in the document in order to apply array updates."); } uassert(ErrorCodes::BadValue, str::stream() << "Cannot apply array updates to non-array element " - << element.toString(), - element.getType() == BSONType::Array); + << applyParams.element.toString(), + applyParams.element.getType() == BSONType::Array); // Construct a map from the array index to the set of updates that should be applied to the // array element at that index. We do not apply the updates yet because we need to know how many @@ -80,7 +67,7 @@ void UpdateArrayNode::apply(mutablebson::Element element, // UpdateNode children. std::map<size_t, std::vector<UpdateNode*>> matchingElements; size_t i = 0; - for (auto childElement = element.leftChild(); childElement.ok(); + for (auto childElement = applyParams.element.leftChild(); childElement.ok(); childElement = childElement.rightSibling()) { // 'childElement' will always be serialized because no updates have been performed on the @@ -118,8 +105,9 @@ void UpdateArrayNode::apply(mutablebson::Element element, size_t nModified = 0; // Update array elements. + auto applyResult = ApplyResult::noopResult(); i = 0; - for (auto childElement = element.leftChild(); childElement.ok(); + for (auto childElement = applyParams.element.leftChild(); childElement.ok(); childElement = childElement.rightSibling()) { auto updates = matchingElements.find(i); if (updates != matchingElements.end()) { @@ -127,7 +115,7 @@ void UpdateArrayNode::apply(mutablebson::Element element, // Merge all of the updates for this array element. invariant(updates->second.size() > 0); auto mergedChild = updates->second[0]; - FieldRefTempAppend tempAppend(*pathTaken, childElement.getFieldName()); + FieldRefTempAppend tempAppend(*(applyParams.pathTaken), childElement.getFieldName()); for (size_t j = 1; j < updates->second.size(); ++j) { // Use the cached merge result, if it is available. @@ -141,28 +129,22 @@ void UpdateArrayNode::apply(mutablebson::Element element, // result. _mergedChildrenCache[mergedChild][updates->second[j]] = UpdateNode::createUpdateNodeByMerging( - *mergedChild, *updates->second[j], pathTaken); + *mergedChild, *updates->second[j], applyParams.pathTaken.get()); mergedChild = _mergedChildrenCache[mergedChild][updates->second[j]].get(); } - bool childAffectsIndexes = false; - bool childNoop = false; - - mergedChild->apply(childElement, - pathToCreate, - pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - childrenShouldLogThemselves ? logBuilder : nullptr, - &childAffectsIndexes, - &childNoop); - - *indexesAffected = *indexesAffected || childAffectsIndexes; - *noop = *noop && childNoop; - if (!childNoop) { + auto childApplyParams = applyParams; + childApplyParams.element = childElement; + if (!childrenShouldLogThemselves) { + childApplyParams.logBuilder = nullptr; + } + + auto childApplyResult = mergedChild->apply(childApplyParams); + + applyResult.indexesAffected = + applyResult.indexesAffected || childApplyResult.indexesAffected; + applyResult.noop = applyResult.noop && childApplyResult.noop; + if (!childApplyResult.noop) { modifiedElement = childElement; ++nModified; } @@ -172,25 +154,28 @@ void UpdateArrayNode::apply(mutablebson::Element element, } // If the child updates have not been logged, log the updated array elements. - if (!childrenShouldLogThemselves && logBuilder) { + if (!childrenShouldLogThemselves && applyParams.logBuilder) { if (nModified > 1) { // Log the entire array. - auto logElement = logBuilder->getDocument().makeElementWithNewFieldName( - pathTaken->dottedField(), element); + auto logElement = applyParams.logBuilder->getDocument().makeElementWithNewFieldName( + applyParams.pathTaken->dottedField(), applyParams.element); invariant(logElement.ok()); - uassertStatusOK(logBuilder->addToSets(logElement)); + uassertStatusOK(applyParams.logBuilder->addToSets(logElement)); } else if (nModified == 1) { // Log the modified array element. invariant(modifiedElement); - FieldRefTempAppend tempAppend(*pathTaken, modifiedElement->getFieldName()); - auto logElement = logBuilder->getDocument().makeElementWithNewFieldName( - pathTaken->dottedField(), *modifiedElement); + FieldRefTempAppend tempAppend(*(applyParams.pathTaken), + modifiedElement->getFieldName()); + auto logElement = applyParams.logBuilder->getDocument().makeElementWithNewFieldName( + applyParams.pathTaken->dottedField(), *modifiedElement); invariant(logElement.ok()); - uassertStatusOK(logBuilder->addToSets(logElement)); + uassertStatusOK(applyParams.logBuilder->addToSets(logElement)); } } + + return applyResult; } UpdateNode* UpdateArrayNode::getChild(const std::string& field) const { diff --git a/src/mongo/db/update/update_array_node.h b/src/mongo/db/update/update_array_node.h index 8ccd8d028f8..ef535419b7f 100644 --- a/src/mongo/db/update/update_array_node.h +++ b/src/mongo/db/update/update_array_node.h @@ -72,17 +72,7 @@ public: } } - void apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const final; + ApplyResult apply(ApplyParams applyParams) const final; UpdateNode* getChild(const std::string& field) const final; diff --git a/src/mongo/db/update/update_array_node_test.cpp b/src/mongo/db/update/update_array_node_test.cpp index a142c04a1f0..60c5bfb4448 100644 --- a/src/mongo/db/update/update_array_node_test.cpp +++ b/src/mongo/db/update/update_array_node_test.cpp @@ -33,6 +33,7 @@ #include "mongo/bson/mutable/algorithm.h" #include "mongo/bson/mutable/mutable_bson_test_utils.h" #include "mongo/db/json.h" +#include "mongo/db/update/update_node_test_fixture.h" #include "mongo/db/update/update_object_node.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" @@ -40,10 +41,11 @@ namespace mongo { namespace { +using UpdateArrayNodeTest = UpdateNodeTest; using mongo::mutablebson::Document; using mongo::mutablebson::Element; -TEST(UpdateArrayNodeTest, ApplyCreatePathFails) { +TEST_F(UpdateArrayNodeTest, ApplyCreatePathFails) { auto update = fromjson("{$set: {'a.b.$[i]': 0}}"); auto arrayFilter = fromjson("{i: 0}"); const CollatorInterface* collator = nullptr; @@ -59,36 +61,15 @@ TEST(UpdateArrayNodeTest, ApplyCreatePathFails) { foundIdentifiers)); Document doc(fromjson("{a: {}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; + addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT( - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + root.apply(getApplyParams(doc.root())), UserException, ErrorCodes::BadValue, "The path 'a.b' must exist in the document in order to apply array updates."); } -TEST(UpdateArrayNodeTest, ApplyToNonArrayFails) { +TEST_F(UpdateArrayNodeTest, ApplyToNonArrayFails) { auto update = fromjson("{$set: {'a.$[i]': 0}}"); auto arrayFilter = fromjson("{i: 0}"); const CollatorInterface* collator = nullptr; @@ -104,35 +85,14 @@ TEST(UpdateArrayNodeTest, ApplyToNonArrayFails) { foundIdentifiers)); Document doc(fromjson("{a: {}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), UserException, ErrorCodes::BadValue, "Cannot apply array updates to non-array element a: {}"); } -TEST(UpdateArrayNodeTest, UpdateIsAppliedToAllMatchingElements) { +TEST_F(UpdateArrayNodeTest, UpdateIsAppliedToAllMatchingElements) { auto update = fromjson("{$set: {'a.$[i]': 2}}"); auto arrayFilter = fromjson("{i: 0}"); const CollatorInterface* collator = nullptr; @@ -148,39 +108,18 @@ TEST(UpdateArrayNodeTest, UpdateIsAppliedToAllMatchingElements) { foundIdentifiers)); Document doc(fromjson("{a: [0, 1, 0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [2, 1, 2]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [2, 1, 2]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [2, 1, 2]}}"), getLogDoc()); } -DEATH_TEST(UpdateArrayNodeTest, - ArrayElementsMustNotBeDeserialized, - "Invariant failure childElement.hasValue()") { +DEATH_TEST_F(UpdateArrayNodeTest, + ArrayElementsMustNotBeDeserialized, + "Invariant failure childElement.hasValue()") { auto update = fromjson("{$set: {'a.$[i].b': 0}}"); auto arrayFilter = fromjson("{'i.c': 0}"); const CollatorInterface* collator = nullptr; @@ -198,32 +137,11 @@ DEATH_TEST(UpdateArrayNodeTest, Document doc(fromjson("{a: [{c: 0}, {c: 0}, {c: 1}]}")); doc.root()["a"]["1"]["c"].setValueInt(1).transitional_ignore(); doc.root()["a"]["2"]["c"].setValueInt(0).transitional_ignore(); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); + addIndexedPath("a"); + root.apply(getApplyParams(doc.root())); } -TEST(UpdateArrayNodeTest, UpdateForEmptyIdentifierIsAppliedToAllArrayElements) { +TEST_F(UpdateArrayNodeTest, UpdateForEmptyIdentifierIsAppliedToAllArrayElements) { auto update = fromjson("{$set: {'a.$[]': 1}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -237,37 +155,16 @@ TEST(UpdateArrayNodeTest, UpdateForEmptyIdentifierIsAppliedToAllArrayElements) { foundIdentifiers)); Document doc(fromjson("{a: [0, 0, 0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [1, 1, 1]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [1, 1, 1]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [1, 1, 1]}}"), getLogDoc()); } -TEST(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElement) { +TEST_F(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElement) { auto update = fromjson("{$set: {'a.$[i].b': 1, 'a.$[j].c': 1, 'a.$[k].d': 1}}"); auto arrayFilterI = fromjson("{'i.b': 0}"); auto arrayFilterJ = fromjson("{'j.c': 0}"); @@ -299,37 +196,16 @@ TEST(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElement) { foundIdentifiers)); Document doc(fromjson("{a: [{b: 0, c: 0, d: 0}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{b: 1, c: 1, d: 1}]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.0.b': 1, 'a.0.c': 1, 'a.0.d': 1}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.0.b': 1, 'a.0.c': 1, 'a.0.d': 1}}"), getLogDoc()); } -TEST(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElementsUsingMergedChildrenCache) { +TEST_F(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElementsUsingMergedChildrenCache) { auto update = fromjson("{$set: {'a.$[i].b': 1, 'a.$[j].c': 1}}"); auto arrayFilterI = fromjson("{'i.b': 0}"); auto arrayFilterJ = fromjson("{'j.c': 0}"); @@ -353,37 +229,16 @@ TEST(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElementsUsingMergedChildren foundIdentifiers)); Document doc(fromjson("{a: [{b: 0, c: 0}, {b: 0, c: 0}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto validateForStorage = true; - FieldRefSet immutablePaths; - auto fromReplication = false; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{b: 1, c: 1}, {b: 1, c: 1}]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [{b: 1, c: 1}, {b: 1, c: 1}]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [{b: 1, c: 1}, {b: 1, c: 1}]}}"), getLogDoc()); } -TEST(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElementsWithoutMergedChildrenCache) { +TEST_F(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElementsWithoutMergedChildrenCache) { auto update = fromjson("{$set: {'a.$[i].b': 2, 'a.$[j].c': 2, 'a.$[k].d': 2}}"); auto arrayFilterI = fromjson("{'i.b': 0}"); auto arrayFilterJ = fromjson("{'j.c': 0}"); @@ -415,37 +270,16 @@ TEST(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElementsWithoutMergedChildr foundIdentifiers)); Document doc(fromjson("{a: [{b: 0, c: 0, d: 1}, {b: 1, c: 0, d: 0}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{b: 2, c: 2, d: 1}, {b: 1, c: 2, d: 2}]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [{b: 2, c: 2, d: 1}, {b: 1, c: 2, d: 2}]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [{b: 2, c: 2, d: 1}, {b: 1, c: 2, d: 2}]}}"), getLogDoc()); } -TEST(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElementWithEmptyIdentifiers) { +TEST_F(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElementWithEmptyIdentifiers) { auto update = fromjson("{$set: {'a.$[].b': 1, 'a.$[].c': 1}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -465,37 +299,16 @@ TEST(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElementWithEmptyIdentifiers foundIdentifiers)); Document doc(fromjson("{a: [{b: 0, c: 0}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{b: 1, c: 1}]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.0.b': 1, 'a.0.c': 1}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.0.b': 1, 'a.0.c': 1}}"), getLogDoc()); } -TEST(UpdateArrayNodeTest, ApplyNestedArrayUpdates) { +TEST_F(UpdateArrayNodeTest, ApplyNestedArrayUpdates) { auto update = fromjson("{$set: {'a.$[i].b.$[j].c': 1, 'a.$[k].b.$[l].d': 1}}"); auto arrayFilterI = fromjson("{'i.x': 0}"); auto arrayFilterJ = fromjson("{'j.c': 0}"); @@ -523,37 +336,16 @@ TEST(UpdateArrayNodeTest, ApplyNestedArrayUpdates) { foundIdentifiers)); Document doc(fromjson("{a: [{x: 0, b: [{c: 0, d: 0}]}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{x: 0, b: [{c: 1, d: 1}]}]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.0.b.0.c': 1, 'a.0.b.0.d': 1}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.0.b.0.c': 1, 'a.0.b.0.d': 1}}"), getLogDoc()); } -TEST(UpdateArrayNodeTest, ApplyUpdatesWithMergeConflictToArrayElementFails) { +TEST_F(UpdateArrayNodeTest, ApplyUpdatesWithMergeConflictToArrayElementFails) { auto update = fromjson("{$set: {'a.$[i]': 1, 'a.$[j]': 1}}"); auto arrayFilterI = fromjson("{'i': 0}"); auto arrayFilterJ = fromjson("{'j': 0}"); @@ -577,35 +369,14 @@ TEST(UpdateArrayNodeTest, ApplyUpdatesWithMergeConflictToArrayElementFails) { foundIdentifiers)); Document doc(fromjson("{a: [0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), UserException, ErrorCodes::ConflictingUpdateOperators, "Update created a conflict at 'a.0'"); } -TEST(UpdateArrayNodeTest, ApplyUpdatesWithEmptyIdentifiersWithMergeConflictToArrayElementFails) { +TEST_F(UpdateArrayNodeTest, ApplyUpdatesWithEmptyIdentifiersWithMergeConflictToArrayElementFails) { auto update = fromjson("{$set: {'a.$[].b.$[i]': 1, 'a.$[].b.$[j]': 1}}"); auto arrayFilterI = fromjson("{'i': 0}"); auto arrayFilterJ = fromjson("{'j': 0}"); @@ -629,35 +400,14 @@ TEST(UpdateArrayNodeTest, ApplyUpdatesWithEmptyIdentifiersWithMergeConflictToArr foundIdentifiers)); Document doc(fromjson("{a: [{b: [0]}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), UserException, ErrorCodes::ConflictingUpdateOperators, "Update created a conflict at 'a.0.b.0'"); } -TEST(UpdateArrayNodeTest, ApplyNestedArrayUpdatesWithMergeConflictFails) { +TEST_F(UpdateArrayNodeTest, ApplyNestedArrayUpdatesWithMergeConflictFails) { auto update = fromjson("{$set: {'a.$[i].b.$[j]': 1, 'a.$[k].b.$[l]': 1}}"); auto arrayFilterI = fromjson("{'i.c': 0}"); auto arrayFilterJ = fromjson("{j: 0}"); @@ -685,35 +435,14 @@ TEST(UpdateArrayNodeTest, ApplyNestedArrayUpdatesWithMergeConflictFails) { foundIdentifiers)); Document doc(fromjson("{a: [{b: [0], c: 0}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), UserException, ErrorCodes::ConflictingUpdateOperators, "Update created a conflict at 'a.0.b.0'"); } -TEST(UpdateArrayNodeTest, NoArrayElementsMatch) { +TEST_F(UpdateArrayNodeTest, NoArrayElementsMatch) { auto update = fromjson("{$set: {'a.$[i]': 1}}"); auto arrayFilter = fromjson("{'i': 0}"); const CollatorInterface* collator = nullptr; @@ -729,37 +458,16 @@ TEST(UpdateArrayNodeTest, NoArrayElementsMatch) { foundIdentifiers)); Document doc(fromjson("{a: [2, 2, 2]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(indexesAffected); - ASSERT_TRUE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.indexesAffected); + ASSERT_TRUE(result.noop); ASSERT_EQUALS(fromjson("{a: [2, 2, 2]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(UpdateArrayNodeTest, UpdatesToAllArrayElementsAreNoops) { +TEST_F(UpdateArrayNodeTest, UpdatesToAllArrayElementsAreNoops) { auto update = fromjson("{$set: {'a.$[i]': 1}}"); auto arrayFilter = fromjson("{'i': 0}"); const CollatorInterface* collator = nullptr; @@ -775,37 +483,16 @@ TEST(UpdateArrayNodeTest, UpdatesToAllArrayElementsAreNoops) { foundIdentifiers)); Document doc(fromjson("{a: [1, 1, 1]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(indexesAffected); - ASSERT_TRUE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.indexesAffected); + ASSERT_TRUE(result.noop); ASSERT_EQUALS(fromjson("{a: [1, 1, 1]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(UpdateArrayNodeTest, NoArrayElementAffectsIndexes) { +TEST_F(UpdateArrayNodeTest, NoArrayElementAffectsIndexes) { auto update = fromjson("{$set: {'a.$[i].b': 0}}"); auto arrayFilter = fromjson("{'i.c': 0}"); const CollatorInterface* collator = nullptr; @@ -821,37 +508,16 @@ TEST(UpdateArrayNodeTest, NoArrayElementAffectsIndexes) { foundIdentifiers)); Document doc(fromjson("{a: [{c: 0}, {c: 0}, {c: 0}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a.c"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a.c"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{c: 0, b: 0}, {c: 0, b: 0}, {c: 0, b: 0}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: [{c: 0, b: 0}, {c: 0, b: 0}, {c: 0, b: 0}]}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: [{c: 0, b: 0}, {c: 0, b: 0}, {c: 0, b: 0}]}}"), getLogDoc()); } -TEST(UpdateArrayNodeTest, WhenOneElementIsMatchedLogElementUpdateDirectly) { +TEST_F(UpdateArrayNodeTest, WhenOneElementIsMatchedLogElementUpdateDirectly) { auto update = fromjson("{$set: {'a.$[i].b': 0}}"); auto arrayFilter = fromjson("{'i.c': 0}"); const CollatorInterface* collator = nullptr; @@ -867,37 +533,16 @@ TEST(UpdateArrayNodeTest, WhenOneElementIsMatchedLogElementUpdateDirectly) { foundIdentifiers)); Document doc(fromjson("{a: [{c: 1}, {c: 0}, {c: 1}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{c: 1}, {c: 0, b: 0}, {c: 1}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.1.b': 0}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.1.b': 0}}"), getLogDoc()); } -TEST(UpdateArrayNodeTest, WhenOneElementIsModifiedLogElement) { +TEST_F(UpdateArrayNodeTest, WhenOneElementIsModifiedLogElement) { auto update = fromjson("{$set: {'a.$[i].b': 0}}"); auto arrayFilter = fromjson("{'i.c': 0}"); const CollatorInterface* collator = nullptr; @@ -913,37 +558,16 @@ TEST(UpdateArrayNodeTest, WhenOneElementIsModifiedLogElement) { foundIdentifiers)); Document doc(fromjson("{a: [{c: 0, b: 0}, {c: 0}, {c: 1}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{c: 0, b: 0}, {c: 0, b: 0}, {c: 1}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.1': {c: 0, b: 0}}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.1': {c: 0, b: 0}}}"), getLogDoc()); } -TEST(UpdateArrayNodeTest, ArrayUpdateOnEmptyArrayIsANoop) { +TEST_F(UpdateArrayNodeTest, ArrayUpdateOnEmptyArrayIsANoop) { auto update = fromjson("{$set: {'a.$[]': 0}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -957,37 +581,16 @@ TEST(UpdateArrayNodeTest, ArrayUpdateOnEmptyArrayIsANoop) { foundIdentifiers)); Document doc(fromjson("{a: []}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(indexesAffected); - ASSERT_TRUE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.indexesAffected); + ASSERT_TRUE(result.noop); ASSERT_EQUALS(fromjson("{a: []}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(UpdateArrayNodeTest, ApplyPositionalInsideArrayUpdate) { +TEST_F(UpdateArrayNodeTest, ApplyPositionalInsideArrayUpdate) { auto update = fromjson("{$set: {'a.$[i].b.$': 1}}"); auto arrayFilter = fromjson("{'i.c': 0}"); const CollatorInterface* collator = nullptr; @@ -1003,37 +606,17 @@ TEST(UpdateArrayNodeTest, ApplyPositionalInsideArrayUpdate) { foundIdentifiers)); Document doc(fromjson("{a: [{b: [0, 0], c: 0}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField = "1"; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + setMatchedField("1"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{b: [0, 1], c: 0}]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {'a.0.b.1': 1}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {'a.0.b.1': 1}}"), getLogDoc()); } -TEST(UpdateArrayNodeTest, ApplyArrayUpdateFromReplication) { +TEST_F(UpdateArrayNodeTest, ApplyArrayUpdateFromReplication) { auto update = fromjson("{$set: {'a.$[i].b': 1}}"); auto arrayFilter = fromjson("{'i': 0}"); const CollatorInterface* collator = nullptr; @@ -1049,37 +632,17 @@ TEST(UpdateArrayNodeTest, ApplyArrayUpdateFromReplication) { foundIdentifiers)); Document doc(fromjson("{a: [0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(indexesAffected); - ASSERT_TRUE(noop); + addIndexedPath("a"); + setFromReplication(true); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.indexesAffected); + ASSERT_TRUE(result.noop); ASSERT_EQUALS(fromjson("{a: [0]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{}"), logDoc); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); } -TEST(UpdateArrayNodeTest, ApplyArrayUpdateNotFromReplication) { +TEST_F(UpdateArrayNodeTest, ApplyArrayUpdateNotFromReplication) { auto update = fromjson("{$set: {'a.$[i].b': 1}}"); auto arrayFilter = fromjson("{'i': 0}"); const CollatorInterface* collator = nullptr; @@ -1095,35 +658,14 @@ TEST(UpdateArrayNodeTest, ApplyArrayUpdateNotFromReplication) { foundIdentifiers)); Document doc(fromjson("{a: [0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), UserException, ErrorCodes::PathNotViable, "Cannot create field 'b' in element {0: 0}"); } -TEST(UpdateArrayNodeTest, ApplyArrayUpdateWithoutLogBuilderOrIndexData) { +TEST_F(UpdateArrayNodeTest, ApplyArrayUpdateWithoutLogBuilderOrIndexData) { auto update = fromjson("{$set: {'a.$[i]': 1}}"); auto arrayFilter = fromjson("{'i': 0}"); const CollatorInterface* collator = nullptr; @@ -1139,29 +681,10 @@ TEST(UpdateArrayNodeTest, ApplyArrayUpdateWithoutLogBuilderOrIndexData) { foundIdentifiers)); Document doc(fromjson("{a: [0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - LogBuilder* logBuilder = nullptr; - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(indexesAffected); - ASSERT_FALSE(noop); + setLogBuilderToNull(); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [1]}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); } diff --git a/src/mongo/db/update/update_driver.cpp b/src/mongo/db/update/update_driver.cpp index b094dcef6f9..8402971f9af 100644 --- a/src/mongo/db/update/update_driver.cpp +++ b/src/mongo/db/update/update_driver.cpp @@ -289,27 +289,21 @@ Status UpdateDriver::update(StringData matchedField, if (_root) { // We parsed using the new UpdateNode implementation. - FieldRef pathToCreate; - FieldRef pathTaken; - bool indexesAffected = false; - bool noop = false; - _root->apply(doc->root(), - &pathToCreate, - &pathTaken, - matchedField, - _modOptions.fromReplication, - validateForStorage, - immutablePaths, - _indexedFields, - (_logOp && logOpRec) ? &logBuilder : nullptr, - &indexesAffected, - &noop); - if (indexesAffected) { + UpdateNode::ApplyParams applyParams(doc->root(), immutablePaths); + applyParams.matchedField = matchedField; + applyParams.fromReplication = _modOptions.fromReplication; + applyParams.validateForStorage = validateForStorage; + applyParams.indexData = _indexedFields; + if (_logOp && logOpRec) { + applyParams.logBuilder = &logBuilder; + } + auto applyResult = _root->apply(applyParams); + if (applyResult.indexesAffected) { _affectIndices = true; doc->disableInPlaceUpdates(); } if (docWasModified) { - *docWasModified = !noop; + *docWasModified = !applyResult.noop; } } else { diff --git a/src/mongo/db/update/update_node.h b/src/mongo/db/update/update_node.h index a553b9ef111..f2dccbad85d 100644 --- a/src/mongo/db/update/update_node.h +++ b/src/mongo/db/update/update_node.h @@ -76,32 +76,69 @@ 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. Trips a uassert (which throws UserException) if the update node - * cannot be applied to the document. If 'validateForStorage' is true, ensures that modified - * elements do not violate depth or DBRef constraints. Ensures that no paths in 'immutablePaths' - * are modified (though they may be created, if they do not yet exist). + * The parameters required by UpdateNode::apply. */ - virtual void apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const = 0; + struct ApplyParams { + ApplyParams(mutablebson::Element element, const FieldRefSet& immutablePaths) + : element(element), immutablePaths(immutablePaths) {} + + // The element to update. + mutablebson::Element element; + + // UpdateNode::apply uasserts if it modifies an immutable path. + const FieldRefSet& immutablePaths; + + // 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, 'pathToCreate'="b.c". + std::shared_ptr<FieldRef> pathToCreate = std::make_shared<FieldRef>(); + + // The path through the root document to 'element', ending with the field name of 'element'. + // For example, if the update is {$set: {'a.b.c': 5}}, and the document is {a: {}}, then at + // the leaf node, 'pathTaken'="a". + std::shared_ptr<FieldRef> pathTaken = std::make_shared<FieldRef>(); + + // 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. + StringData matchedField; + + // This is provided because some modifiers may ignore certain errors when the update is from + // replication. + bool fromReplication = false; + + // If true, UpdateNode::apply ensures that modified elements do not violate depth or DBRef + // constraints. + bool validateForStorage = true; + + // Used to determine whether indexes are affected. + const UpdateIndexData* indexData = nullptr; + + // If provided, UpdateNode::apply will log the update here. + LogBuilder* logBuilder = nullptr; + }; + + /** + * The outputs of apply(). + */ + struct ApplyResult { + static ApplyResult noopResult() { + ApplyResult applyResult; + applyResult.indexesAffected = false; + applyResult.noop = true; + return applyResult; + } + + bool indexesAffected = true; + bool noop = false; + }; + + /** + * Applies the update node to 'applyParams.element', creating the fields in + * 'applyParams.pathToCreate' if required by the leaves (i.e. the leaves are not all $unset). + * Returns an ApplyResult specifying whether the operation was a no-op and whether indexes are + * affected. + */ + virtual ApplyResult apply(ApplyParams applyParams) const = 0; /** * Creates a new node by merging the contents of two input nodes. The semantics of the merge diff --git a/src/mongo/db/update/update_node_test_fixture.h b/src/mongo/db/update/update_node_test_fixture.h new file mode 100644 index 00000000000..29766992539 --- /dev/null +++ b/src/mongo/db/update/update_node_test_fixture.h @@ -0,0 +1,133 @@ +/** + * 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/logical_clock.h" +#include "mongo/db/service_context_noop.h" +#include "mongo/db/update/update_node.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { + +class UpdateNodeTest : public mongo::unittest::Test { +public: + ~UpdateNodeTest() override = default; + +protected: + void setUp() override { + resetApplyParams(); + + // Set up the logical clock needed by CurrentDateNode and ObjectReplaceNode. + auto service = mongo::getGlobalServiceContext(); + auto logicalClock = mongo::stdx::make_unique<mongo::LogicalClock>(service); + mongo::LogicalClock::set(service, std::move(logicalClock)); + } + + void resetApplyParams() { + _immutablePathsVector.clear(); + _immutablePaths.clear(); + _pathToCreate = std::make_shared<FieldRef>(); + _pathTaken = std::make_shared<FieldRef>(); + _matchedField = StringData(); + _fromReplication = false; + _validateForStorage = true; + _indexData.reset(); + _logDoc.reset(); + _logBuilder = stdx::make_unique<LogBuilder>(_logDoc.root()); + } + + UpdateNode::ApplyParams getApplyParams(mutablebson::Element element) { + UpdateNode::ApplyParams applyParams(element, _immutablePaths); + applyParams.pathToCreate = _pathToCreate; + applyParams.pathTaken = _pathTaken; + applyParams.matchedField = _matchedField; + applyParams.fromReplication = _fromReplication; + applyParams.validateForStorage = _validateForStorage; + applyParams.indexData = _indexData.get(); + applyParams.logBuilder = _logBuilder.get(); + return applyParams; + } + + void addImmutablePath(StringData path) { + auto fieldRef = stdx::make_unique<FieldRef>(path); + _immutablePathsVector.push_back(std::move(fieldRef)); + _immutablePaths.insert(_immutablePathsVector.back().get()); + } + + void setPathToCreate(StringData path) { + _pathToCreate->clear(); + _pathToCreate->parse(path); + } + + void setPathTaken(StringData path) { + _pathTaken->clear(); + _pathTaken->parse(path); + } + + void setMatchedField(StringData matchedField) { + _matchedField = matchedField; + } + + void setFromReplication(bool fromReplication) { + _fromReplication = fromReplication; + } + + void setValidateForStorage(bool validateForStorage) { + _validateForStorage = validateForStorage; + } + + void addIndexedPath(StringData path) { + if (!_indexData) { + _indexData = stdx::make_unique<UpdateIndexData>(); + } + _indexData->addPath(path); + } + + void setLogBuilderToNull() { + _logBuilder.reset(); + } + + const mutablebson::Document& getLogDoc() { + return _logDoc; + } + +private: + std::vector<std::unique_ptr<FieldRef>> _immutablePathsVector; + FieldRefSet _immutablePaths; + std::shared_ptr<FieldRef> _pathToCreate; + std::shared_ptr<FieldRef> _pathTaken; + StringData _matchedField; + bool _fromReplication; + bool _validateForStorage; + std::unique_ptr<UpdateIndexData> _indexData; + mutablebson::Document _logDoc; + std::unique_ptr<LogBuilder> _logBuilder; +}; + +} // namespace mongo diff --git a/src/mongo/db/update/update_object_node.cpp b/src/mongo/db/update/update_object_node.cpp index 25c51dc32a2..dd615278e46 100644 --- a/src/mongo/db/update/update_object_node.cpp +++ b/src/mongo/db/update/update_object_node.cpp @@ -81,39 +81,32 @@ StatusWith<std::string> parseArrayFilterIdentifier( } /** - * 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'. + * Applies 'child' to the child of 'applyParams->element' named 'field' (which will create it, if it + * does not exist). If 'applyParams->pathToCreate' is created, then 'applyParams->pathToCreate' is + * moved to the end of 'applyParams->pathTaken', and 'applyParams->element' is advanced to the end + * of 'applyParams->pathTaken'. Updates 'applyResult' based on whether 'child' was a noop or + * affected indexes. */ void applyChild(const UpdateNode& child, StringData field, - mutablebson::Element* element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) { - - auto pathTakenSizeBefore = pathTaken->numParts(); + UpdateNode::ApplyParams* applyParams, + UpdateNode::ApplyResult* applyResult) { + + auto pathTakenSizeBefore = applyParams->pathTaken->numParts(); // A non-ok value for childElement will indicate that we need to append 'field' to the // 'pathToCreate' FieldRef. - auto childElement = element->getDocument().end(); + auto childElement = applyParams->element.getDocument().end(); invariant(!childElement.ok()); - if (!pathToCreate->empty()) { + if (!applyParams->pathToCreate->empty()) { // We're already traversing a path with elements that don't exist yet, so we will definitely // need to append. - } else if (element->getType() == BSONType::Object) { - childElement = element->findFirstChildNamed(field); - } else if (element->getType() == BSONType::Array) { + } else if (applyParams->element.getType() == BSONType::Object) { + childElement = applyParams->element[field]; + } else if (applyParams->element.getType() == BSONType::Array) { boost::optional<size_t> indexFromField = parseUnsignedBase10Integer(field); if (indexFromField) { - childElement = element->findNthChild(*indexFromField); + childElement = applyParams->element.findNthChild(*indexFromField); } else { // We're trying to traverse an array element, but the path specifies a name instead of // an index. We append the name to 'pathToCreate' for now, even though we know we won't @@ -126,65 +119,54 @@ void applyChild(const UpdateNode& child, // The path we've traversed so far already exists in our document, and 'childElement' // represents the Element indicated by the 'field' name or index, which we indicate by // updating the 'pathTaken' FieldRef. - pathTaken->appendPart(field); + applyParams->pathTaken->appendPart(field); } else { // We are traversing path components that do not exist in our document. Any update modifier // that creates new path components (i.e., any PathCreatingNode update nodes) will need to // create this component, so we append it to the 'pathToCreate' FieldRef. - childElement = *element; - pathToCreate->appendPart(field); + childElement = applyParams->element; + applyParams->pathToCreate->appendPart(field); } - bool childAffectsIndexes = false; - bool childNoop = false; - - child.apply(childElement, - pathToCreate, - pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - &childAffectsIndexes, - &childNoop); + auto childApplyParams = *applyParams; + childApplyParams.element = childElement; + auto childApplyResult = child.apply(childApplyParams); - *indexesAffected = *indexesAffected || childAffectsIndexes; - *noop = *noop && childNoop; + applyResult->indexesAffected = applyResult->indexesAffected || childApplyResult.indexesAffected; + applyResult->noop = applyResult->noop && childApplyResult.noop; // Pop 'field' off of 'pathToCreate' or 'pathTaken'. - if (!pathToCreate->empty()) { - pathToCreate->removeLastPart(); + if (!applyParams->pathToCreate->empty()) { + applyParams->pathToCreate->removeLastPart(); } else { - pathTaken->removeLastPart(); + applyParams->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()); + if (applyParams->pathTaken->numParts() > pathTakenSizeBefore) { + for (auto i = pathTakenSizeBefore; i < applyParams->pathTaken->numParts(); ++i) { + applyParams->element = applyParams->element[applyParams->pathTaken->getPart(i)]; + invariant(applyParams->element.ok()); } - } else if (!pathToCreate->empty()) { + } else if (!applyParams->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)]; + childElement = applyParams->element[applyParams->pathToCreate->getPart(0)]; if (childElement.ok()) { - *element = childElement; - pathTaken->appendPart(pathToCreate->getPart(0)); + applyParams->element = childElement; + applyParams->pathTaken->appendPart(applyParams->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)); + for (size_t i = 1; i < applyParams->pathToCreate->numParts(); ++i) { + applyParams->element = applyParams->element[applyParams->pathToCreate->getPart(i)]; + invariant(applyParams->element.ok()); + applyParams->pathTaken->appendPart(applyParams->pathToCreate->getPart(i)); } - pathToCreate->clear(); + applyParams->pathToCreate->clear(); } } } @@ -384,72 +366,43 @@ void UpdateObjectNode::setChild(std::string field, std::unique_ptr<UpdateNode> c } } -void UpdateObjectNode::apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const { - *indexesAffected = false; - *noop = true; - +UpdateNode::ApplyResult UpdateObjectNode::apply(ApplyParams applyParams) const { bool applyPositional = _positionalChild.get(); if (applyPositional) { uassert(ErrorCodes::BadValue, "The positional operator did not find the match needed from the query.", - !matchedField.empty()); + !applyParams.matchedField.empty()); } - // Capture arguments to applyChild() to avoid code duplication. - auto applyChildClosure = [=, &element, &immutablePaths](const UpdateNode& child, - StringData field) { - applyChild(child, - field, - &element, - pathToCreate, - pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - logBuilder, - indexesAffected, - noop); - }; + auto applyResult = ApplyResult::noopResult(); 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) { + if (applyPositional && pair.first == applyParams.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)); + for (size_t i = 0; i < applyParams.pathToCreate->numParts(); ++i) { + applyParams.pathTaken->appendPart(applyParams.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(); + applyParams.pathTaken->appendPart(applyParams.matchedField); + auto insertResult = _mergedChildrenCache.emplace(std::make_pair( + pair.first, + UpdateNode::createUpdateNodeByMerging( + *_positionalChild, *pair.second, applyParams.pathTaken.get()))); + for (size_t i = 0; i < applyParams.pathToCreate->numParts() + 1; ++i) { + applyParams.pathTaken->removeLastPart(); } invariant(insertResult.second); mergedChild = insertResult.first; } - applyChildClosure(*mergedChild->second.get(), pair.first); + applyChild(*mergedChild->second.get(), pair.first, &applyParams, &applyResult); applyPositional = false; continue; @@ -457,19 +410,22 @@ void UpdateObjectNode::apply(mutablebson::Element element, // If 'matchedField' is alphabetically before the current child, we should apply the // positional child now. - if (applyPositional && matchedField < pair.first) { - applyChildClosure(*_positionalChild.get(), matchedField); + if (applyPositional && applyParams.matchedField < pair.first) { + applyChild( + *_positionalChild.get(), applyParams.matchedField, &applyParams, &applyResult); applyPositional = false; } // Apply the current child. - applyChildClosure(*pair.second, pair.first); + applyChild(*pair.second, pair.first, &applyParams, &applyResult); } // 'matchedField' is alphabetically after all children, so we apply it now. if (applyPositional) { - applyChildClosure(*_positionalChild.get(), matchedField); + applyChild(*_positionalChild.get(), applyParams.matchedField, &applyParams, &applyResult); } + + return applyResult; } } // namespace mongo diff --git a/src/mongo/db/update/update_object_node.h b/src/mongo/db/update/update_object_node.h index 07ed5b77dfd..81608da70c1 100644 --- a/src/mongo/db/update/update_object_node.h +++ b/src/mongo/db/update/update_object_node.h @@ -91,17 +91,7 @@ public: } } - void apply(mutablebson::Element element, - FieldRef* pathToCreate, - FieldRef* pathTaken, - StringData matchedField, - bool fromReplication, - bool validateForStorage, - const FieldRefSet& immutablePaths, - const UpdateIndexData* indexData, - LogBuilder* logBuilder, - bool* indexesAffected, - bool* noop) const final; + ApplyResult apply(ApplyParams applyParams) const final; UpdateNode* getChild(const std::string& field) const final; diff --git a/src/mongo/db/update/update_object_node_test.cpp b/src/mongo/db/update/update_object_node_test.cpp index 3257380011e..f991d33f7ab 100644 --- a/src/mongo/db/update/update_object_node_test.cpp +++ b/src/mongo/db/update/update_object_node_test.cpp @@ -37,12 +37,14 @@ #include "mongo/db/update/conflict_placeholder_node.h" #include "mongo/db/update/rename_node.h" #include "mongo/db/update/update_array_node.h" +#include "mongo/db/update/update_node_test_fixture.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" namespace mongo { namespace { +using UpdateObjectNodeTest = UpdateNodeTest; using mongo::mutablebson::Document; using mongo::mutablebson::Element; @@ -1704,7 +1706,7 @@ TEST(UpdateObjectNodeTest, NoMergeConflictThroughArrayNodesSucceeds) { ASSERT_TRUE(fieldsMatch({"b", "c"}, *iNode)); } -TEST(UpdateObjectNodeTest, ApplyCreateField) { +TEST_F(UpdateObjectNodeTest, ApplyCreateField) { auto setUpdate = fromjson("{$set: {b: 6}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -1718,37 +1720,16 @@ TEST(UpdateObjectNodeTest, ApplyCreateField) { foundIdentifiers)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("b"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("b"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: 5, b: 6}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {b: 6}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {b: 6}}"), getLogDoc()); } -TEST(UpdateObjectNodeTest, ApplyExistingField) { +TEST_F(UpdateObjectNodeTest, ApplyExistingField) { auto setUpdate = fromjson("{$set: {a: 6}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -1762,37 +1743,16 @@ TEST(UpdateObjectNodeTest, ApplyExistingField) { foundIdentifiers)); Document doc(fromjson("{a: 5}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: 6}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_EQUALS(fromjson("{$set: {a: 6}}"), logDoc); + ASSERT_EQUALS(fromjson("{$set: {a: 6}}"), getLogDoc()); } -TEST(UpdateObjectNodeTest, ApplyExistingAndNonexistingFields) { +TEST_F(UpdateObjectNodeTest, ApplyExistingAndNonexistingFields) { auto setUpdate = fromjson("{$set: {a: 5, b: 6, c: 7, d: 8}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -1824,37 +1784,16 @@ TEST(UpdateObjectNodeTest, ApplyExistingAndNonexistingFields) { foundIdentifiers)); Document doc(fromjson("{a: 0, c: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {a: 5, b: 6, c: 7, d: 8}}"), getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, ApplyExistingNestedPaths) { +TEST_F(UpdateObjectNodeTest, ApplyExistingNestedPaths) { auto setUpdate = fromjson("{$set: {'a.b': 6, 'a.c': 7, 'b.d': 8, 'b.e': 9}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -1886,38 +1825,17 @@ TEST(UpdateObjectNodeTest, ApplyExistingNestedPaths) { foundIdentifiers)); Document doc(fromjson("{a: {b: 5, c: 5}, b: {d: 5, e: 5}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, ApplyCreateNestedPaths) { +TEST_F(UpdateObjectNodeTest, ApplyCreateNestedPaths) { auto setUpdate = fromjson("{$set: {'a.b': 6, 'a.c': 7, 'b.d': 8, 'b.e': 9}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -1949,38 +1867,17 @@ TEST(UpdateObjectNodeTest, ApplyCreateNestedPaths) { foundIdentifiers)); Document doc(fromjson("{z: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, ApplyCreateDeeplyNestedPaths) { +TEST_F(UpdateObjectNodeTest, ApplyCreateDeeplyNestedPaths) { auto setUpdate = fromjson("{$set: {'a.b.c.d': 6, 'a.b.c.e': 7, 'a.f': 8}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2006,38 +1903,17 @@ TEST(UpdateObjectNodeTest, ApplyCreateDeeplyNestedPaths) { foundIdentifiers)); Document doc(fromjson("{z: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, ChildrenShouldBeAppliedInAlphabeticalOrder) { +TEST_F(UpdateObjectNodeTest, ChildrenShouldBeAppliedInAlphabeticalOrder) { auto setUpdate = fromjson("{$set: {a: 5, d: 6, c: 7, b: 8, z: 9}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2075,37 +1951,16 @@ TEST(UpdateObjectNodeTest, ChildrenShouldBeAppliedInAlphabeticalOrder) { foundIdentifiers)); Document doc(fromjson("{z: 0, a: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {a: 5, b: 8, c: 7, d: 6, z: 9}}"), getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, CollatorShouldNotAffectUpdateOrder) { +TEST_F(UpdateObjectNodeTest, CollatorShouldNotAffectUpdateOrder) { auto setUpdate = fromjson("{$set: {abc: 5, cba: 6}}"); CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2125,37 +1980,16 @@ TEST(UpdateObjectNodeTest, CollatorShouldNotAffectUpdateOrder) { foundIdentifiers)); Document doc(fromjson("{}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("abc"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("abc"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {abc: 5, cba: 6}}"), getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, ApplyNoop) { +TEST_F(UpdateObjectNodeTest, ApplyNoop) { auto setUpdate = fromjson("{$set: {a: 5, b: 6, c: 7}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2181,39 +2015,18 @@ TEST(UpdateObjectNodeTest, ApplyNoop) { foundIdentifiers)); Document doc(fromjson("{a: 5, b: 6, c: 7}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - indexData.addPath("b"); - indexData.addPath("c"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(indexesAffected); - ASSERT_TRUE(noop); + addIndexedPath("a"); + addIndexedPath("b"); + addIndexedPath("c"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.indexesAffected); + ASSERT_TRUE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: 5, b: 6, c: 7}"), doc.getObject()); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_BSONOBJ_EQ(fromjson("{}"), logDoc.getObject()); + ASSERT_BSONOBJ_EQ(fromjson("{}"), getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, ApplySomeChildrenNoops) { +TEST_F(UpdateObjectNodeTest, ApplySomeChildrenNoops) { auto setUpdate = fromjson("{$set: {a: 5, b: 6, c: 7}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2239,39 +2052,18 @@ TEST(UpdateObjectNodeTest, ApplySomeChildrenNoops) { foundIdentifiers)); Document doc(fromjson("{a: 5, b: 0, c: 7}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - indexData.addPath("b"); - indexData.addPath("c"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + addIndexedPath("b"); + addIndexedPath("c"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {b: 6}}"), getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, ApplyBlockingElement) { +TEST_F(UpdateObjectNodeTest, ApplyBlockingElement) { auto setUpdate = fromjson("{$set: {'a.b': 5}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2285,35 +2077,14 @@ TEST(UpdateObjectNodeTest, ApplyBlockingElement) { foundIdentifiers)); Document doc(fromjson("{a: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), UserException, ErrorCodes::PathNotViable, "Cannot create field 'b' in element {a: 0}"); } -TEST(UpdateObjectNodeTest, ApplyBlockingElementFromReplication) { +TEST_F(UpdateObjectNodeTest, ApplyBlockingElementFromReplication) { auto setUpdate = fromjson("{$set: {'a.b': 5, b: 6}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2333,37 +2104,17 @@ TEST(UpdateObjectNodeTest, ApplyBlockingElementFromReplication) { foundIdentifiers)); Document doc(fromjson("{a: 0}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = true; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + setFromReplication(true); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: 0, b: 6}"), doc.getObject()); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_BSONOBJ_EQ(fromjson("{$set: {b: 6}}"), logDoc.getObject()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {b: 6}}"), getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, ApplyPositionalMissingMatchedField) { +TEST_F(UpdateObjectNodeTest, ApplyPositionalMissingMatchedField) { auto setUpdate = fromjson("{$set: {'a.$': 5}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2377,36 +2128,15 @@ TEST(UpdateObjectNodeTest, ApplyPositionalMissingMatchedField) { foundIdentifiers)); Document doc(fromjson("{}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; + addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT( - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + root.apply(getApplyParams(doc.root())), UserException, ErrorCodes::BadValue, "The positional operator did not find the match needed from the query."); } -TEST(UpdateObjectNodeTest, ApplyMergePositionalChild) { +TEST_F(UpdateObjectNodeTest, ApplyMergePositionalChild) { auto setUpdate = fromjson("{$set: {'a.0.b': 5, 'a.$.c': 6}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2426,37 +2156,17 @@ TEST(UpdateObjectNodeTest, ApplyMergePositionalChild) { foundIdentifiers)); Document doc(fromjson("{a: [{b: 0, c: 0}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField = "0"; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + setMatchedField("0"); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.0.b': 5, 'a.0.c': 6}}"), getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, ApplyOrderMergedPositionalChild) { +TEST_F(UpdateObjectNodeTest, ApplyOrderMergedPositionalChild) { auto setUpdate = fromjson("{$set: {'a.2': 5, 'a.1.b': 6, 'a.0': 7, 'a.$.c': 8}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2488,38 +2198,18 @@ TEST(UpdateObjectNodeTest, ApplyOrderMergedPositionalChild) { foundIdentifiers)); Document doc(fromjson("{}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField = "1"; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + setMatchedField("1"); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, ApplyMergeConflictWithPositionalChild) { +TEST_F(UpdateObjectNodeTest, ApplyMergeConflictWithPositionalChild) { auto setUpdate = fromjson("{$set: {'a.0': 5, 'a.$': 6}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2539,35 +2229,15 @@ TEST(UpdateObjectNodeTest, ApplyMergeConflictWithPositionalChild) { foundIdentifiers)); Document doc(fromjson("{}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField = "0"; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + setMatchedField("0"); + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), UserException, ErrorCodes::ConflictingUpdateOperators, "Update created a conflict at 'a.0'"); } -TEST(UpdateObjectNodeTest, ApplyDoNotMergePositionalChild) { +TEST_F(UpdateObjectNodeTest, ApplyDoNotMergePositionalChild) { auto setUpdate = fromjson("{$set: {'a.0': 5, 'a.2': 6, 'a.$': 7}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2593,37 +2263,17 @@ TEST(UpdateObjectNodeTest, ApplyDoNotMergePositionalChild) { foundIdentifiers)); Document doc(fromjson("{}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField = "1"; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + setMatchedField("1"); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.0': 5, 'a.1': 7, 'a.2': 6}}"), getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, ApplyPositionalChildLast) { +TEST_F(UpdateObjectNodeTest, ApplyPositionalChildLast) { auto setUpdate = fromjson("{$set: {'a.$': 5, 'a.0': 6, 'a.1': 7}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2649,37 +2299,17 @@ TEST(UpdateObjectNodeTest, ApplyPositionalChildLast) { foundIdentifiers)); Document doc(fromjson("{}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField = "2"; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + setMatchedField("2"); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.0': 6, 'a.1': 7, 'a.2': 5}}"), getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, ApplyUseStoredMergedPositional) { +TEST_F(UpdateObjectNodeTest, ApplyUseStoredMergedPositional) { auto setUpdate = fromjson("{$set: {'a.0.b': 5, 'a.$.c': 6}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2699,57 +2329,28 @@ TEST(UpdateObjectNodeTest, ApplyUseStoredMergedPositional) { foundIdentifiers)); Document doc(fromjson("{a: [{b: 0, c: 0}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField = "0"; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + setMatchedField("0"); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.0.b': 5, 'a.0.c': 6}}"), getLogDoc().getObject()); Document doc2(fromjson("{a: [{b: 0, c: 0}]}")); - Document logDoc2; - LogBuilder logBuilder2(logDoc2.root()); - root.apply(doc2.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder2, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + resetApplyParams(); + setMatchedField("0"); + addIndexedPath("a"); + result = root.apply(getApplyParams(doc2.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.0.b': 5, 'a.0.c': 6}}"), getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, ApplyDoNotUseStoredMergedPositional) { +TEST_F(UpdateObjectNodeTest, ApplyDoNotUseStoredMergedPositional) { auto setUpdate = fromjson("{$set: {'a.0.b': 5, 'a.$.c': 6, 'a.1.d': 7}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2775,56 +2376,27 @@ TEST(UpdateObjectNodeTest, ApplyDoNotUseStoredMergedPositional) { foundIdentifiers)); Document doc(fromjson("{a: [{b: 0, c: 0}, {c: 0, d: 0}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField = "0"; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + setMatchedField("0"); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.0.b': 5, 'a.0.c': 6, 'a.1.d': 7}}"), + getLogDoc().getObject()); Document doc2(fromjson("{a: [{b: 0, c: 0}, {c: 0, d: 0}]}")); - StringData matchedField2 = "1"; - Document logDoc2; - LogBuilder logBuilder2(logDoc2.root()); - root.apply(doc2.root(), - &pathToCreate, - &pathTaken, - matchedField2, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder2, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + resetApplyParams(); + setMatchedField("1"); + addIndexedPath("a"); + result = root.apply(getApplyParams(doc2.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.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()); + getLogDoc().getObject()); } /** @@ -2832,7 +2404,7 @@ TEST(UpdateObjectNodeTest, ApplyDoNotUseStoredMergedPositional) { * string, a leading zero will cause the lookup to fail. That is, even if 'element' is an array, * element["02"] will not find the element with subscript 2. */ -TEST(UpdateObjectNodeTest, ApplyToArrayByIndexWithLeadingZero) { +TEST_F(UpdateObjectNodeTest, ApplyToArrayByIndexWithLeadingZero) { auto setUpdate = fromjson("{$set: {'a.02': 2}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2846,34 +2418,13 @@ TEST(UpdateObjectNodeTest, ApplyToArrayByIndexWithLeadingZero) { foundIdentifiers)); Document doc(fromjson("{a: [0, 0, 0, 0, 0]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: [0, 0, 2, 0, 0]}"), doc.getObject()); ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.02': 2}}"), logDoc.getObject()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.02': 2}}"), getLogDoc().getObject()); } /** @@ -2882,7 +2433,7 @@ TEST(UpdateObjectNodeTest, ApplyToArrayByIndexWithLeadingZero) { elements did not have field names to match their array indexes. As a result, the 'a.2' update failed. */ -TEST(UpdateObjectNodeTest, ApplyMultipleArrayUpdates) { +TEST_F(UpdateObjectNodeTest, ApplyMultipleArrayUpdates) { auto setUpdate = fromjson("{$set: {'a.2': 2, 'a.10': 10}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2902,39 +2453,18 @@ TEST(UpdateObjectNodeTest, ApplyMultipleArrayUpdates) { foundIdentifiers)); Document doc(fromjson("{a: []}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - UpdateIndexData indexData; - indexData.addPath("a"); - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - auto indexesAffected = false; - auto noop = false; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_TRUE(indexesAffected); - ASSERT_FALSE(noop); + addIndexedPath("a"); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_TRUE(result.indexesAffected); + ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ( fromjson("{a: [null, null, 2, null, null, null, null, null, null, null, 10]}"), doc.getObject()); ASSERT_FALSE(doc.isInPlaceModeEnabled()); - ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.10': 10, 'a.2': 2}}"), logDoc.getObject()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.10': 10, 'a.2': 2}}"), getLogDoc().getObject()); } -TEST(UpdateObjectNodeTest, ApplyUpdateToNonViablePathInArray) { +TEST_F(UpdateObjectNodeTest, ApplyUpdateToNonViablePathInArray) { auto setUpdate = fromjson("{$set: {'a.b': 3}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2948,35 +2478,14 @@ TEST(UpdateObjectNodeTest, ApplyUpdateToNonViablePathInArray) { foundIdentifiers)); Document doc(fromjson("{a: [{b: 1}, {b: 2}]}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - 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, - validateForStorage, - immutablePaths, - &indexData, - &logBuilder, - &indexesAffected, - &noop), + addIndexedPath("a"); + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), UserException, ErrorCodes::PathNotViable, "Cannot create field 'b' in element {a: [ { b: 1 }, { b: 2 } ]}"); } -TEST(UpdateObjectNodeTest, SetAndPopModifiersWithCommonPrefixApplySuccessfully) { +TEST_F(UpdateObjectNodeTest, SetAndPopModifiersWithCommonPrefixApplySuccessfully) { auto update = fromjson("{$set: {'a.b': 5}, $pop: {'a.c': -1}}"); const CollatorInterface* collator = nullptr; std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -2996,33 +2505,12 @@ TEST(UpdateObjectNodeTest, SetAndPopModifiersWithCommonPrefixApplySuccessfully) foundIdentifiers)); Document doc(fromjson("{a: {b: 3, c: [1, 2, 3, 4]}}")); - FieldRef pathToCreate(""); - FieldRef pathTaken(""); - StringData matchedField; - auto fromReplication = false; - auto validateForStorage = true; - FieldRefSet immutablePaths; - const UpdateIndexData* indexData = nullptr; - Document logDoc; - LogBuilder logBuilder(logDoc.root()); - bool indexesAffected; - bool noop; - root.apply(doc.root(), - &pathToCreate, - &pathTaken, - matchedField, - fromReplication, - validateForStorage, - immutablePaths, - indexData, - &logBuilder, - &indexesAffected, - &noop); - ASSERT_FALSE(noop); - ASSERT_FALSE(indexesAffected); + auto result = root.apply(getApplyParams(doc.root())); + ASSERT_FALSE(result.indexesAffected); + ASSERT_FALSE(result.noop); 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()); + ASSERT_BSONOBJ_EQ(fromjson("{$set: {'a.b': 5, 'a.c': [2, 3, 4]}}"), getLogDoc().getObject()); } TEST(ParseRenameTest, RenameToStringWithEmbeddedNullFails) { |