diff options
Diffstat (limited to 'src/mongo/db/update')
31 files changed, 829 insertions, 683 deletions
diff --git a/src/mongo/db/update/SConscript b/src/mongo/db/update/SConscript index 2a1475ccf45..2bbb89d7db5 100644 --- a/src/mongo/db/update/SConscript +++ b/src/mongo/db/update/SConscript @@ -72,9 +72,9 @@ env.Library( 'bit_node.cpp', 'compare_node.cpp', 'current_date_node.cpp', + 'modifier_node.cpp', 'modifier_table.cpp', 'object_replace_node.cpp', - 'path_creating_node.cpp', 'pop_node.cpp', 'pull_node.cpp', 'pullall_node.cpp', diff --git a/src/mongo/db/update/addtoset_node.cpp b/src/mongo/db/update/addtoset_node.cpp index 469669210ca..f0a43dca382 100644 --- a/src/mongo/db/update/addtoset_node.cpp +++ b/src/mongo/db/update/addtoset_node.cpp @@ -102,10 +102,8 @@ void AddToSetNode::setCollator(const CollatorInterface* collator) { deduplicate(_elements, _collator); } -PathCreatingNode::UpdateExistingElementResult AddToSetNode::updateExistingElement( - mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const { +ModifierNode::ModifyResult AddToSetNode::updateExistingElement( + mutablebson::Element* element, std::shared_ptr<FieldRef> elementPath) const { uassert(ErrorCodes::BadValue, str::stream() << "Cannot apply $addToSet to non-array field. Field named '" << element->getFieldName() @@ -130,7 +128,7 @@ PathCreatingNode::UpdateExistingElementResult AddToSetNode::updateExistingElemen } if (elementsToAdd.empty()) { - return UpdateExistingElementResult::kNoOp; + return ModifyResult::kNoOp; } for (auto&& elem : elementsToAdd) { @@ -138,7 +136,7 @@ PathCreatingNode::UpdateExistingElementResult AddToSetNode::updateExistingElemen invariantOK(element->pushBack(toAdd)); } - return UpdateExistingElementResult::kUpdated; + return ModifyResult::kNormalUpdate; } 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 c7b1ad6baf9..b5677caf169 100644 --- a/src/mongo/db/update/addtoset_node.h +++ b/src/mongo/db/update/addtoset_node.h @@ -28,7 +28,7 @@ #pragma once -#include "mongo/db/update/path_creating_node.h" +#include "mongo/db/update/modifier_node.h" #include "mongo/stdx/memory.h" namespace mongo { @@ -36,7 +36,7 @@ namespace mongo { /** * Represents the application of an $addToSet to the value at the end of a path. */ -class AddToSetNode : public PathCreatingNode { +class AddToSetNode : public ModifierNode { public: Status init(BSONElement modExpr, const CollatorInterface* collator) final; @@ -47,11 +47,14 @@ public: void setCollator(const CollatorInterface* collator) final; protected: - UpdateExistingElementResult updateExistingElement(mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const final; + ModifyResult updateExistingElement(mutablebson::Element* element, + std::shared_ptr<FieldRef> elementPath) const final; void setValueForNewElement(mutablebson::Element* element) const final; + bool allowCreation() const final { + return true; + } + private: // The array of elements to be added. std::vector<BSONElement> _elements; diff --git a/src/mongo/db/update/arithmetic_node.cpp b/src/mongo/db/update/arithmetic_node.cpp index 6525e714f4c..49dfc9165e6 100644 --- a/src/mongo/db/update/arithmetic_node.cpp +++ b/src/mongo/db/update/arithmetic_node.cpp @@ -73,10 +73,8 @@ Status ArithmeticNode::init(BSONElement modExpr, const CollatorInterface* collat return Status::OK(); } -PathCreatingNode::UpdateExistingElementResult ArithmeticNode::updateExistingElement( - mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const { +ModifierNode::ModifyResult ArithmeticNode::updateExistingElement( + mutablebson::Element* element, std::shared_ptr<FieldRef> elementPath) const { if (!element->isNumeric()) { mutablebson::Element idElem = mutablebson::findFirstChildNamed(element->getDocument().root(), "_id"); @@ -104,12 +102,12 @@ PathCreatingNode::UpdateExistingElementResult ArithmeticNode::updateExistingElem // 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)) { - return UpdateExistingElementResult::kNoOp; + return ModifyResult::kNoOp; } else { // This can fail if 'valueToSet' is not representable as a 64-bit integer. uassertStatusOK(element->setValueSafeNum(valueToSet)); - return UpdateExistingElementResult::kUpdated; + return ModifyResult::kNormalUpdate; } } diff --git a/src/mongo/db/update/arithmetic_node.h b/src/mongo/db/update/arithmetic_node.h index 46e2ba19069..14981e0778e 100644 --- a/src/mongo/db/update/arithmetic_node.h +++ b/src/mongo/db/update/arithmetic_node.h @@ -28,7 +28,7 @@ #pragma once -#include "mongo/db/update/path_creating_node.h" +#include "mongo/db/update/modifier_node.h" #include "mongo/stdx/memory.h" namespace mongo { @@ -36,7 +36,7 @@ namespace mongo { /** * Represents the application of $inc or $mul to the value at the end of a path. */ -class ArithmeticNode : public PathCreatingNode { +class ArithmeticNode : public ModifierNode { public: enum class ArithmeticOp { kAdd, kMultiply }; @@ -51,11 +51,14 @@ public: void setCollator(const CollatorInterface* collator) final {} protected: - UpdateExistingElementResult updateExistingElement(mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const final; + ModifyResult updateExistingElement(mutablebson::Element* element, + std::shared_ptr<FieldRef> elementPath) const final; void setValueForNewElement(mutablebson::Element* element) const final; + bool allowCreation() const final { + return true; + } + private: ArithmeticOp _op; BSONElement _val; diff --git a/src/mongo/db/update/array_culling_node.cpp b/src/mongo/db/update/array_culling_node.cpp index 166c1b21fa5..41389cdbed9 100644 --- a/src/mongo/db/update/array_culling_node.cpp +++ b/src/mongo/db/update/array_culling_node.cpp @@ -32,24 +32,15 @@ namespace mongo { -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( - applyParams.element, *(applyParams.pathToCreate), *(applyParams.pathTaken)); - - return ApplyResult::noopResult(); - } - - // This operation only applies to arrays +ModifierNode::ModifyResult ArrayCullingNode::updateExistingElement( + mutablebson::Element* element, std::shared_ptr<FieldRef> elementPath) const { + invariant(element->ok()); uassert(ErrorCodes::BadValue, "Cannot apply $pull to a non-array value", - applyParams.element.getType() == mongo::Array); + element->getType() == mongo::Array); size_t numRemoved = 0; - auto cursor = applyParams.element.leftChild(); + auto cursor = 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. @@ -61,51 +52,18 @@ UpdateNode::ApplyResult ArrayCullingNode::apply(ApplyParams applyParams) const { cursor = nextElement; } - if (numRemoved == 0) { - return ApplyResult::noopResult(); // Skip the index check, immutable path check, and - // logging steps. - } - - ApplyResult applyResult; - - // Determine if indexes are affected. - 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 : applyParams.immutablePaths) { - uassert(ErrorCodes::ImmutableField, - str::stream() << "Performing an update on the path '" - << applyParams.pathTaken->dottedField() - << "' would modify the immutable field '" - << immutablePath->dottedField() - << "'", - applyParams.pathTaken->commonPrefixSize(*immutablePath) < - std::min(applyParams.pathTaken->numParts(), immutablePath->numParts())); - } - - if (applyParams.logBuilder) { - auto& doc = applyParams.logBuilder->getDocument(); - auto logElement = doc.makeElementArray(applyParams.pathTaken->dottedField()); - - for (auto cursor = applyParams.element.leftChild(); cursor.ok(); - cursor = cursor.rightSibling()) { - dassert(cursor.hasValue()); - - auto copy = doc.makeElementWithNewFieldName(StringData(), cursor.getValue()); - uassert(ErrorCodes::InternalError, "could not create copy element", copy.ok()); - uassertStatusOK(logElement.pushBack(copy)); - } + return (numRemoved == 0) ? ModifyResult::kNoOp : ModifyResult::kNormalUpdate; +} - uassertStatusOK(applyParams.logBuilder->addToSets(logElement)); - } +void ArrayCullingNode::validateUpdate(mutablebson::ConstElement updatedElement, + mutablebson::ConstElement leftSibling, + mutablebson::ConstElement rightSibling, + std::uint32_t recursionLevel, + ModifyResult modifyResult) const { + invariant(modifyResult == ModifyResult::kNormalUpdate); - return applyResult; + // Removing elements from an array cannot increase BSON depth or modify a DBRef, so we can + // override validateUpdate to do nothing. } } // namespace mongo diff --git a/src/mongo/db/update/array_culling_node.h b/src/mongo/db/update/array_culling_node.h index cad10143f7e..97b88aefce9 100644 --- a/src/mongo/db/update/array_culling_node.h +++ b/src/mongo/db/update/array_culling_node.h @@ -29,7 +29,7 @@ #pragma once #include "mongo/base/clonable_ptr.h" -#include "mongo/db/update/update_leaf_node.h" +#include "mongo/db/update/modifier_node.h" #include "mongo/stdx/memory.h" namespace mongo { @@ -41,9 +41,16 @@ namespace mongo { * subclass-supplied ElementMatcher to determine which array elements should be removed. The init * method for each subclass must populate _matcher with an ElementMatcher implementation. */ -class ArrayCullingNode : public UpdateLeafNode { +class ArrayCullingNode : public ModifierNode { public: - ApplyResult apply(ApplyParams applyParams) const final; + ModifyResult updateExistingElement(mutablebson::Element* element, + std::shared_ptr<FieldRef> elementPath) const final; + + void validateUpdate(mutablebson::ConstElement updatedElement, + mutablebson::ConstElement leftSibling, + mutablebson::ConstElement rightSibling, + std::uint32_t recursionLevel, + ModifyResult modifyResult) 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 26a920769f2..f2123d6324e 100644 --- a/src/mongo/db/update/bit_node.cpp +++ b/src/mongo/db/update/bit_node.cpp @@ -89,10 +89,8 @@ Status BitNode::init(BSONElement modExpr, const CollatorInterface* collator) { return Status::OK(); } -PathCreatingNode::UpdateExistingElementResult BitNode::updateExistingElement( - mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const { +ModifierNode::ModifyResult BitNode::updateExistingElement( + mutablebson::Element* element, std::shared_ptr<FieldRef> elementPath) const { if (!element->isIntegral()) { mutablebson::Element idElem = mutablebson::findFirstChildNamed(element->getDocument().root(), "_id"); @@ -109,9 +107,9 @@ PathCreatingNode::UpdateExistingElementResult BitNode::updateExistingElement( if (!value.isIdentical(element->getValueSafeNum())) { invariantOK(element->setValueSafeNum(value)); - return UpdateExistingElementResult::kUpdated; + return ModifyResult::kNormalUpdate; } else { - return UpdateExistingElementResult::kNoOp; + return ModifyResult::kNoOp; } } diff --git a/src/mongo/db/update/bit_node.h b/src/mongo/db/update/bit_node.h index 3edc84179a7..2f23d9a53df 100644 --- a/src/mongo/db/update/bit_node.h +++ b/src/mongo/db/update/bit_node.h @@ -28,7 +28,7 @@ #pragma once -#include "mongo/db/update/path_creating_node.h" +#include "mongo/db/update/modifier_node.h" #include "mongo/stdx/memory.h" namespace mongo { @@ -36,7 +36,7 @@ namespace mongo { /** * Represents the application of a $bit to the value at the end of a path. */ -class BitNode : public PathCreatingNode { +class BitNode : public ModifierNode { public: Status init(BSONElement modExpr, const CollatorInterface* collator) final; @@ -47,11 +47,14 @@ public: void setCollator(const CollatorInterface* collator) final {} protected: - UpdateExistingElementResult updateExistingElement(mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const final; + ModifyResult updateExistingElement(mutablebson::Element* element, + std::shared_ptr<FieldRef> elementPath) const final; void setValueForNewElement(mutablebson::Element* element) const final; + bool allowCreation() const final { + return true; + } + private: /** * Applies each op in "_opList" to "value" and returns the result. diff --git a/src/mongo/db/update/compare_node.cpp b/src/mongo/db/update/compare_node.cpp index 166499f40f9..744a452002b 100644 --- a/src/mongo/db/update/compare_node.cpp +++ b/src/mongo/db/update/compare_node.cpp @@ -46,16 +46,14 @@ void CompareNode::setCollator(const CollatorInterface* collator) { _collator = collator; } -PathCreatingNode::UpdateExistingElementResult CompareNode::updateExistingElement( - mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const { +ModifierNode::ModifyResult CompareNode::updateExistingElement( + mutablebson::Element* element, std::shared_ptr<FieldRef> elementPath) const { const auto compareVal = element->compareWithBSONElement(_val, _collator, false); if ((compareVal == 0) || ((_mode == CompareMode::kMax) ? (compareVal > 0) : (compareVal < 0))) { - return UpdateExistingElementResult::kNoOp; + return ModifyResult::kNoOp; } else { invariantOK(element->setValueBSONElement(_val)); - return UpdateExistingElementResult::kUpdated; + return ModifyResult::kNormalUpdate; } } diff --git a/src/mongo/db/update/compare_node.h b/src/mongo/db/update/compare_node.h index 11421a08eb5..6fe9cbd3084 100644 --- a/src/mongo/db/update/compare_node.h +++ b/src/mongo/db/update/compare_node.h @@ -28,7 +28,7 @@ #pragma once -#include "mongo/db/update/path_creating_node.h" +#include "mongo/db/update/modifier_node.h" #include "mongo/stdx/memory.h" namespace mongo { @@ -36,7 +36,7 @@ namespace mongo { /** * Represents the application of $max or $min to the value at the end of a path. */ -class CompareNode : public PathCreatingNode { +class CompareNode : public ModifierNode { public: enum class CompareMode { kMax, kMin }; @@ -51,11 +51,18 @@ public: void setCollator(const CollatorInterface* collator) final; protected: - UpdateExistingElementResult updateExistingElement(mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const final; + ModifyResult updateExistingElement(mutablebson::Element* element, + std::shared_ptr<FieldRef> elementPath) const final; void setValueForNewElement(mutablebson::Element* element) const final; + bool allowCreation() const final { + return true; + } + + bool canSetObjectValue() const final { + return true; + } + private: CompareMode _mode; BSONElement _val; diff --git a/src/mongo/db/update/current_date_node.cpp b/src/mongo/db/update/current_date_node.cpp index a251ad14854..4d6075a65f7 100644 --- a/src/mongo/db/update/current_date_node.cpp +++ b/src/mongo/db/update/current_date_node.cpp @@ -93,12 +93,10 @@ Status CurrentDateNode::init(BSONElement modExpr, const CollatorInterface* colla return Status::OK(); } -PathCreatingNode::UpdateExistingElementResult CurrentDateNode::updateExistingElement( - mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const { +ModifierNode::ModifyResult CurrentDateNode::updateExistingElement( + mutablebson::Element* element, std::shared_ptr<FieldRef> elementPath) const { setValue(element, _typeIsDate); - return UpdateExistingElementResult::kUpdated; + return ModifyResult::kNormalUpdate; } 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 e35402da37b..bac2eb5c857 100644 --- a/src/mongo/db/update/current_date_node.h +++ b/src/mongo/db/update/current_date_node.h @@ -28,7 +28,7 @@ #pragma once -#include "mongo/db/update/path_creating_node.h" +#include "mongo/db/update/modifier_node.h" #include "mongo/stdx/memory.h" namespace mongo { @@ -36,7 +36,7 @@ namespace mongo { /** * Represents the application of a $currentDate to the value at the end of a path. */ -class CurrentDateNode : public PathCreatingNode { +class CurrentDateNode : public ModifierNode { public: Status init(BSONElement modExpr, const CollatorInterface* collator) final; @@ -47,11 +47,14 @@ public: void setCollator(const CollatorInterface* collator) final {} protected: - UpdateExistingElementResult updateExistingElement(mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const final; + ModifyResult updateExistingElement(mutablebson::Element* element, + std::shared_ptr<FieldRef> elementPath) const final; void setValueForNewElement(mutablebson::Element* element) const final; + bool allowCreation() const final { + return true; + } + private: // If true, the current date should be expressed as a Date. If false, a Timestamp. bool _typeIsDate; diff --git a/src/mongo/db/update/modifier_node.cpp b/src/mongo/db/update/modifier_node.cpp new file mode 100644 index 00000000000..7de8ff73ac5 --- /dev/null +++ b/src/mongo/db/update/modifier_node.cpp @@ -0,0 +1,336 @@ +/** + * 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/update/modifier_node.h" + +#include "mongo/db/bson/dotted_path_support.h" +#include "mongo/db/update/path_support.h" +#include "mongo/db/update/storage_validation.h" + +namespace mongo { + +namespace { + +/** + * Checks that no immutable paths were modified in the case where we are modifying an existing path + * in the document. + * + * This check does not make assumptions about how 'element' was modified; it explicitly checks + * immutable fields in 'element' to see if they differ from the 'original' value. Consider an + * updating the document {a: {b: 1, c: 1}} with {$set: {a: {b: 1, d: 1}}} where 'a.b' is an + * immutable path. Even though we've overwritten the immutable field, it has the same value, and the + * update is allowed. + * + * 'element' should be the modified element. 'pathTaken' is the path to the modified element. + * 'original' should be provided as the preimage of the whole document. We _do_ assume that we have + * already checked the update is not a noop. + */ +void checkImmutablePathsNotModifiedFromOriginal(mutablebson::Element element, + FieldRef* pathTaken, + const FieldRefSet& immutablePaths, + BSONObj original) { + for (auto immutablePath = immutablePaths.begin(); immutablePath != immutablePaths.end(); + ++immutablePath) { + auto prefixSize = pathTaken->commonPrefixSize(**immutablePath); + + // If 'immutablePath' is a (strict or non-strict) prefix of 'pathTaken', and the update is + // not a noop, then we have modified 'immutablePath', which is immutable. + if (prefixSize == (*immutablePath)->numParts()) { + uasserted(ErrorCodes::ImmutableField, + str::stream() << "Updating the path '" << pathTaken->dottedField() << "' to " + << element.toString() + << " would modify the immutable field '" + << (*immutablePath)->dottedField() + << "'"); + } + + // If 'pathTaken' is a strict prefix of 'immutablePath', then we may have modified + // 'immutablePath'. We already know that 'pathTaken' is not equal to 'immutablePath', or we + // would have uasserted. + if (prefixSize == pathTaken->numParts()) { + auto oldElem = dotted_path_support::extractElementAtPath( + original, (*immutablePath)->dottedField()); + + // We are allowed to modify immutable paths that do not yet exist. + if (!oldElem.ok()) { + continue; + } + + auto newElem = element; + for (size_t i = pathTaken->numParts(); i < (*immutablePath)->numParts(); ++i) { + uassert(ErrorCodes::NotSingleValueField, + str::stream() + << "After applying the update to the document, the immutable field '" + << (*immutablePath)->dottedField() + << "' was found to be an array or array descendant.", + newElem.getType() != BSONType::Array); + newElem = newElem[(*immutablePath)->getPart(i)]; + if (!newElem.ok()) { + break; + } + } + + uassert(ErrorCodes::ImmutableField, + str::stream() << "After applying the update, the immutable field '" + << (*immutablePath)->dottedField() + << "' was found to have been removed.", + newElem.ok()); + uassert(ErrorCodes::ImmutableField, + str::stream() << "After applying the update, the immutable field '" + << (*immutablePath)->dottedField() + << "' was found to have been altered to " + << newElem.toString(), + newElem.compareWithBSONElement(oldElem, nullptr, false) == 0); + } + } +} + +/** + * Checks that no immutable paths were modified in the case where we are modifying an existing path + * in the document. + * + * Unlike checkImmutablePathsNotModifiedFromOriginal(), this function does not check the original + * document. It always assumes that an update where 'pathTaken' is a prefix of any immutable path or + * vice versa is modifying an immutable path. This assumption is valid when we already know that + * 'pathTaken' is not a prefix of any immutable path or when the update is to a primitive value or + * array. (Immutable paths cannot include array elements.) + * + * See the comment above checkImmutablePathNotModifiedFromOriginal() for an example where that + * assumption does not apply. + * + * 'element' should be the modified element. 'pathTaken' is the path to the modified element. We + * assume that we have already checked the update is not a noop. + */ +void checkImmutablePathsNotModified(mutablebson::Element element, + FieldRef* pathTaken, + const FieldRefSet& immutablePaths) { + for (auto immutablePath = immutablePaths.begin(); immutablePath != immutablePaths.end(); + ++immutablePath) { + uassert(ErrorCodes::ImmutableField, + str::stream() << "Performing an update on the path '" << pathTaken->dottedField() + << "' would modify the immutable field '" + << (*immutablePath)->dottedField() + << "'", + pathTaken->commonPrefixSize(**immutablePath) < + std::min(pathTaken->numParts(), (*immutablePath)->numParts())); + } +} + +} // namespace + +UpdateNode::ApplyResult ModifierNode::applyToExistingElement(ApplyParams applyParams) const { + invariant(!applyParams.pathTaken->empty()); + invariant(applyParams.pathToCreate->empty()); + invariant(applyParams.element.ok()); + + mutablebson::ConstElement leftSibling = applyParams.element.leftSibling(); + mutablebson::ConstElement rightSibling = applyParams.element.rightSibling(); + + bool compareWithOriginal = false; + if (canSetObjectValue()) { + for (auto immutablePath = applyParams.immutablePaths.begin(); + immutablePath != applyParams.immutablePaths.end(); + ++immutablePath) { + if (applyParams.pathTaken->isPrefixOf(**immutablePath)) { + compareWithOriginal = true; + break; + } + } + } + + // We have two different ways of checking for changes to immutable paths, depending on the style + // of update. See the comments above checkImmutablePathsNotModifiedFromOriginal() and + // checkImmutablePathsNotModified(). + ModifyResult updateResult; + if (compareWithOriginal) { + BSONObj original = applyParams.element.getDocument().getObject(); + updateResult = updateExistingElement(&applyParams.element, applyParams.pathTaken); + if (updateResult == ModifyResult::kNoOp) { + return ApplyResult::noopResult(); + } + checkImmutablePathsNotModifiedFromOriginal( + applyParams.element, applyParams.pathTaken.get(), applyParams.immutablePaths, original); + } else { + updateResult = updateExistingElement(&applyParams.element, applyParams.pathTaken); + if (updateResult == ModifyResult::kNoOp) { + return ApplyResult::noopResult(); + } + checkImmutablePathsNotModified( + applyParams.element, applyParams.pathTaken.get(), applyParams.immutablePaths); + } + invariant(updateResult != ModifyResult::kCreated); + + ApplyResult applyResult; + + if (!applyParams.indexData || + !applyParams.indexData->mightBeIndexed(applyParams.pathTaken->dottedField())) { + applyResult.indexesAffected = false; + } + + if (applyParams.validateForStorage) { + const uint32_t recursionLevel = applyParams.pathTaken->numParts(); + validateUpdate( + applyParams.element, leftSibling, rightSibling, recursionLevel, updateResult); + } + + if (applyParams.logBuilder) { + logUpdate(applyParams.logBuilder, + applyParams.pathTaken->dottedField(), + applyParams.element, + updateResult); + } + + return applyResult; +} + +UpdateNode::ApplyResult ModifierNode::applyToNonexistentElement(ApplyParams applyParams) const { + if (allowCreation()) { + 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( + *(applyParams.pathToCreate), 0, applyParams.element, newElement); + if (!statusWithFirstCreatedElem.isOK()) { + // $set operaions 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 applied. For example, consider applying the following updates twice to an + // initially empty document: + // {$set: {c: 0}} + // {$set: {'a.b': 0, c: 1}} + // {$set: {a: 0}} + // Setting 'a.b' will fail the second time, but we must still set 'c'. + // (There are modifiers besides $set that use this code path, but they are not used for + // replication, so we are not concerned with their behavior when "fromReplication" is + // true.) + if (statusWithFirstCreatedElem.getStatus().code() == ErrorCodes::PathNotViable && + applyParams.fromReplication) { + return ApplyResult::noopResult(); + } + uassertStatusOK(statusWithFirstCreatedElem); + MONGO_UNREACHABLE; // The previous uassertStatusOK should always throw. + } + + if (applyParams.validateForStorage) { + const uint32_t recursionLevel = applyParams.pathTaken->numParts() + 1; + mutablebson::ConstElement elementForValidation = statusWithFirstCreatedElem.getValue(); + validateUpdate(elementForValidation, + elementForValidation.leftSibling(), + elementForValidation.rightSibling(), + recursionLevel, + ModifyResult::kCreated); + } + + 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'. + // (Note that this behavior is subtly different from checkImmutablePathsNotModified(), + // because we just created this element.) + uassert(ErrorCodes::ImmutableField, + str::stream() << "Updating the path '" << applyParams.pathTaken->dottedField() + << "' to " + << applyParams.element.toString() + << " would modify the immutable field '" + << (*immutablePath)->dottedField() + << "'", + applyParams.pathTaken->commonPrefixSize(**immutablePath) != + (*immutablePath)->numParts()); + } + + invariant(!applyParams.pathToCreate->empty()); + std::string fullPath; + if (applyParams.pathTaken->empty()) { + fullPath = applyParams.pathToCreate->dottedField().toString(); + } else { + fullPath = str::stream() << applyParams.pathTaken->dottedField() << "." + << applyParams.pathToCreate->dottedField(); + } + + ApplyResult applyResult; + + // Determine if indexes are affected. + if (!applyParams.indexData || !applyParams.indexData->mightBeIndexed(fullPath)) { + applyResult.indexesAffected = false; + } + + if (applyParams.logBuilder) { + logUpdate(applyParams.logBuilder, fullPath, newElement, ModifyResult::kCreated); + } + + return applyResult; + } else { + // This path is for modifiers like $pop or $pull that generally have no effect when applied + // to a path that does not exist. + if (!allowNonViablePath()) { + // One exception: some of these modifiers still fail when the nonexistent path is + // "non-viable," meaning it couldn't be created even if we intended to. + UpdateLeafNode::checkViability( + applyParams.element, *(applyParams.pathToCreate), *(applyParams.pathTaken)); + } + + return ApplyResult::noopResult(); + } +} + +UpdateNode::ApplyResult ModifierNode::apply(ApplyParams applyParams) const { + if (context == Context::kInsertOnly && !applyParams.insert) { + return ApplyResult::noopResult(); + } else if (!applyParams.pathToCreate->empty()) { + return applyToNonexistentElement(applyParams); + } else { + return applyToExistingElement(applyParams); + } +} + +void ModifierNode::validateUpdate(mutablebson::ConstElement updatedElement, + mutablebson::ConstElement leftSibling, + mutablebson::ConstElement rightSibling, + std::uint32_t recursionLevel, + ModifyResult modifyResult) const { + const bool doRecursiveCheck = true; + storage_validation::storageValid(updatedElement, doRecursiveCheck, recursionLevel); +} + +void ModifierNode::logUpdate(LogBuilder* logBuilder, + StringData pathTaken, + mutablebson::Element element, + ModifyResult modifyResult) const { + invariant(logBuilder); + invariant(modifyResult == ModifyResult::kNormalUpdate || + modifyResult == ModifyResult::kCreated); + uassertStatusOK(logBuilder->addToSetsWithNewFieldName(pathTaken, element)); +} + +} // namespace mongo diff --git a/src/mongo/db/update/modifier_node.h b/src/mongo/db/update/modifier_node.h new file mode 100644 index 00000000000..638a00f3014 --- /dev/null +++ b/src/mongo/db/update/modifier_node.h @@ -0,0 +1,175 @@ +/** + * 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/update/update_leaf_node.h" +#include "mongo/stdx/memory.h" + +namespace mongo { + +/** + * The apply() function for an update modifier must + * 1) update the element at the update path if it exists; + * 2) raise a user error, indicate a no-op, or create and initialize a new element if the update + * path does not exist, depending on the specific modifier semantics; + * 3) check for any changes that modify an immutable path; + * 4) determine whether indexes may be affected; + * 5) validate that the updated document is valid for storage; and + * 6) create an oplog entry for the update. + * ModifierNode provides a generic implementation of the apply() function that does all this heavy + * lifting and calls out to modifier-specific overrides that customize apply() behavior where + * necessary. + */ +class ModifierNode : public UpdateLeafNode { +public: + explicit ModifierNode(Context context = Context::kAll) : UpdateLeafNode(context) {} + + ApplyResult apply(ApplyParams applyParams) const final; + +protected: + enum class ModifyResult { + // No log entry is necessary for no-op updates. + kNoOp, + + // The update can be logged as normal (usually with a $set on the entire element). + kNormalUpdate, + + // The element is an array, and the update only appends new array items to the end. The + // update can be logged as a $set on each appended element, rather than including the entire + // array in the log entry. + kArrayAppendUpdate, + + // The element did not exist, so it was created then populated with setValueForNewElement(). + // The updateExistingElement() method should never return this value. + kCreated + }; + + /** + * ModifierNode::apply() calls this method when applying an update to an existing path. The + * child's implementation of this method is responsible for updating 'element' and indicating + * what kind of update was performed in its return value. + */ + virtual ModifyResult updateExistingElement(mutablebson::Element* element, + std::shared_ptr<FieldRef> elementPath) const = 0; + + /** + * ModifierNode::apply() calls this method when applying an update to a path that does not yet + * exist and should be created. The child's implementation of this method is responsible for + * assigning a value to the new element, which will initially be null. + * + * This method only gets called when the child class implementation of allowCreation() returns + * true. ModiferNode child classes should override setValueForNewElement() iff allowCreation() + * returns true. + */ + virtual void setValueForNewElement(mutablebson::Element* element) const { + // Only implementations that return true for allowCreation() will override this method. + MONGO_UNREACHABLE; + }; + + /** + * ModifierNode::apply() calls this method after it finishes applying its update to validate + * that no changes resulted in an invalid document. See the implementation of + * storage_validation::storageValid() for more detail about document validation requirements. + * Most ModifierNode child classes can use the default implementation of this method. + * + * - 'updatedElement' is the element that was set by either updateExistingElement() or + * setValueForNewElement(). + * - 'leftSibling' and 'rightSibling' are the left and right siblings of 'updatedElement' or + * are invalid if 'updatedElement' is the first or last element in its object, respectively. + * If a ModifierNode deletes an element as part of its update (as $unset does), + * 'updatedElement' will be invalid, but validateUpdate() can use the siblings to perform + * validation instead. + * - 'recursionLevel' is the document nesting depth of the 'updatedElement' field. + * - 'modifyResult' is either the value returned by updateExistingElement() or the value + * ModifyResult::kCreated. + */ + virtual void validateUpdate(mutablebson::ConstElement updatedElement, + mutablebson::ConstElement leftSibling, + mutablebson::ConstElement rightSibling, + std::uint32_t recursionLevel, + ModifyResult modifyResult) const; + + /** + * ModifierNode::apply() calls this method after validation to create an oplog entry for the + * applied update. Most ModifierNode child classes can use the default implementation of this + * method, which creates a $set entry using 'pathTaken' for the path and 'element' for the + * value. + * + * - 'logBuilder' provides the interface for appending log entries. + * - 'pathTaken' is the path of the applied update. + * - 'element' is the element that was set by either updateExistingElement() or + * setValueForNewElement(). + * - 'modifyResult' is either the value returned by updateExistingElement() or the value + * ModifyResult::kCreated. + */ + virtual void logUpdate(LogBuilder* logBuilder, + StringData pathTaken, + mutablebson::Element element, + ModifyResult modifyResult) const; + + /** + * ModifierNode::apply() calls this method to determine what to do when applying an update to a + * path that does not exist. If the child class overrides this method to return true, + * ModifierNode::apply() will create a new element at the path and call setValueForNewElement() + * on it. Child classes must implement setValueForNewElement() iff this function returns true. + */ + virtual bool allowCreation() const { + return false; + } + + + /** + * When allowCreation() returns false, ModiferNode::apply() calls this method when determining + * if an update to a non-existent path is a no-op or an error. When allowNonViablePath() is + * false, an update to a path that could be created (i.e. a "viable" path) is a no-op, and an + * update to a path that could not be created results in a PathNotViable user error. When + * allowNonViablePath() is true, there is no viabilty check, and any update to a nonexistent + * path is a no-op. + */ + virtual bool allowNonViablePath() const { + return false; + } + + /** + * When it is possible for updateExistingElement to replace the element's contents with an + * object value (e.g., {$set: {a: {c: 1}}} applied to the document {a: {b:1}}), + * ModifierNode::apply() has to do extra work to determine if any immutable paths have been + * modified. Any ModifierNode child class that can perform such an update should override this + * method to return true, so that ModifierNode::apply() knows to do the extra checks. + */ + virtual bool canSetObjectValue() const { + return false; + } + +private: + ApplyResult applyToNonexistentElement(ApplyParams applyParams) const; + ApplyResult applyToExistingElement(ApplyParams applyParams) const; +}; + +} // namespace mongo diff --git a/src/mongo/db/update/path_creating_node.cpp b/src/mongo/db/update/path_creating_node.cpp deleted file mode 100644 index 155f41029cd..00000000000 --- a/src/mongo/db/update/path_creating_node.cpp +++ /dev/null @@ -1,236 +0,0 @@ -/** - * 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. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/db/update/path_creating_node.h" - -#include "mongo/db/bson/dotted_path_support.h" -#include "mongo/db/update/path_support.h" -#include "mongo/db/update/storage_validation.h" - -namespace mongo { - -namespace { - -/** - * Checks that no immutable paths were modified in the case where we are modifying an existing path - * in the document. 'element' should be the modified element. 'pathTaken' is the path to the - * modified element. If 'pathTaken' is a strict prefix of any immutable path, 'original' should be - * provided as the preimage of the whole document. We assume that we have already checked the update - * is not a noop. - */ -void checkImmutablePathsNotModified(mutablebson::Element element, - FieldRef* pathTaken, - const FieldRefSet& immutablePaths, - BSONObj original) { - for (auto immutablePath = immutablePaths.begin(); immutablePath != immutablePaths.end(); - ++immutablePath) { - auto prefixSize = pathTaken->commonPrefixSize(**immutablePath); - - // If 'immutablePath' is a (strict or non-strict) prefix of 'pathTaken', and the update is - // not a noop, then we have modified 'immutablePath', which is immutable. - if (prefixSize == (*immutablePath)->numParts()) { - uasserted(ErrorCodes::ImmutableField, - str::stream() << "Updating the path '" << pathTaken->dottedField() << "' to " - << element.toString() - << " would modify the immutable field '" - << (*immutablePath)->dottedField() - << "'"); - } - - // If 'pathTaken' is a strict prefix of 'immutablePath', then we may have modified - // 'immutablePath'. We already know that 'pathTaken' is not equal to 'immutablePath', or we - // would have uasserted. - if (prefixSize == pathTaken->numParts()) { - auto oldElem = dotted_path_support::extractElementAtPath( - original, (*immutablePath)->dottedField()); - - // We are allowed to modify immutable paths that do not yet exist. - if (!oldElem.ok()) { - continue; - } - - auto newElem = element; - for (size_t i = pathTaken->numParts(); i < (*immutablePath)->numParts(); ++i) { - uassert(ErrorCodes::NotSingleValueField, - str::stream() - << "After applying the update to the document, the immutable field '" - << (*immutablePath)->dottedField() - << "' was found to be an array or array descendant.", - newElem.getType() != BSONType::Array); - newElem = newElem[(*immutablePath)->getPart(i)]; - if (!newElem.ok()) { - break; - } - } - - uassert(ErrorCodes::ImmutableField, - str::stream() << "After applying the update, the immutable field '" - << (*immutablePath)->dottedField() - << "' was found to have been removed.", - newElem.ok()); - uassert(ErrorCodes::ImmutableField, - str::stream() << "After applying the update, the immutable field '" - << (*immutablePath)->dottedField() - << "' was found to have been altered to " - << newElem.toString(), - newElem.compareWithBSONElement(oldElem, nullptr, false) == 0); - } - } -} - -} // namespace - -UpdateNode::ApplyResult PathCreatingNode::apply(ApplyParams applyParams) const { - if (context == Context::kInsertOnly && !applyParams.insert) { - return ApplyResult::noopResult(); - } - - // The value in this Element gets used to create a logging entry (if we have a LogBuilder). - mutablebson::Element valueToLog = applyParams.element.getDocument().end(); - - 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 = applyParams.immutablePaths.begin(); - immutablePath != applyParams.immutablePaths.end(); - ++immutablePath) { - if (applyParams.pathTaken->isPrefixOf(**immutablePath)) { - original = applyParams.element.getDocument().getObject(); - break; - } - } - - // We found an existing element at the update path. - auto updateResult = updateExistingElement( - &applyParams.element, applyParams.pathTaken, applyParams.logBuilder); - if (updateResult == UpdateExistingElementResult::kNoOp) { - return ApplyResult::noopResult(); // Successful no-op update. - } - - if (applyParams.validateForStorage) { - const bool doRecursiveCheck = true; - const uint32_t recursionLevel = applyParams.pathTaken->numParts(); - storage_validation::storageValid(applyParams.element, doRecursiveCheck, recursionLevel); - } - - checkImmutablePathsNotModified( - applyParams.element, applyParams.pathTaken.get(), applyParams.immutablePaths, original); - - if (updateResult == UpdateExistingElementResult::kUpdated) { - valueToLog = applyParams.element; - } else { - // updateExistingElement() has already performed logging, so we don't set a log value. - } - } else { - // We did not find an element at the update path. Create one. - 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( - *(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 - // applied. For example, consider applying the following updates twice to an initially - // empty document: - // {$set: {c: 0}} - // {$set: {'a.b': 0, c: 1}} - // {$set: {a: 0}} - // Setting 'a.b' will fail the second time, but we must still set 'c'. - // (There are modifiers besides $set that use this code path, but they are not used for - // replication, so we are not concerned with their behavior when "fromReplication" is - // true.) - if (statusWithFirstCreatedElem.getStatus().code() == ErrorCodes::PathNotViable && - applyParams.fromReplication) { - return ApplyResult::noopResult(); - } - uassertStatusOK(statusWithFirstCreatedElem); - MONGO_UNREACHABLE; // The previous uassertStatusOK should always throw. - } - - if (applyParams.validateForStorage) { - const bool doRecursiveCheck = true; - const uint32_t recursionLevel = applyParams.pathTaken->numParts() + 1; - storage_validation::storageValid( - statusWithFirstCreatedElem.getValue(), doRecursiveCheck, recursionLevel); - } - - 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 '" << applyParams.pathTaken->dottedField() - << "' to " - << applyParams.element.toString() - << " would modify the immutable field '" - << (*immutablePath)->dottedField() - << "'", - applyParams.pathTaken->commonPrefixSize(**immutablePath) != - (*immutablePath)->numParts()); - } - - valueToLog = newElement; - } - - // Create full field path of set element. - StringBuilder builder; - builder << applyParams.pathTaken->dottedField(); - if (!applyParams.pathTaken->empty() && !applyParams.pathToCreate->empty()) { - builder << "."; - } - builder << applyParams.pathToCreate->dottedField(); - auto fullPath = builder.str(); - - ApplyResult applyResult; - - // Determine if indexes are affected. - if (!applyParams.indexData || !applyParams.indexData->mightBeIndexed(fullPath)) { - applyResult.indexesAffected = false; - } - - // Log the operation. - if (applyParams.logBuilder && valueToLog.ok()) { - auto logElement = - applyParams.logBuilder->getDocument().makeElementWithNewFieldName(fullPath, valueToLog); - invariant(logElement.ok()); - 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 deleted file mode 100644 index c806a5791f4..00000000000 --- a/src/mongo/db/update/path_creating_node.h +++ /dev/null @@ -1,72 +0,0 @@ -/** - * 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/update/update_leaf_node.h" -#include "mongo/stdx/memory.h" - -namespace mongo { - -/** - * A PathCreatingNode represents an update modifier that materializes the field it wants to update - * when that field does not already exist. This category of modifiers includes $set, $inc, and $mul. - * These nodes are very similar in operation, so most of their logic is in the apply() method of - * this abstract base class, which calls into a virtual methods for modifier-specific functionality. - */ -class PathCreatingNode : public UpdateLeafNode { -public: - explicit PathCreatingNode(Context context = Context::kAll) : UpdateLeafNode(context) {} - - ApplyResult apply(ApplyParams applyParams) const final; - -protected: - enum class UpdateExistingElementResult { - kNoOp, - kUpdated, - kUpdatedAndLogged, // PathCreatingNode::apply() does not need to do any logging. - }; - - /** - * 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 returning false to indicate that no update is necessary. - */ - virtual UpdateExistingElementResult updateExistingElement(mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const = 0; - - /** - * PathCreatingNode::apply() calls the setValueForNewElement() method when it must materialize a - * new field in order to apply its update. The child's implemenation of this method is - * responsible for assigning a value to the new element (which will initially be null). - */ - virtual void setValueForNewElement(mutablebson::Element* element) const = 0; -}; - -} // namespace mongo diff --git a/src/mongo/db/update/pop_node.cpp b/src/mongo/db/update/pop_node.cpp index 3a862413853..96c495c29a5 100644 --- a/src/mongo/db/update/pop_node.cpp +++ b/src/mongo/db/update/pop_node.cpp @@ -47,73 +47,36 @@ Status PopNode::init(BSONElement modExpr, const CollatorInterface* collator) { return Status::OK(); } -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. - return ApplyResult::noopResult(); - } - - 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( - applyParams.element, *(applyParams.pathToCreate), *(applyParams.pathTaken)); - - return ApplyResult::noopResult(); - } - - 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(applyParams.element.ok()); +ModifierNode::ModifyResult PopNode::updateExistingElement( + mutablebson::Element* element, std::shared_ptr<FieldRef> elementPath) const { + invariant(element->ok()); uassert(ErrorCodes::TypeMismatch, - str::stream() << "Path '" << applyParams.pathTaken->dottedField() + str::stream() << "Path '" << elementPath->dottedField() << "' contains an element of non-array type '" - << typeName(applyParams.element.getType()) + << typeName(element->getType()) << "'", - applyParams.element.getType() == BSONType::Array); + element->getType() == BSONType::Array); - if (!applyParams.element.hasChildren()) { + if (!element->hasChildren()) { // The path exists and contains an array, but the array is empty. - return ApplyResult::noopResult(); + return ModifyResult::kNoOp; } - ApplyResult applyResult; - - if (!applyParams.indexData || - !applyParams.indexData->mightBeIndexed(applyParams.pathTaken->dottedField())) { - applyResult.indexesAffected = false; - } - - auto elementToRemove = - _popFromFront ? applyParams.element.leftChild() : applyParams.element.rightChild(); + auto elementToRemove = _popFromFront ? element->leftChild() : 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 = applyParams.immutablePaths.begin(); - immutablePath != applyParams.immutablePaths.end(); - ++immutablePath) { - uassert(ErrorCodes::ImmutableField, - str::stream() << "Performing a $pop on the path '" - << applyParams.pathTaken->dottedField() - << "' would modify the immutable field '" - << (*immutablePath)->dottedField() - << "'", - applyParams.pathTaken->commonPrefixSize(**immutablePath) < - std::min(applyParams.pathTaken->numParts(), (*immutablePath)->numParts())); - } + return ModifyResult::kNormalUpdate; +} - if (applyParams.logBuilder) { - uassertStatusOK(applyParams.logBuilder->addToSetsWithNewFieldName( - applyParams.pathTaken->dottedField(), applyParams.element)); - } +void PopNode::validateUpdate(mutablebson::ConstElement updatedElement, + mutablebson::ConstElement leftSibling, + mutablebson::ConstElement rightSibling, + std::uint32_t recursionLevel, + ModifyResult modifyResult) const { + invariant(modifyResult == ModifyResult::kNormalUpdate); - return applyResult; + // Removing elements from an array cannot increase BSON depth or modify a DBRef, so we can + // override validateUpdate to do nothing. } } // namespace mongo diff --git a/src/mongo/db/update/pop_node.h b/src/mongo/db/update/pop_node.h index 1cbdac4b1bc..2b8d99ce1e7 100644 --- a/src/mongo/db/update/pop_node.h +++ b/src/mongo/db/update/pop_node.h @@ -28,16 +28,23 @@ #pragma once -#include "mongo/db/update/update_leaf_node.h" +#include "mongo/db/update/modifier_node.h" #include "mongo/stdx/memory.h" namespace mongo { -class PopNode final : public UpdateLeafNode { +class PopNode final : public ModifierNode { public: Status init(BSONElement modExpr, const CollatorInterface* collator) final; - ApplyResult apply(ApplyParams applyParams) const final; + ModifyResult updateExistingElement(mutablebson::Element* element, + std::shared_ptr<FieldRef> elementPath) const final; + + void validateUpdate(mutablebson::ConstElement updatedElement, + mutablebson::ConstElement leftSibling, + mutablebson::ConstElement rightSibling, + std::uint32_t recursionLevel, + ModifyResult modifyResult) 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 40e0d38c36c..8057331fab5 100644 --- a/src/mongo/db/update/pop_node_test.cpp +++ b/src/mongo/db/update/pop_node_test.cpp @@ -344,7 +344,7 @@ TEST_F(PopNodeTest, ThrowsWhenPathIsImmutable) { popNode.apply(getApplyParams(doc.root()["a"]["b"])), AssertionException, ErrorCodes::ImmutableField, - "Performing a $pop on the path 'a.b' would modify the immutable field 'a.b'"); + "Performing an update on the path 'a.b' would modify the immutable field 'a.b'"); } TEST_F(PopNodeTest, ThrowsWhenPathIsPrefixOfImmutable) { @@ -366,7 +366,7 @@ TEST_F(PopNodeTest, ThrowsWhenPathIsPrefixOfImmutable) { popNode.apply(getApplyParams(doc.root()["a"])), AssertionException, ErrorCodes::ImmutableField, - "Performing a $pop on the path 'a' would modify the immutable field 'a.0'"); + "Performing an update on the path 'a' would modify the immutable field 'a.0'"); } TEST_F(PopNodeTest, ThrowsWhenPathIsSuffixOfImmutable) { @@ -383,7 +383,7 @@ TEST_F(PopNodeTest, ThrowsWhenPathIsSuffixOfImmutable) { popNode.apply(getApplyParams(doc.root()["a"]["b"])), AssertionException, ErrorCodes::ImmutableField, - "Performing a $pop on the path 'a.b' would modify the immutable field 'a'"); + "Performing an update on the path 'a.b' would modify the immutable field 'a'"); } TEST_F(PopNodeTest, NoopOnImmutablePathSucceeds) { diff --git a/src/mongo/db/update/push_node.cpp b/src/mongo/db/update/push_node.cpp index 0d4339c20a3..84748ae7caa 100644 --- a/src/mongo/db/update/push_node.cpp +++ b/src/mongo/db/update/push_node.cpp @@ -190,10 +190,10 @@ Status PushNode::init(BSONElement modExpr, const CollatorInterface* collator) { return Status::OK(); } -PushNode::PushResult PushNode::insertElementsWithPosition( - mutablebson::Element* array, long long position, const std::vector<BSONElement> valuesToPush) { +ModifierNode::ModifyResult PushNode::insertElementsWithPosition( + mutablebson::Element* array, long long position, const std::vector<BSONElement>& valuesToPush) { if (valuesToPush.empty()) { - return PushResult::kNoOp; + return ModifyResult::kNoOp; } auto& document = array->getDocument(); @@ -205,24 +205,24 @@ PushNode::PushResult PushNode::insertElementsWithPosition( // We insert the first element of 'valuesToPush' at the location requested in the 'position' // variable. - PushResult result; + ModifyResult result; if (arraySize == 0) { invariantOK(array->pushBack(firstElementToInsert)); - result = PushResult::kModifyArray; // See comment describing PushResult. + result = ModifyResult::kNormalUpdate; } else if (position > arraySize) { invariantOK(array->pushBack(firstElementToInsert)); - result = PushResult::kAppendToEndOfArray; + result = ModifyResult::kArrayAppendUpdate; } else if (position > 0) { auto insertAfter = getNthChild(*array, position - 1); invariantOK(insertAfter.addSiblingRight(firstElementToInsert)); - result = PushResult::kModifyArray; + result = ModifyResult::kNormalUpdate; } else if (position < 0 && -position < arraySize) { auto insertAfter = getNthChild(*array, arraySize - (-position) - 1); invariantOK(insertAfter.addSiblingRight(firstElementToInsert)); - result = PushResult::kModifyArray; + result = ModifyResult::kNormalUpdate; } else { invariantOK(array->pushFront(firstElementToInsert)); - result = PushResult::kModifyArray; + result = ModifyResult::kNormalUpdate; } // We insert all the rest of the elements after the one we just inserted. @@ -239,8 +239,8 @@ PushNode::PushResult PushNode::insertElementsWithPosition( return result; } -PushNode::PushResult PushNode::performPush(mutablebson::Element* element, - FieldRef* elementPath) const { +ModifierNode::ModifyResult PushNode::performPush(mutablebson::Element* element, + FieldRef* elementPath) const { if (element->getType() != BSONType::Array) { invariant(elementPath); // We can only hit this error if we are updating an existing path. auto idElem = mutablebson::findFirstChildNamed(element->getDocument().root(), "_id"); @@ -256,12 +256,12 @@ PushNode::PushResult PushNode::performPush(mutablebson::Element* element, auto result = insertElementsWithPosition(element, _position, _valuesToPush); if (_sort) { - result = PushResult::kModifyArray; + result = ModifyResult::kNormalUpdate; sortChildren(*element, *_sort); } while (static_cast<long long>(countChildren(*element)) > std::abs(_slice)) { - result = PushResult::kModifyArray; + result = ModifyResult::kNormalUpdate; if (_slice >= 0) { invariantOK(element->popBack()); } else { @@ -274,37 +274,37 @@ PushNode::PushResult PushNode::performPush(mutablebson::Element* element, return result; } -PathCreatingNode::UpdateExistingElementResult PushNode::updateExistingElement( - mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const { - auto originalSize = countChildren(*element); - - switch (performPush(element, elementPath.get())) { - case PushResult::kNoOp: - return UpdateExistingElementResult::kNoOp; - case PushResult::kAppendToEndOfArray: - if (logBuilder) { - // Special case: the only modification to the array is to add new elements, so we - // can log the update as a $set for each new element, rather than logging a $set - // with the entire array contents. - auto position = originalSize; - for (auto&& valueToLog : _valuesToPush) { - std::string positionString(str::stream() << position); - UpdateInternalNode::FieldRefTempAppend tempAppend(*elementPath, positionString); - uassertStatusOK(logBuilder->addToSetsWithNewFieldName( - elementPath->dottedField(), valueToLog)); - - ++position; - } - } +ModifierNode::ModifyResult PushNode::updateExistingElement( + mutablebson::Element* element, std::shared_ptr<FieldRef> elementPath) const { + return performPush(element, elementPath.get()); +} - // Indicate that PathCreatingNode::apply() does not need to do any more logging. - return UpdateExistingElementResult::kUpdatedAndLogged; - case PushResult::kModifyArray: - return UpdateExistingElementResult::kUpdated; - default: - MONGO_UNREACHABLE; +void PushNode::logUpdate(LogBuilder* logBuilder, + StringData pathTaken, + mutablebson::Element element, + ModifyResult modifyResult) const { + invariant(logBuilder); + + if (modifyResult == ModifyResult::kNormalUpdate || modifyResult == ModifyResult::kCreated) { + // Simple case: log the entires contents of the updated array. + uassertStatusOK(logBuilder->addToSetsWithNewFieldName(pathTaken, element)); + } else if (modifyResult == ModifyResult::kArrayAppendUpdate) { + // This update only modified the array by appending entries to the end. Rather than writing + // out the entire contents of the array, we create oplog entries for the newly appended + // elements. + auto numAppended = _valuesToPush.size(); + auto arraySize = countChildren(element); + + invariant(arraySize > numAppended); + auto position = arraySize - numAppended; + for (const auto& valueToLog : _valuesToPush) { + std::string pathToArrayElement(str::stream() << pathTaken << "." << position); + uassertStatusOK(logBuilder->addToSetsWithNewFieldName(pathToArrayElement, valueToLog)); + + ++position; + } + } else { + MONGO_UNREACHABLE; } } diff --git a/src/mongo/db/update/push_node.h b/src/mongo/db/update/push_node.h index 38784dc7365..66e93d03393 100644 --- a/src/mongo/db/update/push_node.h +++ b/src/mongo/db/update/push_node.h @@ -32,13 +32,13 @@ #include <limits> #include <vector> -#include "mongo/db/update/path_creating_node.h" +#include "mongo/db/update/modifier_node.h" #include "mongo/db/update/push_sorter.h" #include "mongo/stdx/memory.h" namespace mongo { -class PushNode final : public PathCreatingNode { +class PushNode final : public ModifierNode { public: PushNode() : _slice(std::numeric_limits<long long>::max()), @@ -57,27 +57,40 @@ public: } protected: - UpdateExistingElementResult updateExistingElement(mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const final; + ModifyResult updateExistingElement(mutablebson::Element* element, + std::shared_ptr<FieldRef> elementPath) const final; void setValueForNewElement(mutablebson::Element* element) const final; + void logUpdate(LogBuilder* logBuilder, + StringData pathTaken, + mutablebson::Element element, + ModifyResult modifyResult) const final; + + bool allowCreation() const final { + return true; + } + private: + // A helper for performPush(). + static ModifyResult insertElementsWithPosition(mutablebson::Element* array, + long long position, + const std::vector<BSONElement>& valuesToPush); + /** - * Used to describe the result of the PerformPush operation. Note that appending to any empty - * array is always considered kModifyArray. That's because we want $push onto an empty to array - * to trigger a log entry with a $set on the entire array. + * Inserts the elements from '_valuesToPush' in the 'element' array using '_position' to + * determine where to insert. This function also applies any '_slice' and or '_sort' that is + * specified. The return value of this function will indicate to logUpdate() what kind of oplog + * entries should be generated. + * + * Returns: + * - ModifyResult::kNoOp if '_valuesToPush' is empty and no slice or sort gets performed; + * - ModifyResult::kArrayAppendUpdate if the 'elements' array is initially non-empty, all + * inserted values are appended to the end, and no slice or sort gets performed; or + * - ModifyResult::kNormalUpdate if 'elements' is initially an empty array, values get + * inserted at the beginning or in the middle of the array, or a slice or sort gets + * performed. */ - enum class PushResult { - kNoOp, // The array is left exactly as it was. - kAppendToEndOfArray, // The only change to the array is items appended to the end. - kModifyArray // Any other modification of the array. - }; - - static PushResult insertElementsWithPosition(mutablebson::Element* array, - long long position, - const std::vector<BSONElement> valuesToPush); - PushResult performPush(mutablebson::Element* element, FieldRef* elementPath) const; + ModifyResult performPush(mutablebson::Element* element, FieldRef* elementPath) const; static const StringData kEachClauseName; static const StringData kSliceClauseName; diff --git a/src/mongo/db/update/rename_node.cpp b/src/mongo/db/update/rename_node.cpp index 0368314ba84..fc50de42567 100644 --- a/src/mongo/db/update/rename_node.cpp +++ b/src/mongo/db/update/rename_node.cpp @@ -32,9 +32,10 @@ #include "mongo/bson/mutable/algorithm.h" #include "mongo/db/update/field_checker.h" -#include "mongo/db/update/path_creating_node.h" +#include "mongo/db/update/modifier_node.h" #include "mongo/db/update/path_support.h" #include "mongo/db/update/storage_validation.h" +#include "mongo/db/update/unset_node.h" namespace mongo { @@ -45,11 +46,11 @@ namespace { * field to a destination field behaves logically like a $set on the destination followed by a * $unset on the source, and the first of those operations is executed by calling apply on a * SetElementNode object. We create a class for this purpose (rather than a stand-alone function) so - * that it can inherit from PathCreatingNode. + * that it can inherit from ModifierNode. * * Unlike SetNode, SetElementNode takes a mutablebson::Element as its input. */ -class SetElementNode : public PathCreatingNode { +class SetElementNode : public ModifierNode { public: SetElementNode(mutablebson::Element elemToSet) : _elemToSet(elemToSet) {} @@ -63,9 +64,9 @@ public: return Status::OK(); } - UpdateExistingElementResult updateExistingElement(mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const final { +protected: + ModifierNode::ModifyResult updateExistingElement( + mutablebson::Element* element, std::shared_ptr<FieldRef> elementPath) 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 @@ -74,9 +75,9 @@ public: auto considerFieldName = false; if (_elemToSet.compareWithElement(*element, comparator, considerFieldName) != 0) { invariantOK(element->setValueElement(_elemToSet)); - return UpdateExistingElementResult::kUpdated; + return ModifyResult::kNormalUpdate; } else { - return UpdateExistingElementResult::kNoOp; + return ModifyResult::kNoOp; } } @@ -84,6 +85,14 @@ public: invariantOK(element->setValueElement(_elemToSet)); } + bool allowCreation() const final { + return true; + } + + bool canSetObjectValue() const final { + return true; + } + private: mutablebson::Element _elemToSet; }; @@ -143,7 +152,7 @@ Status RenameNode::init(BSONElement modExpr, const CollatorInterface* collator) 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()); + auto fromFieldRef = std::make_shared<FieldRef>(_val.fieldName()); FieldRef toFieldRef(_val.valueStringData()); mutablebson::Document& document = applyParams.element.getDocument(); @@ -151,9 +160,9 @@ UpdateNode::ApplyResult RenameNode::apply(ApplyParams applyParams) const { size_t fromIdxFound; mutablebson::Element fromElement(document.end()); auto status = - pathsupport::findLongestPrefix(fromFieldRef, document.root(), &fromIdxFound, &fromElement); + pathsupport::findLongestPrefix(*fromFieldRef, document.root(), &fromIdxFound, &fromElement); - if (!status.isOK() || !fromElement.ok() || fromIdxFound != (fromFieldRef.numParts() - 1)) { + if (!status.isOK() || !fromElement.ok() || fromIdxFound != (fromFieldRef->numParts() - 1)) { // We could safely remove this restriction (thereby treating a rename with a non-viable // source path as a no-op), but most updates fail on an attempt to update a non-viable path, // so we throw an error for consistency. @@ -176,7 +185,7 @@ UpdateNode::ApplyResult RenameNode::apply(ApplyParams applyParams) const { auto idElem = mutablebson::findFirstChildNamed(document.root(), "_id"); uasserted(ErrorCodes::BadValue, str::stream() << "The source field cannot be an array element, '" - << fromFieldRef.dottedField() + << fromFieldRef->dottedField() << "' in doc with " << (idElem.ok() ? idElem.toString() : "no id") << " has an array field called '" @@ -206,56 +215,29 @@ UpdateNode::ApplyResult RenameNode::apply(ApplyParams applyParams) const { } } + // Once we've determined that the rename is valid and found the source element, the actual work + // gets broken out into a $set operation and an $unset operation. Note that, generally, we + // should call the init() method of a ModifierNode before calling its apply() method, but the + // init() methods of SetElementNode and UnsetNode don't do anything, so we can skip them. SetElementNode setElement(fromElement); auto setElementApplyResult = setElement.apply(applyParams); - auto leftSibling = fromElement.leftSibling(); - auto rightSibling = fromElement.rightSibling(); + ApplyParams unsetParams(applyParams); + unsetParams.element = fromElement; + unsetParams.pathToCreate = std::make_shared<FieldRef>(); + unsetParams.pathTaken = fromFieldRef; - invariant(fromElement.parent().ok()); - invariantOK(fromElement.remove()); + UnsetNode unsetElement; + auto unsetElementApplyResult = unsetElement.apply(unsetParams); + // Determine the final result based on the results of the $set and $unset. ApplyResult applyResult; + applyResult.indexesAffected = + setElementApplyResult.indexesAffected || unsetElementApplyResult.indexesAffected; - if (!applyParams.indexData || - (!setElementApplyResult.indexesAffected && - !applyParams.indexData->mightBeIndexed(fromFieldRef.dottedField()))) { - applyResult.indexesAffected = false; - } - - if (applyParams.validateForStorage) { - - // Validate the left and right sibling, in case this element was part of a DBRef. - if (leftSibling.ok()) { - const bool doRecursiveCheck = false; - const uint32_t recursionLevel = 0; - storage_validation::storageValid(leftSibling, doRecursiveCheck, recursionLevel); - } - - if (rightSibling.ok()) { - const bool doRecursiveCheck = false; - const uint32_t recursionLevel = 0; - storage_validation::storageValid(rightSibling, doRecursiveCheck, recursionLevel); - } - } - - // Ensure we are not changing any immutable paths. - for (auto immutablePath = applyParams.immutablePaths.begin(); - immutablePath != applyParams.immutablePaths.end(); - ++immutablePath) { - uassert(ErrorCodes::ImmutableField, - str::stream() << "Unsetting the path '" << fromFieldRef.dottedField() - << "' using $rename would modify the immutable field '" - << (*immutablePath)->dottedField() - << "'", - fromFieldRef.commonPrefixSize(**immutablePath) < - std::min(fromFieldRef.numParts(), (*immutablePath)->numParts())); - } - - // Log the $unset. The $set was already logged by SetElementNode::apply(). - if (applyParams.logBuilder) { - uassertStatusOK(applyParams.logBuilder->addToUnsets(fromFieldRef.dottedField())); - } + // The $unset would only be a no-op if the source element did not exist, in which case we would + // have exited early with a no-op result. + invariant(!unsetElementApplyResult.noop); return applyResult; } diff --git a/src/mongo/db/update/rename_node_test.cpp b/src/mongo/db/update/rename_node_test.cpp index 2267f51de6e..1ac25292bab 100644 --- a/src/mongo/db/update/rename_node_test.cpp +++ b/src/mongo/db/update/rename_node_test.cpp @@ -462,7 +462,7 @@ TEST_F(RenameNodeTest, ApplyCannotRemoveImmutablePath) { node.apply(getApplyParams(doc.root())), AssertionException, ErrorCodes::ImmutableField, - "Unsetting the path 'a.b' using $rename would modify the immutable field 'a.b'"); + "Performing an update on the path 'a.b' would modify the immutable field 'a.b'"); } TEST_F(RenameNodeTest, ApplyCannotRemovePrefixOfImmutablePath) { @@ -478,7 +478,7 @@ TEST_F(RenameNodeTest, ApplyCannotRemovePrefixOfImmutablePath) { node.apply(getApplyParams(doc.root())), AssertionException, ErrorCodes::ImmutableField, - "Unsetting the path 'a' using $rename would modify the immutable field 'a.b'"); + "Performing an update on the path 'a' would modify the immutable field 'a.b'"); } TEST_F(RenameNodeTest, ApplyCannotRemoveSuffixOfImmutablePath) { @@ -494,7 +494,7 @@ TEST_F(RenameNodeTest, ApplyCannotRemoveSuffixOfImmutablePath) { node.apply(getApplyParams(doc.root())), AssertionException, ErrorCodes::ImmutableField, - "Unsetting the path 'a.b.c' using $rename would modify the immutable field 'a.b'"); + "Performing an update on the path 'a.b.c' would modify the immutable field 'a.b'"); } TEST_F(RenameNodeTest, ApplyCanRemoveImmutablePathIfNoop) { @@ -543,7 +543,7 @@ TEST_F(RenameNodeTest, ApplyCannotOverwriteImmutablePath) { node.apply(getApplyParams(doc.root()["b"])), AssertionException, ErrorCodes::ImmutableField, - "Updating the path 'b' to b: 0 would modify the immutable field 'b'"); + "Performing an update on the path 'b' would modify the immutable field 'b'"); } } // namespace diff --git a/src/mongo/db/update/set_node.cpp b/src/mongo/db/update/set_node.cpp index f65eb5b234b..db107e32f39 100644 --- a/src/mongo/db/update/set_node.cpp +++ b/src/mongo/db/update/set_node.cpp @@ -42,17 +42,15 @@ Status SetNode::init(BSONElement modExpr, const CollatorInterface* collator) { return Status::OK(); } -PathCreatingNode::UpdateExistingElementResult SetNode::updateExistingElement( - mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const { +ModifierNode::ModifyResult SetNode::updateExistingElement( + mutablebson::Element* element, std::shared_ptr<FieldRef> elementPath) const { // If 'element' is deserialized, then element.getValue() will be EOO, which will never equal // _val. if (element->getValue().binaryEqualValues(_val)) { - return UpdateExistingElementResult::kNoOp; + return ModifyResult::kNoOp; } else { invariantOK(element->setValueBSONElement(_val)); - return UpdateExistingElementResult::kUpdated; + return ModifyResult::kNormalUpdate; } } diff --git a/src/mongo/db/update/set_node.h b/src/mongo/db/update/set_node.h index 754426330ff..3312042f2b2 100644 --- a/src/mongo/db/update/set_node.h +++ b/src/mongo/db/update/set_node.h @@ -28,7 +28,7 @@ #pragma once -#include "mongo/db/update/path_creating_node.h" +#include "mongo/db/update/modifier_node.h" #include "mongo/stdx/memory.h" namespace mongo { @@ -36,9 +36,9 @@ namespace mongo { /** * Represents the application of a $set to the value at the end of a path. */ -class SetNode : public PathCreatingNode { +class SetNode : public ModifierNode { public: - explicit SetNode(Context context = Context::kAll) : PathCreatingNode(context) {} + explicit SetNode(Context context = Context::kAll) : ModifierNode(context) {} Status init(BSONElement modExpr, const CollatorInterface* collator) final; @@ -49,11 +49,18 @@ public: void setCollator(const CollatorInterface* collator) final {} protected: - UpdateExistingElementResult updateExistingElement(mutablebson::Element* element, - std::shared_ptr<FieldRef> elementPath, - LogBuilder* logBuilder) const final; + ModifyResult updateExistingElement(mutablebson::Element* element, + std::shared_ptr<FieldRef> elementPath) const final; void setValueForNewElement(mutablebson::Element* element) const final; + bool allowCreation() const final { + return true; + } + + bool canSetObjectValue() const final { + return true; + } + private: BSONElement _val; }; diff --git a/src/mongo/db/update/set_node_test.cpp b/src/mongo/db/update/set_node_test.cpp index 792b7632c39..ec51b1b1e9c 100644 --- a/src/mongo/db/update/set_node_test.cpp +++ b/src/mongo/db/update/set_node_test.cpp @@ -1055,7 +1055,7 @@ TEST_F(SetNodeTest, ApplyCannotOverwriteImmutablePath) { node.apply(getApplyParams(doc.root()["a"]["b"])), AssertionException, ErrorCodes::ImmutableField, - "Updating the path 'a.b' to b: 1 would modify the immutable field 'a.b'"); + "Performing an update on the path 'a.b' would modify the immutable field 'a.b'"); } TEST_F(SetNodeTest, ApplyCanPerformNoopOnImmutablePath) { @@ -1179,7 +1179,7 @@ TEST_F(SetNodeTest, ApplyCannotOverwriteSuffixOfImmutablePath) { node.apply(getApplyParams(doc.root()["a"]["b"]["c"])), AssertionException, ErrorCodes::ImmutableField, - "Updating the path 'a.b.c' to c: 1 would modify the immutable field 'a.b'"); + "Performing an update on the path 'a.b.c' would modify the immutable field 'a.b'"); } TEST_F(SetNodeTest, ApplyCanPerformNoopOnSuffixOfImmutablePath) { diff --git a/src/mongo/db/update/unset_node.cpp b/src/mongo/db/update/unset_node.cpp index 243e3918027..5ff55a0bf64 100644 --- a/src/mongo/db/update/unset_node.cpp +++ b/src/mongo/db/update/unset_node.cpp @@ -40,70 +40,50 @@ Status UnsetNode::init(BSONElement modExpr, const CollatorInterface* collator) { return Status::OK(); } -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. - return ApplyResult::noopResult(); - } - - ApplyResult applyResult; - - // Determine if indexes are affected. - if (!applyParams.indexData || - !applyParams.indexData->mightBeIndexed(applyParams.pathTaken->dottedField())) { - applyResult.indexesAffected = false; - } - - auto parent = applyParams.element.parent(); - auto leftSibling = applyParams.element.leftSibling(); - auto rightSibling = applyParams.element.rightSibling(); +ModifierNode::ModifyResult UnsetNode::updateExistingElement( + mutablebson::Element* element, std::shared_ptr<FieldRef> elementPath) const { + auto parent = element->parent(); invariant(parent.ok()); if (!parent.isType(BSONType::Array)) { - invariantOK(applyParams.element.remove()); + invariantOK(element->remove()); } else { // Special case: An $unset on an array element sets it to null instead of removing it from // the array. - invariantOK(applyParams.element.setValueNull()); + invariantOK(element->setValueNull()); } - if (applyParams.validateForStorage) { + return ModifyResult::kNormalUpdate; +} - // Validate the left and right sibling, in case this element was part of a DBRef. - if (leftSibling.ok()) { - const bool doRecursiveCheck = false; - const uint32_t recursionLevel = 0; - storage_validation::storageValid(leftSibling, doRecursiveCheck, recursionLevel); - } +void UnsetNode::validateUpdate(mutablebson::ConstElement updatedElement, + mutablebson::ConstElement leftSibling, + mutablebson::ConstElement rightSibling, + std::uint32_t recursionLevel, + ModifyResult modifyResult) const { + invariant(modifyResult == ModifyResult::kNormalUpdate); - if (rightSibling.ok()) { - const bool doRecursiveCheck = false; - const uint32_t recursionLevel = 0; - storage_validation::storageValid(rightSibling, doRecursiveCheck, recursionLevel); - } - } + // We only need to check the left and right sibling to see if the removed element was part of a + // now invalid DBRef. + const bool doRecursiveCheck = false; + const uint32_t recursionLevelForCheck = 0; - // Ensure we are not changing any immutable paths. - for (auto immutablePath = applyParams.immutablePaths.begin(); - immutablePath != applyParams.immutablePaths.end(); - ++immutablePath) { - uassert(ErrorCodes::ImmutableField, - str::stream() << "Unsetting the path '" << applyParams.pathTaken->dottedField() - << "' would modify the immutable field '" - << (*immutablePath)->dottedField() - << "'", - applyParams.pathTaken->commonPrefixSize(**immutablePath) < - std::min(applyParams.pathTaken->numParts(), (*immutablePath)->numParts())); + if (leftSibling.ok()) { + storage_validation::storageValid(leftSibling, doRecursiveCheck, recursionLevelForCheck); } - // Log the unset. - if (applyParams.logBuilder) { - uassertStatusOK(applyParams.logBuilder->addToUnsets(applyParams.pathTaken->dottedField())); + if (rightSibling.ok()) { + storage_validation::storageValid(rightSibling, doRecursiveCheck, recursionLevelForCheck); } +} - return applyResult; +void UnsetNode::logUpdate(LogBuilder* logBuilder, + StringData pathTaken, + mutablebson::Element element, + ModifyResult modifyResult) const { + invariant(logBuilder); + invariant(modifyResult == ModifyResult::kNormalUpdate); + uassertStatusOK(logBuilder->addToUnsets(pathTaken)); } } // namespace mongo diff --git a/src/mongo/db/update/unset_node.h b/src/mongo/db/update/unset_node.h index a8d19f9ade8..645497ef6a9 100644 --- a/src/mongo/db/update/unset_node.h +++ b/src/mongo/db/update/unset_node.h @@ -28,7 +28,7 @@ #pragma once -#include "mongo/db/update/update_leaf_node.h" +#include "mongo/db/update/modifier_node.h" #include "mongo/stdx/memory.h" namespace mongo { @@ -36,7 +36,7 @@ namespace mongo { /** * Represents the application of a $unset to the value at the end of a path. */ -class UnsetNode : public UpdateLeafNode { +class UnsetNode : public ModifierNode { public: Status init(BSONElement modExpr, const CollatorInterface* collator) final; @@ -46,7 +46,23 @@ public: void setCollator(const CollatorInterface* collator) final {} - ApplyResult apply(ApplyParams applyParams) const final; + ModifyResult updateExistingElement(mutablebson::Element* element, + std::shared_ptr<FieldRef> elementPath) const final; + + void validateUpdate(mutablebson::ConstElement updatedElement, + mutablebson::ConstElement leftSibling, + mutablebson::ConstElement rightSibling, + std::uint32_t recursionLevel, + ModifyResult modifyResult) const final; + + void logUpdate(LogBuilder* logBuilder, + StringData pathTaken, + mutablebson::Element element, + ModifyResult modifyResult) const final; + + bool allowNonViablePath() const final { + return true; + } }; } // namespace mongo diff --git a/src/mongo/db/update/unset_node_test.cpp b/src/mongo/db/update/unset_node_test.cpp index f1c43b4c6fa..26c89e05048 100644 --- a/src/mongo/db/update/unset_node_test.cpp +++ b/src/mongo/db/update/unset_node_test.cpp @@ -51,7 +51,7 @@ DEATH_TEST(UnsetNodeTest, InitFailsForEmptyElement, "Invariant failure modExpr.o node.init(update["$unset"].embeddedObject().firstElement(), collator).transitional_ignore(); } -DEATH_TEST_F(UnsetNodeTest, ApplyToRootFails, "Invariant failure parent.ok()") { +DEATH_TEST_F(UnsetNodeTest, ApplyToRootFails, "Invariant failure !applyParams.pathTaken->empty()") { auto update = fromjson("{$unset: {}}"); const CollatorInterface* collator = nullptr; UnsetNode node; @@ -369,10 +369,11 @@ TEST_F(UnsetNodeTest, ApplyCannotRemoveImmutablePath) { mutablebson::Document doc(fromjson("{a: {b: 1}}")); setPathTaken("a.b"); addImmutablePath("a.b"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"]["b"])), - AssertionException, - ErrorCodes::ImmutableField, - "Unsetting the path 'a.b' would modify the immutable field 'a.b'"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]["b"])), + AssertionException, + ErrorCodes::ImmutableField, + "Performing an update on the path 'a.b' would modify the immutable field 'a.b'"); } TEST_F(UnsetNodeTest, ApplyCannotRemovePrefixOfImmutablePath) { @@ -384,10 +385,11 @@ TEST_F(UnsetNodeTest, ApplyCannotRemovePrefixOfImmutablePath) { 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'"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"])), + AssertionException, + ErrorCodes::ImmutableField, + "Performing an update on the path 'a' would modify the immutable field 'a.b'"); } TEST_F(UnsetNodeTest, ApplyCannotRemoveSuffixOfImmutablePath) { @@ -403,7 +405,7 @@ TEST_F(UnsetNodeTest, ApplyCannotRemoveSuffixOfImmutablePath) { node.apply(getApplyParams(doc.root()["a"]["b"]["c"])), AssertionException, ErrorCodes::ImmutableField, - "Unsetting the path 'a.b.c' would modify the immutable field 'a.b'"); + "Performing an update on the path 'a.b.c' would modify the immutable field 'a.b'"); } TEST_F(UnsetNodeTest, ApplyCanRemoveImmutablePathIfNoop) { diff --git a/src/mongo/db/update/update_object_node.cpp b/src/mongo/db/update/update_object_node.cpp index 0ed380f1373..9b984eb3a33 100644 --- a/src/mongo/db/update/update_object_node.cpp +++ b/src/mongo/db/update/update_object_node.cpp @@ -122,8 +122,9 @@ void applyChild(const UpdateNode& child, 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. + // that creates new path components (i.e., any modifiers that return true for + // allowCreation()) will need to create this component, so we append it to the + // 'pathToCreate' FieldRef. childElement = applyParams->element; applyParams->pathToCreate->appendPart(field); } |