TEST_F(UpdateArrayNodeTest, UpdateIsAppliedToAllMatchingElements) { auto update = fromjson("{$set: {'a.$[i]': 2}}"); auto arrayFilter = fromjson("{i: 0}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx)); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set<std::string> foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[i]"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: [0, 1, 0]}")); 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]}}"), getLogDoc()); ASSERT_EQUALS("{a.0, a.2}", getModifiedPaths()); }
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)); mutablebson::Document doc(fromjson("{a: {$ref: 'c', $id: 0}}")); setPathTaken("a.$id"); ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"]["$id"])), AssertionException, ErrorCodes::InvalidDBRef, "The DBRef $ref field must be followed by a $id field"); }
TEST_F(UnsetNodeTest, ApplyCannotRemoveRequiredPartOfDBRef) { auto update = fromjson("{$unset: {'a.$id': true}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.$id"], expCtx)); mutablebson::Document doc(fromjson("{a: {$ref: 'c', $id: 0}}")); setPathTaken("a.$id"); ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"]["$id"])), AssertionException, ErrorCodes::InvalidDBRef, "The DBRef $ref field must be followed by a $id field"); }
TEST_F(UnsetNodeTest, ApplyCannotRemovePrefixOfImmutablePath) { auto update = fromjson("{$unset: {a: true}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a"], collator)); mutablebson::Document doc(fromjson("{a: {b: 1}}")); setPathTaken("a"); addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), AssertionException, ErrorCodes::ImmutableField, "Unsetting the path 'a' would modify the immutable field 'a.b'"); }
TEST_F(UnsetNodeTest, ApplyCannotRemoveSuffixOfImmutablePath) { auto update = fromjson("{$unset: {'a.b.c': true}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.b.c"], expCtx)); mutablebson::Document doc(fromjson("{a: {b: {c: 1}}}")); setPathTaken("a.b.c"); addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( node.apply(getApplyParams(doc.root()["a"]["b"]["c"])), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path 'a.b.c' would modify the immutable field 'a.b'"); }
TEST_F(PopNodeTest, NoopWhenFirstPathComponentDoesNotExist) { auto update = fromjson("{$pop: {'a.b': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); PopNode popNode; ASSERT_OK(popNode.init(update["$pop"]["a.b"], expCtx)); mmb::Document doc(fromjson("{b: [1, 2, 3]}")); 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("{}"), getLogDoc()); }
TEST_F(UnsetNodeTest, ApplyNoIndexDataNoLogBuilder) { auto update = fromjson("{$unset: {a: 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a"], collator)); mutablebson::Document doc(fromjson("{a: 5}")); 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_F(UnsetNodeTest, UnsetNoOpEmptyDoc) { auto update = fromjson("{$unset: {a: 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a"], collator)); mutablebson::Document doc(fromjson("{}")); 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("{}"), getLogDoc()); }
TEST_F(PopNodeTest, ThrowsWhenPathIsBlockedByAScalar) { auto update = fromjson("{$pop: {'a.b': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); PopNode popNode; ASSERT_OK(popNode.init(update["$pop"]["a.b"], expCtx)); mmb::Document doc(fromjson("{a: 'foo'}")); setPathToCreate("b"); setPathTaken("a"); addIndexedPath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( popNode.apply(getApplyParams(doc.root()["a"])), AssertionException, ErrorCodes::PathNotViable, "Cannot use the part (b) of (a.b) to traverse the element ({a: \"foo\"})"); }
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)); mutablebson::Document doc(fromjson("{a: 'cbc'}")); 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'}}"), getLogDoc()); }
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)); mutablebson::Document doc(fromjson("{'a.b':4, a: {b: 2}}")); 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}}"), getLogDoc()); }
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)); mutablebson::Document doc(fromjson("{a: 1}")); 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("{}"), getLogDoc()); }
TEST_F(PopNodeTest, NoopWhenNumericalPathComponentExceedsArrayLength) { auto update = fromjson("{$pop: {'a.0': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); PopNode popNode; ASSERT_OK(popNode.init(update["$pop"]["a.0"], expCtx)); mmb::Document doc(fromjson("{a: []}")); 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("{}"), getLogDoc()); }
TEST_F(UnsetNodeTest, ApplyNoIndexDataNoLogBuilder) { auto update = fromjson("{$unset: {a: 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a"], expCtx)); mutablebson::Document doc(fromjson("{a: 5}")); 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()); ASSERT_EQUALS(getModifiedPaths(), "{a}"); }
TEST_F(PopNodeTest, NoopWhenPathPartiallyExists) { auto update = fromjson("{$pop: {'a.b.c': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); PopNode popNode; ASSERT_OK(popNode.init(update["$pop"]["a.b.c"], expCtx)); mmb::Document doc(fromjson("{a: {}}")); 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("{}"), getLogDoc()); }
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)); mutablebson::Document doc(fromjson("{a: 0.0}")); 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("{}"), getLogDoc()); }
TEST_F(UnsetNodeTest, ApplyFieldWithDot) { auto update = fromjson("{$unset: {'a.b': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.b"], expCtx)); mutablebson::Document doc(fromjson("{'a.b':4, a: {b: 2}}")); 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}}"), getLogDoc()); ASSERT_EQUALS(getModifiedPaths(), "{a.b}"); }
TEST_F(UnsetNodeTest, UnsetNoOpEmptyDoc) { auto update = fromjson("{$unset: {a: 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a"], expCtx)); mutablebson::Document doc(fromjson("{}")); 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("{}"), getLogDoc()); ASSERT_EQUALS(getModifiedPaths(), "{a}"); }
TEST_F(UnsetNodeTest, UnsetPositional) { auto update = fromjson("{$unset: {'a.$': 1}}"); const CollatorInterface* collator = nullptr; UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.$"], collator)); mutablebson::Document doc(fromjson("{a: [0, 1, 2]}")); 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}}"), getLogDoc()); }
TEST_F(UnsetNodeTest, UnsetPositional) { auto update = fromjson("{$unset: {'a.$': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.$"], expCtx)); mutablebson::Document doc(fromjson("{a: [0, 1, 2]}")); 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}}"), getLogDoc()); ASSERT_EQUALS(getModifiedPaths(), "{a.1}"); }
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)); mutablebson::Document doc(fromjson("{a: {$ref: 'c', $id: 0}}")); 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}}"), getLogDoc()); }
TEST_F(UnsetNodeTest, ApplyCanRemoveRequiredPartOfDBRefIfValidateForStorageIsFalse) { auto update = fromjson("{$unset: {'a.$id': true}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"]["a.$id"], expCtx)); mutablebson::Document doc(fromjson("{a: {$ref: 'c', $id: 0}}")); 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}}"), getLogDoc()); ASSERT_EQUALS(getModifiedPaths(), "{a.$id}"); }
TEST_F(CompareNodeTest, ApplyMaxRespectsCollationFromSetCollator) { auto update = fromjson("{$max: {a: 'abd'}}"); const CollatorInterface* binaryComparisonCollator = nullptr; CompareNode node(CompareNode::CompareMode::kMax); ASSERT_OK(node.init(update["$max"]["a"], binaryComparisonCollator)); const CollatorInterfaceMock reverseStringCollator( CollatorInterfaceMock::MockType::kReverseString); node.setCollator(&reverseStringCollator); mutablebson::Document doc(fromjson("{a: 'cbc'}")); 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'}}"), getLogDoc()); }
auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx)); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set<std::string> foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[i].b"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: [{c: 0}, {c: 0}, {c: 1}]}")); ASSERT_OK(doc.root()["a"][1]["c"].setValueInt(1)); ASSERT_OK(doc.root()["a"][2]["c"].setValueInt(0)); addIndexedPath("a"); root.apply(getApplyParams(doc.root())); } TEST_F(UpdateArrayNodeTest, UpdateForEmptyIdentifierIsAppliedToAllArrayElements) { auto update = fromjson("{$set: {'a.$[]': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; std::set<std::string> foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[]"], expCtx, arrayFilters, foundIdentifiers));
ErrorCodes::PathNotViable, "Cannot use the part (b) of (a.b) to traverse the element ({a: \"foo\"})"); } DEATH_TEST_F(PopNodeTest, NonOkElementWhenPathExistsIsFatal, "Invariant failure applyParams.element.ok()") { auto update = fromjson("{$pop: {'a.b': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); PopNode popNode; ASSERT_OK(popNode.init(update["$pop"]["a.b"], expCtx)); mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); setPathTaken("a.b"); addIndexedPath("a.b"); popNode.apply(getApplyParams(doc.end())); } TEST_F(PopNodeTest, ThrowsWhenPathExistsButDoesNotContainAnArray) { auto update = fromjson("{$pop: {'a.b': 1}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); PopNode popNode; ASSERT_OK(popNode.init(update["$pop"]["a.b"], expCtx)); mmb::Document doc(fromjson("{a: {b: 'foo'}}")); setPathTaken("a.b"); addIndexedPath("a.b"); ASSERT_THROWS_CODE_AND_WHAT(popNode.apply(getApplyParams(doc.root()["a"]["b"])), AssertionException, ErrorCodes::TypeMismatch, "Path 'a.b' contains an element of non-array type 'string'");