summaryrefslogtreecommitdiff
path: root/src/mongo/db/update
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/update')
-rw-r--r--src/mongo/db/update/SConscript2
-rw-r--r--src/mongo/db/update/addtoset_node.cpp10
-rw-r--r--src/mongo/db/update/addtoset_node.h13
-rw-r--r--src/mongo/db/update/arithmetic_node.cpp10
-rw-r--r--src/mongo/db/update/arithmetic_node.h13
-rw-r--r--src/mongo/db/update/array_culling_node.cpp72
-rw-r--r--src/mongo/db/update/array_culling_node.h13
-rw-r--r--src/mongo/db/update/bit_node.cpp10
-rw-r--r--src/mongo/db/update/bit_node.h13
-rw-r--r--src/mongo/db/update/compare_node.cpp10
-rw-r--r--src/mongo/db/update/compare_node.h17
-rw-r--r--src/mongo/db/update/current_date_node.cpp8
-rw-r--r--src/mongo/db/update/current_date_node.h13
-rw-r--r--src/mongo/db/update/modifier_node.cpp336
-rw-r--r--src/mongo/db/update/modifier_node.h175
-rw-r--r--src/mongo/db/update/path_creating_node.cpp236
-rw-r--r--src/mongo/db/update/path_creating_node.h72
-rw-r--r--src/mongo/db/update/pop_node.cpp75
-rw-r--r--src/mongo/db/update/pop_node.h13
-rw-r--r--src/mongo/db/update/pop_node_test.cpp6
-rw-r--r--src/mongo/db/update/push_node.cpp86
-rw-r--r--src/mongo/db/update/push_node.h49
-rw-r--r--src/mongo/db/update/rename_node.cpp92
-rw-r--r--src/mongo/db/update/rename_node_test.cpp8
-rw-r--r--src/mongo/db/update/set_node.cpp10
-rw-r--r--src/mongo/db/update/set_node.h19
-rw-r--r--src/mongo/db/update/set_node_test.cpp4
-rw-r--r--src/mongo/db/update/unset_node.cpp78
-rw-r--r--src/mongo/db/update/unset_node.h22
-rw-r--r--src/mongo/db/update/unset_node_test.cpp22
-rw-r--r--src/mongo/db/update/update_object_node.cpp5
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);
}