summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTess Avitabile <tess.avitabile@mongodb.com>2017-04-21 16:13:21 -0400
committerTess Avitabile <tess.avitabile@mongodb.com>2017-05-01 10:00:22 -0400
commit314809d7cdda82d6c2741dbe2daf119270e5ad02 (patch)
tree6fe3ede8bac16e227d1046611e4bcb827fd99e39
parent5273c2bad7df58c44afdd677609b8656f399a906 (diff)
downloadmongo-314809d7cdda82d6c2741dbe2daf119270e5ad02.tar.gz
SERVER-28759 Implement SetNode::apply()
-rw-r--r--src/mongo/db/update/SConscript3
-rw-r--r--src/mongo/db/update/path_support.cpp14
-rw-r--r--src/mongo/db/update/path_support_test.cpp24
-rw-r--r--src/mongo/db/update/set_node.cpp69
-rw-r--r--src/mongo/db/update/set_node.h10
-rw-r--r--src/mongo/db/update/set_node_test.cpp1688
-rw-r--r--src/mongo/db/update/update_leaf_node.h30
7 files changed, 1836 insertions, 2 deletions
diff --git a/src/mongo/db/update/SConscript b/src/mongo/db/update/SConscript
index 2bf1d3b756b..82d60443cd9 100644
--- a/src/mongo/db/update/SConscript
+++ b/src/mongo/db/update/SConscript
@@ -61,6 +61,7 @@ env.Library(
],
LIBDEPS=[
'$BUILD_DIR/mongo/db/ops/update',
+ '$BUILD_DIR/mongo/db/update_index_data',
'update_common',
],
)
@@ -77,6 +78,7 @@ env.CppUnitTest(
target='set_node_test',
source='set_node_test.cpp',
LIBDEPS=[
+ '$BUILD_DIR/mongo/bson/mutable/mutable_bson_test_utils',
'update',
],
)
@@ -99,7 +101,6 @@ env.Library(
'$BUILD_DIR/mongo/db/common',
'$BUILD_DIR/mongo/db/ops/update',
'$BUILD_DIR/mongo/db/query/query_planner',
- '$BUILD_DIR/mongo/db/update_index_data',
'update',
],
)
diff --git a/src/mongo/db/update/path_support.cpp b/src/mongo/db/update/path_support.cpp
index 5e336bb8202..993d4195bc4 100644
--- a/src/mongo/db/update/path_support.cpp
+++ b/src/mongo/db/update/path_support.cpp
@@ -162,6 +162,14 @@ Status createPathAt(const FieldRef& prefix,
mutablebson::Element newElem) {
Status status = Status::OK();
+ if (elemFound.getType() != BSONType::Object && elemFound.getType() != BSONType::Array) {
+ return Status(ErrorCodes::PathNotViable,
+ str::stream() << "Cannot create field '" << prefix.getPart(idxFound)
+ << "' in element {"
+ << elemFound.toString()
+ << "}");
+ }
+
// Sanity check that 'idxField' is an actual part.
const size_t size = prefix.numParts();
if (idxFound >= size) {
@@ -177,7 +185,11 @@ Status createPathAt(const FieldRef& prefix,
if (elemFound.getType() == mongo::Array) {
size_t newIdx = 0;
if (!isNumeric(prefix.getPart(idxFound), &newIdx)) {
- return Status(ErrorCodes::InvalidPath, "Array require numeric fields");
+ return Status(ErrorCodes::PathNotViable,
+ str::stream() << "Cannot create field '" << prefix.getPart(idxFound)
+ << "' in element {"
+ << elemFound.toString()
+ << "}");
}
status = maybePadTo(&elemFound, newIdx);
diff --git a/src/mongo/db/update/path_support_test.cpp b/src/mongo/db/update/path_support_test.cpp
index 5091a2b8cba..5ea820bbf7b 100644
--- a/src/mongo/db/update/path_support_test.cpp
+++ b/src/mongo/db/update/path_support_test.cpp
@@ -198,6 +198,18 @@ TEST_F(SimpleDoc, NotCommonPrefix) {
ASSERT_EQUALS(root().rightChild().leftSibling().getFieldName(), "a");
}
+TEST_F(SimpleDoc, CreatePathAtFailsIfElemFoundIsNonObjectNonArray) {
+ setField("b");
+
+ auto elemFound = root()["a"];
+ auto newElem = doc().makeElementInt("b", 1);
+ ASSERT_TRUE(newElem.ok());
+ auto result = createPathAt(field(), 0, elemFound, newElem);
+ ASSERT_NOT_OK(result);
+ ASSERT_EQ(result.code(), ErrorCodes::PathNotViable);
+ ASSERT_EQ(result.reason(), "Cannot create field 'b' in element {a: 1}");
+}
+
class NestedDoc : public mongo::unittest::Test {
public:
NestedDoc() : _doc() {}
@@ -520,6 +532,18 @@ TEST_F(ArrayDoc, NonNumericPathInArray) {
ASSERT_EQUALS(elemFound.compareWithElement(root()["b"], nullptr), 0);
}
+TEST_F(ArrayDoc, CreatePathAtFailsIfElemFoundIsArrayAndIdxFoundFieldIsNonNumeric) {
+ setField("b");
+
+ auto elemFound = root()["a"];
+ auto newElem = doc().makeElementInt("b", 1);
+ ASSERT_TRUE(newElem.ok());
+ auto result = createPathAt(field(), 0, elemFound, newElem);
+ ASSERT_NOT_OK(result);
+ ASSERT_EQ(result.code(), ErrorCodes::PathNotViable);
+ ASSERT_EQ(result.reason(), "Cannot create field 'b' in element {a: []}");
+}
+
//
// Tests of equality extraction from MatchExpressions
// NONGOAL: Testing query/match expression parsing and optimization
diff --git a/src/mongo/db/update/set_node.cpp b/src/mongo/db/update/set_node.cpp
index 26c6be59ee3..3cc44424600 100644
--- a/src/mongo/db/update/set_node.cpp
+++ b/src/mongo/db/update/set_node.cpp
@@ -30,6 +30,8 @@
#include "mongo/db/update/set_node.h"
+#include "mongo/db/update/path_support.h"
+
namespace mongo {
Status SetNode::init(BSONElement modExpr, const CollatorInterface* collator) {
@@ -40,4 +42,71 @@ Status SetNode::init(BSONElement modExpr, const CollatorInterface* collator) {
return Status::OK();
}
+Status SetNode::apply(mutablebson::Element element,
+ FieldRef* pathToCreate,
+ FieldRef* pathTaken,
+ StringData matchedField,
+ bool fromReplication,
+ const UpdateIndexData* indexData,
+ LogBuilder* logBuilder,
+ bool* indexesAffected,
+ bool* noop) {
+ *indexesAffected = false;
+ *noop = false;
+
+ // Set the element with the new value.
+ if (pathToCreate->empty()) {
+ // If 'element' is deserialized, then element.getValue() will be EOO, which will never equal
+ // _val.
+ if (element.getValue().binaryEqualValues(_val)) {
+ *noop = true;
+ return Status::OK();
+ }
+ element.setValueBSONElement(_val);
+ } else {
+ auto elemToSet = element.getDocument().makeElementWithNewFieldName(
+ pathToCreate->getPart(pathToCreate->numParts() - 1), _val);
+ invariant(elemToSet.ok());
+ auto status = pathsupport::createPathAt(*pathToCreate, 0, element, elemToSet);
+ if (!status.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'.
+ if (status.code() == ErrorCodes::PathNotViable && fromReplication) {
+ *noop = true;
+ return Status::OK();
+ }
+ return status;
+ }
+ }
+
+ // Create full field path of set element.
+ StringBuilder builder;
+ builder << pathTaken->dottedField();
+ if (!pathTaken->empty() && !pathToCreate->empty()) {
+ builder << ".";
+ }
+ builder << pathToCreate->dottedField();
+ auto fullPath = builder.str();
+
+ // Determine if indexes are affected.
+ if (indexData && indexData->mightBeIndexed(fullPath)) {
+ *indexesAffected = true;
+ }
+
+ // Log the set.
+ if (logBuilder) {
+ auto logElement = logBuilder->getDocument().makeElementWithNewFieldName(fullPath, _val);
+ invariant(logElement.ok());
+ return logBuilder->addToSets(logElement);
+ }
+
+ return Status::OK();
+}
+
} // namespace mongo
diff --git a/src/mongo/db/update/set_node.h b/src/mongo/db/update/set_node.h
index 8ad64010a50..1a0d32a72b0 100644
--- a/src/mongo/db/update/set_node.h
+++ b/src/mongo/db/update/set_node.h
@@ -41,6 +41,16 @@ public:
void setCollator(const CollatorInterface* collator) final {}
+ Status apply(mutablebson::Element element,
+ FieldRef* pathToCreate,
+ FieldRef* pathTaken,
+ StringData matchedField,
+ bool fromReplication,
+ const UpdateIndexData* indexData,
+ LogBuilder* logBuilder,
+ bool* indexesAffected,
+ bool* noop) final;
+
private:
BSONElement _val;
};
diff --git a/src/mongo/db/update/set_node_test.cpp b/src/mongo/db/update/set_node_test.cpp
index c600af34158..e2fe5fe14fa 100644
--- a/src/mongo/db/update/set_node_test.cpp
+++ b/src/mongo/db/update/set_node_test.cpp
@@ -30,6 +30,8 @@
#include "mongo/db/update/set_node.h"
+#include "mongo/bson/mutable/algorithm.h"
+#include "mongo/bson/mutable/mutable_bson_test_utils.h"
#include "mongo/db/json.h"
#include "mongo/unittest/death_test.h"
#include "mongo/unittest/unittest.h"
@@ -37,6 +39,9 @@
namespace {
using namespace mongo;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
+using mongo::mutablebson::countChildren;
DEATH_TEST(SetNodeTest, InitFailsForEmptyElement, "Invariant failure modExpr.ok()") {
auto update = fromjson("{$set: {}}");
@@ -52,4 +57,1687 @@ TEST(SetNodeTest, InitSucceedsForNonemptyElement) {
ASSERT_OK(node.init(update["$set"]["a"], collator));
}
+TEST(SetNodeTest, ApplyNoOp) {
+ auto update = fromjson("{$set: {a: 5}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a"], collator));
+
+ Document doc(fromjson("{a: 5}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 5}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{}"), logDoc);
+}
+
+TEST(SetNodeTest, ApplyEmptyPathToCreate) {
+ auto update = fromjson("{$set: {a: 6}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a"], collator));
+
+ Document doc(fromjson("{a: 5}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 6}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: 6}}"), logDoc);
+}
+
+TEST(SetNodeTest, ApplyCreatePath) {
+ auto update = fromjson("{$set: {'a.b.c': 6}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b.c"], collator));
+
+ Document doc(fromjson("{a: {d: 5}}"));
+ FieldRef pathToCreate("b.c");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {d: 5, b: {c: 6}}}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {'a.b.c': 6}}"), logDoc);
+}
+
+TEST(SetNodeTest, ApplyCreatePathFromRoot) {
+ auto update = fromjson("{$set: {'a.b': 6}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{c: 5}"));
+ FieldRef pathToCreate("a.b");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{c: 5, a: {b: 6}}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {'a.b': 6}}"), logDoc);
+}
+
+TEST(SetNodeTest, ApplyPositional) {
+ auto update = fromjson("{$set: {'a.$': 6}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.$"], collator));
+
+ Document doc(fromjson("{a: [0, 1, 2]}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a.1");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: [0, 1, 2]}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {'a.1': 6}}"), logDoc);
+}
+
+TEST(SetNodeTest, ApplyNonViablePathToCreate) {
+ auto update = fromjson("{$set: {'a.b': 5}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{a: 5}"));
+ FieldRef pathToCreate("b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ auto result = node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_NOT_OK(result);
+ ASSERT_EQ(result.code(), ErrorCodes::PathNotViable);
+ ASSERT_EQ(result.reason(), "Cannot create field 'b' in element {a: 5}");
+}
+
+TEST(SetNodeTest, ApplyNonViablePathToCreateFromReplicationIsNoOp) {
+ auto update = fromjson("{$set: {'a.b': 5}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{a: 5}"));
+ FieldRef pathToCreate("b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 5}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{}"), logDoc);
+}
+
+TEST(SetNodeTest, ApplyNoIndexDataNoLogBuilder) {
+ auto update = fromjson("{$set: {a: 6}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a"], collator));
+
+ Document doc(fromjson("{a: 5}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ const UpdateIndexData* indexData = nullptr;
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 6}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyDoesNotAffectIndexes) {
+ auto update = fromjson("{$set: {a: 6}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a"], collator));
+
+ Document doc(fromjson("{a: 5}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 6}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, TypeChangeIsNotANoop) {
+ auto update = fromjson("{$set: {a: NumberLong(2)}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a"], collator));
+
+ Document doc(fromjson("{a: NumberInt(2)}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: NumberLong(2)}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, IdentityOpOnDeserializedIsNotANoOp) {
+ // Apply an op that would be a no-op.
+ auto update = fromjson("{$set: {a: {b : NumberInt(2)}}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a"], collator));
+
+ Document doc(fromjson("{a: { b: NumberInt(0)}}"));
+ // Apply a mutation to the document that will make it non-serialized.
+ doc.root()["a"]["b"].setValueInt(2);
+
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {b : NumberInt(2)}}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyEmptyDocument) {
+ auto update = fromjson("{$set: {a: 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a"], collator));
+
+ Document doc(fromjson("{}"));
+ FieldRef pathToCreate("a");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 2}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyInPlace) {
+ auto update = fromjson("{$set: {a: 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a"], collator));
+
+ Document doc(fromjson("{a: 1}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 2}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyOverridePath) {
+ auto update = fromjson("{$set: {a: 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a"], collator));
+
+ Document doc(fromjson("{a: {b: 1}}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 2}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyChangeType) {
+ auto update = fromjson("{$set: {a: 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a"], collator));
+
+ Document doc(fromjson("{a: 'str'}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 2}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyNewPath) {
+ auto update = fromjson("{$set: {a: 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a"], collator));
+
+ Document doc(fromjson("{b: 1}"));
+ FieldRef pathToCreate("a");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{b: 1, a: 2}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyLog) {
+ auto update = fromjson("{$set: {a: 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a"], collator));
+
+ Document doc(fromjson("{a: 1}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ const UpdateIndexData* indexData = nullptr;
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_EQUALS(fromjson("{a: 2}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {a: 2}}"), logDoc);
+}
+
+TEST(SetNodeTest, ApplyNoOpDottedPath) {
+ auto update = fromjson("{$set: {'a.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{a: {b: 2}}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a.b");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["b"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {b : 2}}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, TypeChangeOnDottedPathIsNotANoOp) {
+ auto update = fromjson("{$set: {'a.b': NumberInt(2)}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{a: {b: NumberLong(2)}}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a.b");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["b"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {b : NumberLong(2)}}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyPathNotViable) {
+ auto update = fromjson("{$set: {'a.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{a:1}"));
+ FieldRef pathToCreate("b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ const UpdateIndexData* indexData = nullptr;
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_NOT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+}
+
+TEST(SetNodeTest, ApplyPathNotViableArrray) {
+ auto update = fromjson("{$set: {'a.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{a:[{b:1}]}"));
+ FieldRef pathToCreate("b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ const UpdateIndexData* indexData = nullptr;
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_NOT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+}
+
+TEST(SetNodeTest, ApplyInPlaceDottedPath) {
+ auto update = fromjson("{$set: {'a.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{a: {b: 1}}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a.b");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["b"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyChangeTypeDottedPath) {
+ auto update = fromjson("{$set: {'a.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{a: {b: 'str'}}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a.b");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["b"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyChangePath) {
+ auto update = fromjson("{$set: {'a.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{a: {b: {c: 1}}}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a.b");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["b"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyExtendPath) {
+ auto update = fromjson("{$set: {'a.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{a: {c: 1}}"));
+ FieldRef pathToCreate("b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {c: 1, b: 2}}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyNewDottedPath) {
+ auto update = fromjson("{$set: {'a.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{c: 1}"));
+ FieldRef pathToCreate("a.b");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{c: 1, a: {b: 2}}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyEmptyDoc) {
+ auto update = fromjson("{$set: {'a.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{}"));
+ FieldRef pathToCreate("a.b");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyFieldWithDot) {
+ auto update = fromjson("{$set: {'a.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b"], collator));
+
+ Document doc(fromjson("{'a.b':4}"));
+ FieldRef pathToCreate("a.b");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{'a.b':4, a: {b: 2}}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyNoOpArrayIndex) {
+ auto update = fromjson("{$set: {'a.2.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.2.b"], collator));
+
+ Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a.2.b");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.2.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["2"]["b"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, TypeChangeInArrayIsNotANoOp) {
+ auto update = fromjson("{$set: {'a.2.b': NumberInt(2)}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.2.b"], collator));
+
+ Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 2.0}]}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a.2.b");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.2.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["2"]["b"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: NumberInt(2)}]}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyNonViablePath) {
+ auto update = fromjson("{$set: {'a.2.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.2.b"], collator));
+
+ Document doc(fromjson("{a: 0}"));
+ FieldRef pathToCreate("2.b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ const UpdateIndexData* indexData = nullptr;
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_NOT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+}
+
+TEST(SetNodeTest, ApplyInPlaceArrayIndex) {
+ auto update = fromjson("{$set: {'a.2.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.2.b"], collator));
+
+ Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 1}]}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a.2.b");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.2.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["2"]["b"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyNormalArray) {
+ auto update = fromjson("{$set: {'a.2.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.2.b"], collator));
+
+ Document doc(fromjson("{a: [{b: 0},{b: 1}]}"));
+ FieldRef pathToCreate("2.b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.2.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyPaddingArray) {
+ auto update = fromjson("{$set: {'a.2.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.2.b"], collator));
+
+ Document doc(fromjson("{a: [{b: 0}]}"));
+ FieldRef pathToCreate("2.b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.2.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: [{b: 0},null,{b: 2}]}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyNumericObject) {
+ auto update = fromjson("{$set: {'a.2.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.2.b"], collator));
+
+ Document doc(fromjson("{a: {b: 0}}"));
+ FieldRef pathToCreate("2.b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.2.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {b: 0, '2': {b: 2}}}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyNumericField) {
+ auto update = fromjson("{$set: {'a.2.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.2.b"], collator));
+
+ Document doc(fromjson("{a: {'2': {b: 1}}}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a.2.b");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.2.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["2"]["b"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyExtendNumericField) {
+ auto update = fromjson("{$set: {'a.2.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.2.b"], collator));
+
+ Document doc(fromjson("{a: {'2': {c: 1}}}"));
+ FieldRef pathToCreate("b");
+ FieldRef pathTaken("a.2");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.2.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["2"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {'2': {c: 1, b: 2}}}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyEmptyObject) {
+ auto update = fromjson("{$set: {'a.2.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.2.b"], collator));
+
+ Document doc(fromjson("{a: {}}"));
+ FieldRef pathToCreate("2.b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.2.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyEmptyArray) {
+ auto update = fromjson("{$set: {'a.2.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.2.b"], collator));
+
+ Document doc(fromjson("{a: []}"));
+ FieldRef pathToCreate("2.b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.2.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: [null, null, {b: 2}]}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplyLogDottedPath) {
+ auto update = fromjson("{$set: {'a.2.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.2.b"], collator));
+
+ Document doc(fromjson("{a: [{b:0}, {b:1}]}"));
+ FieldRef pathToCreate("2.b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ const UpdateIndexData* indexData = nullptr;
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_EQUALS(fromjson("{a: [{b:0}, {b:1}, {b:2}]}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc);
+}
+
+TEST(SetNodeTest, LogEmptyArray) {
+ auto update = fromjson("{$set: {'a.2.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.2.b"], collator));
+
+ Document doc(fromjson("{a: []}"));
+ FieldRef pathToCreate("2.b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ const UpdateIndexData* indexData = nullptr;
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_EQUALS(fromjson("{a: [null, null, {b:2}]}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc);
+}
+
+TEST(SetNodeTest, LogEmptyObject) {
+ auto update = fromjson("{$set: {'a.2.b': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.2.b"], collator));
+
+ Document doc(fromjson("{a: {}}"));
+ FieldRef pathToCreate("2.b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ const UpdateIndexData* indexData = nullptr;
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc);
+}
+
+TEST(SetNodeTest, ApplyNoOpComplex) {
+ auto update = fromjson("{$set: {'a.1.b': {c: 1, d: 1}}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.1.b"], collator));
+
+ Document doc(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, d: 1}}]}}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a.1.b");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.1.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["1"]["b"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, d: 1}}]}}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ApplySameStructure) {
+ auto update = fromjson("{$set: {'a.1.b': {c: 1, d: 1}}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.1.b"], collator));
+
+ Document doc(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, xxx: 1}}]}}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a.1.b");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("a.1.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["1"]["b"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, d: 1}}]}}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, NonViablePathWithoutRepl) {
+ auto update = fromjson("{$set: {'a.1.b': 1}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.1.b"], collator));
+
+ Document doc(fromjson("{a: 1}"));
+ FieldRef pathToCreate("1.b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ const UpdateIndexData* indexData = nullptr;
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_NOT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+}
+
+TEST(SetNodeTest, SingleFieldFromReplication) {
+ auto update = fromjson("{$set: {'a.1.b': 1}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.1.b"], collator));
+
+ Document doc(fromjson("{_id:1, a: 1}"));
+ FieldRef pathToCreate("1.b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ UpdateIndexData indexData;
+ indexData.addPath("a.1.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{_id:1, a: 1}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, SingleFieldNoIdFromReplication) {
+ auto update = fromjson("{$set: {'a.1.b': 1}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.1.b"], collator));
+
+ Document doc(fromjson("{a: 1}"));
+ FieldRef pathToCreate("1.b");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ UpdateIndexData indexData;
+ indexData.addPath("a.1.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 1}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, NestedFieldFromReplication) {
+ auto update = fromjson("{$set: {'a.a.1.b': 1}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.a.1.b"], collator));
+
+ Document doc(fromjson("{_id:1, a: {a: 1}}"));
+ FieldRef pathToCreate("1.b");
+ FieldRef pathTaken("a.a");
+ StringData matchedField;
+ auto fromReplication = true;
+ UpdateIndexData indexData;
+ indexData.addPath("a.a.1.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{_id:1, a: {a: 1}}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, DoubleNestedFieldFromReplication) {
+ auto update = fromjson("{$set: {'a.b.c.d': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.b.c.d"], collator));
+
+ Document doc(fromjson("{_id:1, a: {b: {c: 1}}}"));
+ FieldRef pathToCreate("d");
+ FieldRef pathTaken("a.b.c");
+ StringData matchedField;
+ auto fromReplication = true;
+ UpdateIndexData indexData;
+ indexData.addPath("a.b.c.d");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["b"]["c"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{_id:1, a: {b: {c: 1}}}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, NestedFieldNoIdFromReplication) {
+ auto update = fromjson("{$set: {'a.a.1.b': 1}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.a.1.b"], collator));
+
+ Document doc(fromjson("{a: {a: 1}}"));
+ FieldRef pathToCreate("1.b");
+ FieldRef pathTaken("a.a");
+ StringData matchedField;
+ auto fromReplication = true;
+ UpdateIndexData indexData;
+ indexData.addPath("a.a.1.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {a: 1}}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, ReplayArrayFieldNotAppendedIntermediateFromReplication) {
+ auto update = fromjson("{$set: {'a.0.b': [0,2]}}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["a.0.b"], collator));
+
+ Document doc(fromjson("{_id: 0, a: [1, {b: [1]}]}"));
+ FieldRef pathToCreate("b");
+ FieldRef pathTaken("a.0");
+ StringData matchedField;
+ auto fromReplication = true;
+ UpdateIndexData indexData;
+ indexData.addPath("a.0.b");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["a"]["0"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{_id: 0, a: [1, {b: [1]}]}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+TEST(SetNodeTest, Set6) {
+ auto update = fromjson("{$set: {'r.a': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["r.a"], collator));
+
+ Document doc(fromjson("{_id: 1, r: {a:1, b:2}}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("r.a");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("r.a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["r"]["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{_id: 1, r: {a:2, b:2}}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'r.a': 2}}"), logDoc);
+}
+
+TEST(SetNodeTest, Set6FromRepl) {
+ auto update = fromjson("{$set: { 'r.a': 2}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["r.a"], collator));
+
+ Document doc(fromjson("{_id: 1, r: {a:1, b:2}}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("r.a");
+ StringData matchedField;
+ auto fromReplication = true;
+ UpdateIndexData indexData;
+ indexData.addPath("r.a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["r"]["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{_id: 1, r: {a:2, b:2} }"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'r.a': 2}}"), logDoc);
+}
+
+TEST(SetNodeTest, ApplySetModToEphemeralDocument) {
+ // The following mod when applied to a document constructed node by node exposed a
+ // latent debug only defect in mutable BSON, so this is more a test of mutable than
+ // $set.
+ auto update = fromjson("{ $set: { x: { a: 100, b: 2 }}}");
+ const CollatorInterface* collator = nullptr;
+ SetNode node;
+ ASSERT_OK(node.init(update["$set"]["x"], collator));
+
+ Document doc;
+ Element x = doc.makeElementObject("x");
+ doc.root().pushBack(x);
+ Element a = doc.makeElementInt("a", 100);
+ x.pushBack(a);
+
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("x");
+ StringData matchedField;
+ auto fromReplication = false;
+ UpdateIndexData indexData;
+ indexData.addPath("x");
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ ASSERT_OK(node.apply(doc.root()["x"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ &indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop));
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{ x : { a : 100, b : 2 } }"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+}
+
} // namespace
diff --git a/src/mongo/db/update/update_leaf_node.h b/src/mongo/db/update/update_leaf_node.h
index 2b378036c7d..8f51dbf4a14 100644
--- a/src/mongo/db/update/update_leaf_node.h
+++ b/src/mongo/db/update/update_leaf_node.h
@@ -29,7 +29,11 @@
#pragma once
#include "mongo/bson/bsonelement.h"
+#include "mongo/bson/mutable/element.h"
+#include "mongo/db/field_ref.h"
+#include "mongo/db/update/log_builder.h"
#include "mongo/db/update/update_node.h"
+#include "mongo/db/update_index_data.h"
namespace mongo {
@@ -53,6 +57,32 @@ public:
* multiple documents.
*/
virtual Status init(BSONElement modExpr, const CollatorInterface* collator) = 0;
+
+ /**
+ * Applies the update node to 'element', creating the fields in 'pathToCreate' if required by
+ * the leaves (i.e. the leaves are not all $unset). 'pathTaken' is the path through the root
+ * document to 'element', ending with the field name of 'element'. 'pathToCreate' is the path
+ * taken through the UpdateNode tree beyond where the path existed in the document. For example,
+ * if the update is {$set: {'a.b.c': 5}}, and the document is {a: {}}, then at the leaf node,
+ * pathTaken="a" and pathToCreate="b.c" If there was a positional ($) element in the update
+ * expression, 'matchedField' is the index of the array element that caused the query to match
+ * the document. 'fromReplication' is provided because some modifiers may ignore certain errors
+ * when the update is from replication. Uses the index information in 'indexData' to determine
+ * whether indexes are affected. If a LogBuilder is provided, logs the update. Outputs whether
+ * the operation was a no-op. Returns a non-OK status if the update node cannot be applied to
+ * the document.
+ *
+ * TODO SERVER-28761: Move this virtual method into UpdateNode.
+ */
+ virtual Status apply(mutablebson::Element element,
+ FieldRef* pathToCreate,
+ FieldRef* pathTaken,
+ StringData matchedField,
+ bool fromReplication,
+ const UpdateIndexData* indexData,
+ LogBuilder* logBuilder,
+ bool* indexesAffected,
+ bool* noop) = 0;
};
} // namespace mongo