/** * 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 . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects * for all of the code used other than as permitted herein. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you do not * wish to do so, delete this exception statement from your version. If you * delete this exception statement from all source files in the program, * then also delete it in the license file. */ #include "mongo/platform/basic.h" #include "mongo/db/update/update_array_node.h" #include "mongo/bson/mutable/algorithm.h" #include "mongo/bson/mutable/mutable_bson_test_utils.h" #include "mongo/db/json.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/pipeline/expression_context_for_test.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" namespace mongo { namespace { using UpdateArrayNodeTest = UpdateNodeTest; using mongo::mutablebson::Element; using unittest::assertGet; TEST_F(UpdateArrayNodeTest, ApplyCreatePathFails) { auto update = fromjson("{$set: {'a.b.$[i]': 0}}"); auto arrayFilter = fromjson("{i: 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx)); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.b.$[i]"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: {}}")); addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT( root.apply(getApplyParams(doc.root())), AssertionException, ErrorCodes::BadValue, "The path 'a.b' must exist in the document in order to apply array updates."); } TEST_F(UpdateArrayNodeTest, ApplyToNonArrayFails) { auto update = fromjson("{$set: {'a.$[i]': 0}}"); auto arrayFilter = fromjson("{i: 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx)); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[i]"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: {}}")); addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), AssertionException, ErrorCodes::BadValue, "Cannot apply array updates to non-array element a: {}"); } TEST_F(UpdateArrayNodeTest, UpdateIsAppliedToAllMatchingElements) { auto update = fromjson("{$set: {'a.$[i]': 2}}"); auto arrayFilter = fromjson("{i: 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx)); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set 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()); } DEATH_TEST_F(UpdateArrayNodeTest, ArrayElementsMustNotBeDeserialized, "Invariant failure childElement.hasValue()") { auto update = fromjson("{$set: {'a.$[i].b': 0}}"); auto arrayFilter = fromjson("{'i.c': 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx)); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set 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 expCtx(new ExpressionContextForTest()); std::map> arrayFilters; std::set foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[]"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: [0, 0, 0]}")); 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]}}"), getLogDoc()); } 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}"); auto arrayFilterK = fromjson("{'k.d': 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilterI = assertGet(MatchExpressionParser::parse(arrayFilterI, expCtx)); auto parsedFilterJ = assertGet(MatchExpressionParser::parse(arrayFilterJ, expCtx)); auto parsedFilterK = assertGet(MatchExpressionParser::parse(arrayFilterK, expCtx)); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterI))); arrayFilters["j"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterJ))); arrayFilters["k"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterK))); std::set foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[i].b"], expCtx, arrayFilters, foundIdentifiers)); ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[j].c"], expCtx, arrayFilters, foundIdentifiers)); ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[k].d"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: [{b: 0, c: 0, d: 0}]}")); 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}}"), getLogDoc()); } 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}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilterI = assertGet(MatchExpressionParser::parse(arrayFilterI, expCtx)); auto parsedFilterJ = assertGet(MatchExpressionParser::parse(arrayFilterJ, expCtx)); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterI))); arrayFilters["j"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterJ))); std::set foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[i].b"], expCtx, arrayFilters, foundIdentifiers)); ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[j].c"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: [{b: 0, c: 0}, {b: 0, c: 0}]}")); 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}]}}"), getLogDoc()); } 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}"); auto arrayFilterK = fromjson("{'k.d': 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilterI = assertGet(MatchExpressionParser::parse(arrayFilterI, expCtx)); auto parsedFilterJ = assertGet(MatchExpressionParser::parse(arrayFilterJ, expCtx)); auto parsedFilterK = assertGet(MatchExpressionParser::parse(arrayFilterK, expCtx)); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterI))); arrayFilters["j"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterJ))); arrayFilters["k"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterK))); std::set foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[i].b"], expCtx, arrayFilters, foundIdentifiers)); ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[j].c"], expCtx, arrayFilters, foundIdentifiers)); ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[k].d"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: [{b: 0, c: 0, d: 1}, {b: 1, c: 0, d: 0}]}")); 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}]}}"), getLogDoc()); } TEST_F(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElementWithEmptyIdentifiers) { auto update = fromjson("{$set: {'a.$[].b': 1, 'a.$[].c': 1}}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; std::set foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[].b"], expCtx, arrayFilters, foundIdentifiers)); ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[].c"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: [{b: 0, c: 0}]}")); 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}}"), getLogDoc()); } 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}"); auto arrayFilterK = fromjson("{'k.x': 0}"); auto arrayFilterL = fromjson("{'l.d': 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilterI = assertGet(MatchExpressionParser::parse(arrayFilterI, expCtx )); auto parsedFilterJ = assertGet(MatchExpressionParser::parse(arrayFilterJ, expCtx )); auto parsedFilterK = assertGet(MatchExpressionParser::parse(arrayFilterK, expCtx )); auto parsedFilterL = assertGet(MatchExpressionParser::parse(arrayFilterL, expCtx )); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterI))); arrayFilters["j"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterJ))); arrayFilters["k"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterK))); arrayFilters["l"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterL))); std::set foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[i].b.$[j].c"], expCtx, arrayFilters, foundIdentifiers)); ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[k].b.$[l].d"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: [{x: 0, b: [{c: 0, d: 0}]}]}")); 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}}"), getLogDoc()); } TEST_F(UpdateArrayNodeTest, ApplyUpdatesWithMergeConflictToArrayElementFails) { auto update = fromjson("{$set: {'a.$[i]': 1, 'a.$[j]': 1}}"); auto arrayFilterI = fromjson("{'i': 0}"); auto arrayFilterJ = fromjson("{'j': 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilterI = assertGet(MatchExpressionParser::parse(arrayFilterI, expCtx )); auto parsedFilterJ = assertGet(MatchExpressionParser::parse(arrayFilterJ, expCtx )); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterI))); arrayFilters["j"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterJ))); std::set foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[i]"], expCtx, arrayFilters, foundIdentifiers)); ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[j]"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: [0]}")); addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), AssertionException, ErrorCodes::ConflictingUpdateOperators, "Update created a conflict at 'a.0'"); } 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}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilterI = assertGet(MatchExpressionParser::parse(arrayFilterI, expCtx )); auto parsedFilterJ = assertGet(MatchExpressionParser::parse(arrayFilterJ, expCtx )); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterI))); arrayFilters["j"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterJ))); std::set foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[].b.$[i]"], expCtx, arrayFilters, foundIdentifiers)); ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[].b.$[j]"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: [{b: [0]}]}")); addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), AssertionException, ErrorCodes::ConflictingUpdateOperators, "Update created a conflict at 'a.0.b.0'"); } 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}"); auto arrayFilterK = fromjson("{'k.c': 0}"); auto arrayFilterL = fromjson("{l: 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilterI = assertGet(MatchExpressionParser::parse(arrayFilterI, expCtx )); auto parsedFilterJ = assertGet(MatchExpressionParser::parse(arrayFilterJ, expCtx )); auto parsedFilterK = assertGet(MatchExpressionParser::parse(arrayFilterK, expCtx )); auto parsedFilterL = assertGet(MatchExpressionParser::parse(arrayFilterL, expCtx )); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterI))); arrayFilters["j"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterJ))); arrayFilters["k"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterK))); arrayFilters["l"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilterL))); std::set foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[i].b.$[j]"], expCtx, arrayFilters, foundIdentifiers)); ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[k].b.$[l]"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: [{b: [0], c: 0}]}")); addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), AssertionException, ErrorCodes::ConflictingUpdateOperators, "Update created a conflict at 'a.0.b.0'"); } TEST_F(UpdateArrayNodeTest, NoArrayElementsMatch) { auto update = fromjson("{$set: {'a.$[i]': 1}}"); auto arrayFilter = fromjson("{'i': 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx )); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[i]"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: [2, 2, 2]}")); 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("{}"), getLogDoc()); } TEST_F(UpdateArrayNodeTest, UpdatesToAllArrayElementsAreNoops) { auto update = fromjson("{$set: {'a.$[i]': 1}}"); auto arrayFilter = fromjson("{'i': 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx )); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[i]"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: [1, 1, 1]}")); 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("{}"), getLogDoc()); } TEST_F(UpdateArrayNodeTest, NoArrayElementAffectsIndexes) { auto update = fromjson("{$set: {'a.$[i].b': 0}}"); auto arrayFilter = fromjson("{'i.c': 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx )); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set 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: 0}]}")); 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}]}}"), getLogDoc()); } TEST_F(UpdateArrayNodeTest, WhenOneElementIsMatchedLogElementUpdateDirectly) { auto update = fromjson("{$set: {'a.$[i].b': 0}}"); auto arrayFilter = fromjson("{'i.c': 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx )); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set 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: 1}, {c: 0}, {c: 1}]}")); 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}}"), getLogDoc()); } TEST_F(UpdateArrayNodeTest, WhenOneElementIsModifiedLogElement) { auto update = fromjson("{$set: {'a.$[i].b': 0}}"); auto arrayFilter = fromjson("{'i.c': 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx )); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set 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, b: 0}, {c: 0}, {c: 1}]}")); 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}}}"), getLogDoc()); } TEST_F(UpdateArrayNodeTest, ArrayUpdateOnEmptyArrayIsANoop) { auto update = fromjson("{$set: {'a.$[]': 0}}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; std::set foundIdentifiers; UpdateObjectNode root; ASSERT_OK(UpdateObjectNode::parseAndMerge(&root, modifiertable::ModifierType::MOD_SET, update["$set"]["a.$[]"], expCtx, arrayFilters, foundIdentifiers)); mutablebson::Document doc(fromjson("{a: []}")); 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("{}"), getLogDoc()); } TEST_F(UpdateArrayNodeTest, ApplyPositionalInsideArrayUpdate) { auto update = fromjson("{$set: {'a.$[i].b.$': 1}}"); auto arrayFilter = fromjson("{'i.c': 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx )); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set 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: [{b: [0, 0], c: 0}]}")); 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}}"), getLogDoc()); } TEST_F(UpdateArrayNodeTest, ApplyArrayUpdateFromReplication) { auto update = fromjson("{$set: {'a.$[i].b': 1}}"); auto arrayFilter = fromjson("{'i': 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx )); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set 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: [0]}")); addIndexedPath("a"); setFromOplogApplication(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("{}"), getLogDoc()); } TEST_F(UpdateArrayNodeTest, ApplyArrayUpdateNotFromReplication) { auto update = fromjson("{$set: {'a.$[i].b': 1}}"); auto arrayFilter = fromjson("{'i': 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx )); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set 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: [0]}")); addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), AssertionException, ErrorCodes::PathNotViable, "Cannot create field 'b' in element {0: 0}"); } TEST_F(UpdateArrayNodeTest, ApplyArrayUpdateWithoutLogBuilderOrIndexData) { auto update = fromjson("{$set: {'a.$[i]': 1}}"); auto arrayFilter = fromjson("{'i': 0}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); std::map> arrayFilters; auto parsedFilter = assertGet(MatchExpressionParser::parse(arrayFilter, expCtx )); arrayFilters["i"] = assertGet(ExpressionWithPlaceholder::make(std::move(parsedFilter))); std::set 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]}")); 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()); } } // namespace } // namespace mongo