summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Seyster <justin.seyster@mongodb.com>2017-08-18 15:00:08 -0400
committerJustin Seyster <justin.seyster@mongodb.com>2017-08-18 19:13:27 -0400
commitaef10829fc71cb41c54df5838e9e7e74d41d122b (patch)
tree23916345612945a059de6798303f2f597561a1f6
parentb3ad5d465cd2fec4983ff84be9da2cc06c1dac97 (diff)
downloadmongo-aef10829fc71cb41c54df5838e9e7e74d41d122b.tar.gz
SERVER-30401 Simplify UpdateLeafNode::apply interface
We need some simplifiction here because UpdateLeafNode::apply is responsible for so many things (a list of which is in modifier_node.h). This change puts most of those things into one function, so that the various modifier implementations can write a few small overrides to customize their functionality, rather than reimplementing all of apply() in each case. This approach extends the PathCreatingNode approach we took previously for all the modifiers. The one exception is RenameNode, which we implement by composing SetNode and UnsetNode.
-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);
}