summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorArun Banala <arun.banala@mongodb.com>2020-09-17 19:45:42 +0100
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-09-17 21:38:12 +0000
commit18545807095a982a8b527ec3eb1e45a447d06719 (patch)
treebad2ef2160ee81b9594536fde8cdda483b32f800 /src/mongo/db
parent63813e4afd4d8b7b0309c0ed07d946f128801848 (diff)
downloadmongo-18545807095a982a8b527ec3eb1e45a447d06719.tar.gz
SERVER-50247 Use document deltas to determine whether update affects indexes while building diff
Diffstat (limited to 'src/mongo/db')
-rw-r--r--src/mongo/db/repl/idempotency_test.cpp11
-rw-r--r--src/mongo/db/update/document_diff_calculator.cpp69
-rw-r--r--src/mongo/db/update/document_diff_calculator.h18
-rw-r--r--src/mongo/db/update/document_diff_calculator_test.cpp223
-rw-r--r--src/mongo/db/update/document_diff_serialization.cpp2
-rw-r--r--src/mongo/db/update/document_diff_serialization.h5
-rw-r--r--src/mongo/db/update/document_diff_test.cpp12
-rw-r--r--src/mongo/db/update/pipeline_executor.cpp13
-rw-r--r--src/mongo/db/update/pipeline_executor_test.cpp312
-rw-r--r--src/mongo/db/update/update_driver.cpp4
10 files changed, 538 insertions, 131 deletions
diff --git a/src/mongo/db/repl/idempotency_test.cpp b/src/mongo/db/repl/idempotency_test.cpp
index f8bba817ee8..56e09b15e6d 100644
--- a/src/mongo/db/repl/idempotency_test.cpp
+++ b/src/mongo/db/repl/idempotency_test.cpp
@@ -287,10 +287,13 @@ void RandomizedIdempotencyTest::runUpdateV2IdempotencyTestCase(double v2Probabil
// computing the input objects) would break idempotency. So we do a dry run of what
// the collection state would look like and compute diffs based on that.
generatedDoc = generateDocWithId(kDocId);
- auto diff = doc_diff::computeDiff(
- oldDoc, *generatedDoc, update_oplog_entry::kSizeOfDeltaOplogEntryMetadata);
- ASSERT(diff);
- oplogDiff = BSON("$v" << 2 << "diff" << *diff);
+ auto diffOutput =
+ doc_diff::computeDiff(oldDoc,
+ *generatedDoc,
+ update_oplog_entry::kSizeOfDeltaOplogEntryMetadata,
+ nullptr);
+ ASSERT(diffOutput);
+ oplogDiff = BSON("$v" << 2 << "diff" << diffOutput->diff);
} else {
oplogDiff = updateV1Generator.generateUpdate();
}
diff --git a/src/mongo/db/update/document_diff_calculator.cpp b/src/mongo/db/update/document_diff_calculator.cpp
index 24a4a989d0e..dcc6309d7c0 100644
--- a/src/mongo/db/update/document_diff_calculator.cpp
+++ b/src/mongo/db/update/document_diff_calculator.cpp
@@ -29,6 +29,7 @@
#include "mongo/platform/basic.h"
+#include "mongo/base/checked_cast.h"
#include "mongo/db/update/document_diff_calculator.h"
namespace mongo::doc_diff {
@@ -156,15 +157,75 @@ void calculateSubDiffHelper(const BSONElement& preVal,
diffNode->addChild(fieldIdentifier, std::move(subDiff));
}
}
+
+class StringWrapper {
+public:
+ StringWrapper(size_t s) : storage(std::to_string(s)), str(storage) {}
+ StringWrapper(StringData s) : str(s) {}
+
+ StringData getStr() {
+ return str;
+ }
+
+private:
+ std::string storage;
+ StringData str;
+};
+
+template <class DiffNode>
+bool anyIndexesMightBeAffected(const DiffNode* node,
+ const UpdateIndexData* indexData,
+ FieldRef* path) {
+ for (auto&& [field, child] : node->getChildren()) {
+ // The 'field' here can either be an integer or a string.
+ StringWrapper wrapper(field);
+ FieldRef::FieldRefTempAppend tempAppend(*path, wrapper.getStr());
+ switch (child->type()) {
+ case diff_tree::NodeType::kDelete:
+ case diff_tree::NodeType::kUpdate:
+ case diff_tree::NodeType::kInsert: {
+ if (indexData && indexData->mightBeIndexed(*path)) {
+ return true;
+ }
+ break;
+ }
+ case diff_tree::NodeType::kDocumentSubDiff: {
+ if (anyIndexesMightBeAffected<diff_tree::DocumentSubDiffNode>(
+ checked_cast<const diff_tree::DocumentSubDiffNode*>(child.get()),
+ indexData,
+ path)) {
+ return true;
+ }
+ break;
+ }
+ case diff_tree::NodeType::kArray: {
+ auto* arrayNode = checked_cast<const diff_tree::ArrayNode*>(child.get());
+ if ((arrayNode->getResize() && indexData && indexData->mightBeIndexed(*path)) ||
+ anyIndexesMightBeAffected<diff_tree::ArrayNode>(arrayNode, indexData, path)) {
+ return true;
+ }
+ break;
+ }
+ case diff_tree::NodeType::kDocumentInsert: {
+ MONGO_UNREACHABLE;
+ }
+ }
+ }
+ return false;
+}
} // namespace
-boost::optional<doc_diff::Diff> computeDiff(const BSONObj& pre,
- const BSONObj& post,
- size_t padding) {
+boost::optional<DiffResult> computeDiff(const BSONObj& pre,
+ const BSONObj& post,
+ size_t padding,
+ const UpdateIndexData* indexData) {
if (auto diffNode = computeDocDiff(pre, post, padding)) {
auto diff = diffNode->serialize();
if (diff.objsize() < post.objsize()) {
- return diff;
+ FieldRef path;
+ return DiffResult{diff,
+ anyIndexesMightBeAffected<diff_tree::DocumentSubDiffNode>(
+ diffNode.get(), indexData, &path)};
}
}
return {};
diff --git a/src/mongo/db/update/document_diff_calculator.h b/src/mongo/db/update/document_diff_calculator.h
index e5329da24f9..620def1798e 100644
--- a/src/mongo/db/update/document_diff_calculator.h
+++ b/src/mongo/db/update/document_diff_calculator.h
@@ -31,17 +31,27 @@
#include "mongo/bson/bsonobj.h"
#include "mongo/db/update/document_diff_serialization.h"
+#include "mongo/db/update_index_data.h"
namespace mongo::doc_diff {
+
+struct DiffResult {
+ Diff diff;
+ bool indexesAffected; // Whether the index data need to be modified if the diff is applied.
+};
+
/**
* Returns the delta between 'pre' and 'post' by recursively iterating the object. If the size
* of the computed delta is larger than the 'post' object then the function returns
* 'boost::none'. The 'paddingForDiff' represents the additional size that needs be added to the
- * size of the diff, while comparing whether the diff is viable.
+ * size of the diff, while comparing whether the diff is viable. If any paths in 'indexData' are
+ * affected by the generated diff, then the 'indexesAffected' field in the output will be set to
+ * true, false otherwise.
*/
-boost::optional<doc_diff::Diff> computeDiff(const BSONObj& pre,
- const BSONObj& post,
- size_t paddingForDiff);
+boost::optional<DiffResult> computeDiff(const BSONObj& pre,
+ const BSONObj& post,
+ size_t paddingForDiff,
+ const UpdateIndexData* indexData);
}; // namespace mongo::doc_diff
diff --git a/src/mongo/db/update/document_diff_calculator_test.cpp b/src/mongo/db/update/document_diff_calculator_test.cpp
index a2b441ff310..55a05051021 100644
--- a/src/mongo/db/update/document_diff_calculator_test.cpp
+++ b/src/mongo/db/update/document_diff_calculator_test.cpp
@@ -39,11 +39,12 @@
namespace mongo {
namespace {
+
TEST(DocumentDiffCalculatorTest, SameObjectsNoDiff) {
auto assertDiffEmpty = [](const BSONObj& doc) {
- auto diff = doc_diff::computeDiff(doc, doc, 5);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff, BSONObj());
+ auto diffOutput = doc_diff::computeDiff(doc, doc, 5, nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, BSONObj());
};
assertDiffEmpty(fromjson("{field1: 1}"));
assertDiffEmpty(fromjson("{field1: []}"));
@@ -55,43 +56,47 @@ TEST(DocumentDiffCalculatorTest, SameObjectsNoDiff) {
}
TEST(DocumentDiffCalculatorTest, LargeDelta) {
- ASSERT_FALSE(
- doc_diff::computeDiff(fromjson("{a: 1, b: 2, c: 3}"), fromjson("{A: 1, B: 2, C: 3}"), 0));
+ ASSERT_FALSE(doc_diff::computeDiff(
+ fromjson("{a: 1, b: 2, c: 3}"), fromjson("{A: 1, B: 2, C: 3}"), 0, nullptr));
// Inserting field at the beginning produces a large delta.
ASSERT_FALSE(doc_diff::computeDiff(
- fromjson("{b: 1, c: 1, d: 1}"), fromjson("{a: 1, b: 1, c: 1, d: 1}"), 0));
+ fromjson("{b: 1, c: 1, d: 1}"), fromjson("{a: 1, b: 1, c: 1, d: 1}"), 0, nullptr));
// Empty objects.
- ASSERT_FALSE(doc_diff::computeDiff(BSONObj(), BSONObj(), 1));
+ ASSERT_FALSE(doc_diff::computeDiff(BSONObj(), BSONObj(), 1, nullptr));
// Full diff.
- ASSERT_FALSE(doc_diff::computeDiff(fromjson("{b: 1}"), BSONObj(), 0));
- ASSERT_FALSE(doc_diff::computeDiff(BSONObj(), fromjson("{b: 1}"), 0));
+ ASSERT_FALSE(doc_diff::computeDiff(fromjson("{b: 1}"), BSONObj(), 0, nullptr));
+ ASSERT_FALSE(doc_diff::computeDiff(BSONObj(), fromjson("{b: 1}"), 0, nullptr));
}
TEST(DocumentDiffCalculatorTest, DeleteAndInsertFieldAtTheEnd) {
- auto diff = doc_diff::computeDiff(fromjson("{a: 1, b: 'valueString', c: 3, d: 4}"),
- fromjson("{b: 'valueString', c: 3, d: 4, a: 1}"),
- 15);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff, fromjson("{i: {a: 1}}"));
+ auto diffOutput = doc_diff::computeDiff(fromjson("{a: 1, b: 'valueString', c: 3, d: 4}"),
+ fromjson("{b: 'valueString', c: 3, d: 4, a: 1}"),
+ 15,
+ nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, fromjson("{i: {a: 1}}"));
}
TEST(DocumentDiffCalculatorTest, DeletesRecordedInAscendingOrderOfFieldNames) {
- auto diff = doc_diff::computeDiff(fromjson("{b: 1, a: 2, c: 3, d: 4, e: 'valueString'}"),
- fromjson("{c: 3, d: 4, e: 'valueString'}"),
- 15);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff, fromjson("{d: {a: false, b: false}}"));
+ auto diffOutput = doc_diff::computeDiff(fromjson("{b: 1, a: 2, c: 3, d: 4, e: 'valueString'}"),
+ fromjson("{c: 3, d: 4, e: 'valueString'}"),
+ 15,
+ nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, fromjson("{d: {a: false, b: false}}"));
// Delete at the end still follow the order.
- diff = doc_diff::computeDiff(
+ diffOutput = doc_diff::computeDiff(
fromjson("{b: 1, a: 2, c: 'value', d: 'valueString', e: 'valueString', g: 1, f: 1}"),
fromjson("{c: 'value', d: 'valueString', e: 'valueString'}"),
- 15);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff, fromjson("{d: {g: false, f: false, a: false, b: false}}"));
+ 15,
+ nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff,
+ fromjson("{d: {g: false, f: false, a: false, b: false}}"));
}
TEST(DocumentDiffCalculatorTest, DataTypeChangeRecorded) {
@@ -101,150 +106,168 @@ TEST(DocumentDiffCalculatorTest, DataTypeChangeRecorded) {
fromjson("{a: 'valueString', b: 2, c: {subField1: 1, subField2: 3}, d: 4}");
const auto objWithLongValue =
fromjson("{a: 'valueString', b: 2, c: {subField1: 1, subField2: NumberLong(3)}, d: 4}");
- auto diff = doc_diff::computeDiff(objWithDoubleValue, objWithIntValue, 15);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff, fromjson("{sc: {u: {subField2: 3}} }"));
+ auto diffOutput = doc_diff::computeDiff(objWithDoubleValue, objWithIntValue, 15, nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, fromjson("{sc: {u: {subField2: 3}} }"));
- diff = doc_diff::computeDiff(objWithIntValue, objWithLongValue, 15);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff, fromjson("{sc: {u: {subField2: NumberLong(3)}} }"));
+ diffOutput = doc_diff::computeDiff(objWithIntValue, objWithLongValue, 15, nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, fromjson("{sc: {u: {subField2: NumberLong(3)}} }"));
- diff = doc_diff::computeDiff(objWithLongValue, objWithDoubleValue, 15);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff, fromjson("{sc: {u: {subField2: 3.0}} }"));
+ diffOutput = doc_diff::computeDiff(objWithLongValue, objWithDoubleValue, 15, nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, fromjson("{sc: {u: {subField2: 3.0}} }"));
}
TEST(DocumentDiffCalculatorTest, NullAndMissing) {
- auto diff = doc_diff::computeDiff(fromjson("{a: null}"), fromjson("{}"), 15);
- ASSERT_FALSE(diff);
+ auto diffOutput = doc_diff::computeDiff(fromjson("{a: null}"), fromjson("{}"), 15, nullptr);
+ ASSERT_FALSE(diffOutput);
- diff =
+ diffOutput =
doc_diff::computeDiff(fromjson("{a: null, b: undefined, c: null, d: 'someValueStr'}"),
fromjson("{a: null, b: undefined, c: undefined, d: 'someValueStr'}"),
- 15);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff, fromjson("{u: {c: undefined}}"));
+ 15,
+ nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, fromjson("{u: {c: undefined}}"));
}
TEST(DocumentDiffCalculatorTest, FieldOrder) {
- auto diff = doc_diff::computeDiff(fromjson("{a: 1, b: 2, c: {p: 1, q: 1, s: 1, r: 2}, d: 4}"),
- fromjson("{a: 1, b: 2, c: {p: 1, q: 1, r: 2, s: 1}, d: 4}"),
- 10);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff, fromjson("{sc: {i: {s: 1}} }"));
+ auto diffOutput =
+ doc_diff::computeDiff(fromjson("{a: 1, b: 2, c: {p: 1, q: 1, s: 1, r: 2}, d: 4}"),
+ fromjson("{a: 1, b: 2, c: {p: 1, q: 1, r: 2, s: 1}, d: 4}"),
+ 10,
+ nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, fromjson("{sc: {i: {s: 1}} }"));
}
TEST(DocumentDiffCalculatorTest, SimpleArrayPush) {
- auto diff = doc_diff::computeDiff(fromjson("{field1: 'abcd', field2: [1, 2, 3]}"),
- fromjson("{field1: 'abcd', field2: [1, 2, 3, 4]}"),
- 5);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff, fromjson("{sfield2: {a: true, 'u3': 4}}"));
+ auto diffOutput = doc_diff::computeDiff(fromjson("{field1: 'abcd', field2: [1, 2, 3]}"),
+ fromjson("{field1: 'abcd', field2: [1, 2, 3, 4]}"),
+ 5,
+ nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, fromjson("{sfield2: {a: true, 'u3': 4}}"));
}
TEST(DocumentDiffCalculatorTest, NestedArray) {
- auto diff = doc_diff::computeDiff(fromjson("{field1: 'abcd', field2: [1, 2, 3, [[2]]]}"),
- fromjson("{field1: 'abcd', field2: [1, 2, 3, [[4]]]}"),
- 0);
- ASSERT(diff);
+ auto diffOutput = doc_diff::computeDiff(fromjson("{field1: 'abcd', field2: [1, 2, 3, [[2]]]}"),
+ fromjson("{field1: 'abcd', field2: [1, 2, 3, [[4]]]}"),
+ 0,
+ nullptr);
+ ASSERT(diffOutput);
// When the sub-array delta is larger than the size of the sub-array, we record it as an update
// operation.
- ASSERT_BSONOBJ_BINARY_EQ(*diff, fromjson("{sfield2: {a: true, 'u3': [[4]]}}"));
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, fromjson("{sfield2: {a: true, 'u3': [[4]]}}"));
- diff = doc_diff::computeDiff(
+ diffOutput = doc_diff::computeDiff(
fromjson("{field1: 'abcd', field2: [1, 2, 3, [1, 'longString', [2], 4, 5, 6], 5, 5, 5]}"),
fromjson("{field1: 'abcd', field2: [1, 2, 3, [1, 'longString', [4], 4], 5, 6]}"),
- 0);
- ASSERT(diff);
+ 0,
+ nullptr);
+ ASSERT(diffOutput);
ASSERT_BSONOBJ_BINARY_EQ(
- *diff, fromjson("{sfield2: {a: true, l: 6, 's3': {a: true, l: 4, 'u2': [4]}, 'u5': 6}}"));
+ diffOutput->diff,
+ fromjson("{sfield2: {a: true, l: 6, 's3': {a: true, l: 4, 'u2': [4]}, 'u5': 6}}"));
}
TEST(DocumentDiffCalculatorTest, SubObjHasEmptyFieldName) {
- auto diff = doc_diff::computeDiff(
+ auto diffOutput = doc_diff::computeDiff(
fromjson("{'': '1', field2: [1, 2, 3, {'': {'': 1, padding: 'largeFieldValue'}}]}"),
fromjson("{'': '2', field2: [1, 2, 3, {'': {'': 2, padding: 'largeFieldValue'}}]}"),
- 15);
+ 15,
+ nullptr);
- ASSERT(diff);
+ ASSERT(diffOutput);
ASSERT_BSONOBJ_BINARY_EQ(
- *diff, fromjson("{u: {'': '2'}, sfield2: {a: true, s3: {s: {u: {'': 2}}} }}"));
+ diffOutput->diff, fromjson("{u: {'': '2'}, sfield2: {a: true, s3: {s: {u: {'': 2}}} }}"));
}
TEST(DocumentDiffCalculatorTest, SubObjInSubArrayUpdateElements) {
- auto diff = doc_diff::computeDiff(
+ auto diffOutput = doc_diff::computeDiff(
fromjson("{field1: 'abcd', field2: [1, 2, 3, "
"{field3: ['veryLargeStringValueveryLargeStringValue', 2, 3, 4]}]}"),
fromjson("{field1: 'abcd', field2: [1, 2, 3, {'field3': "
"['veryLargeStringValueveryLargeStringValue', 2, 4, 3, 5]}]}"),
- 0);
+ 0,
+ nullptr);
- ASSERT(diff);
+ ASSERT(diffOutput);
ASSERT_BSONOBJ_BINARY_EQ(
- *diff, fromjson("{sfield2: {a: true, s3: {sfield3: {a: true, u2: 4, u3: 3, u4: 5}} }}"));
+ diffOutput->diff,
+ fromjson("{sfield2: {a: true, s3: {sfield3: {a: true, u2: 4, u3: 3, u4: 5}} }}"));
}
TEST(DocumentDiffCalculatorTest, SubObjInSubArrayDeleteElements) {
- auto diff = doc_diff::computeDiff(
+ auto diffOutput = doc_diff::computeDiff(
fromjson("{field1: 'abcd', field2: [1, 2, 3, {'field3': ['largeString', 2, 3, 4, 5]}]}"),
fromjson("{field1: 'abcd', field2: [1, 2, 3, {'field3': ['largeString', 2, 3, 5]}]}"),
- 15);
- ASSERT(diff);
+ 15,
+ nullptr);
+ ASSERT(diffOutput);
ASSERT_BSONOBJ_BINARY_EQ(
- *diff, fromjson("{sfield2: {a: true, 's3': {sfield3: {a: true, l: 4, 'u3': 5}} }}"));
+ diffOutput->diff,
+ fromjson("{sfield2: {a: true, 's3': {sfield3: {a: true, l: 4, 'u3': 5}} }}"));
}
TEST(DocumentDiffCalculatorTest, NestedSubObjs) {
- auto diff = doc_diff::computeDiff(
+ auto diffOutput = doc_diff::computeDiff(
fromjson("{level1Field1: 'abcd', level1Field2: {level2Field1: {level3Field1: {p: 1}, "
"level3Field2: {q: 1}}, level2Field2: 2}, level1Field3: 3, level1Field4: "
"{level2Field1: {level3Field1: {p: 1}, level3Field2: {q: 1}}} }"),
fromjson("{level1Field1: 'abcd', level1Field2: {level2Field1: {level3Field1: {q: 1}, "
"level3Field2: {q: 1}}, level2Field2: 2}, level1Field3: '3', level1Field4: "
"{level2Field1: {level3Field1: {q: 1}, level3Field2: {q: 1}}} }"),
- 15);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff,
+ 15,
+ nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff,
fromjson("{u: {level1Field3: '3'}, slevel1Field2: {slevel2Field1: {u: "
"{level3Field1: {q: 1}}}}, slevel1Field4: {slevel2Field1: "
"{u: {level3Field1: {q: 1}}}} }"));
}
TEST(DocumentDiffCalculatorTest, SubArrayInSubArrayLargeDelta) {
- auto diff = doc_diff::computeDiff(
+ auto diffOutput = doc_diff::computeDiff(
fromjson("{field1: 'abcd', field2: [1, 2, 3, {field3: [2, 3, 4, 5]}]}"),
fromjson("{field1: 'abcd', field2: [1, 2, 3, {field3: [1, 2, 3, 4, 5]}]}"),
- 15);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff,
+ 15,
+ nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff,
fromjson("{sfield2: {a: true, 'u3': {field3: [1, 2, 3, 4, 5]}} }"));
}
TEST(DocumentDiffCalculatorTest, SubObjInSubArrayLargeDelta) {
- auto diff =
+ auto diffOutput =
doc_diff::computeDiff(fromjson("{field1: [1, 2, 3, 4, 5, 6, {a: 1, b: 2, c: 3, d: 4}, 7]}"),
fromjson("{field1: [1, 2, 3, 4, 5, 6, {p: 1, q: 2}, 7]}"),
- 0);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff, fromjson("{sfield1: {a: true, 'u6': {p: 1, q: 2}} }"));
+ 0,
+ nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff,
+ fromjson("{sfield1: {a: true, 'u6': {p: 1, q: 2}} }"));
}
TEST(DocumentDiffCalculatorTest, SubObjInSubObjLargeDelta) {
- auto diff = doc_diff::computeDiff(
+ auto diffOutput = doc_diff::computeDiff(
fromjson("{field: {p: 'someString', q: 2, r: {a: 1, b: 2, c: 3, 'd': 4}, s: 3}}"),
fromjson("{field: {p: 'someString', q: 2, r: {p: 1, q: 2}, s: 3}}"),
- 0);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff, fromjson("{sfield: {u: {r: {p: 1, q: 2} }} }"));
+ 0,
+ nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, fromjson("{sfield: {u: {r: {p: 1, q: 2} }} }"));
}
TEST(DocumentDiffCalculatorTest, SubArrayInSubObjLargeDelta) {
- auto diff =
+ auto diffOutput =
doc_diff::computeDiff(fromjson("{field: {p: 'someString', q: 2, r: [1, 3, 4, 5], s: 3}}"),
fromjson("{field: {p: 'someString', q: 2, r: [1, 2, 3, 4], s: 3}}"),
- 0);
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff, fromjson("{sfield: {u: {r: [1, 2, 3, 4]}} }"));
+ 0,
+ nullptr);
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, fromjson("{sfield: {u: {r: [1, 2, 3, 4]}} }"));
}
void buildDeepObj(BSONObjBuilder* builder,
@@ -276,20 +299,20 @@ TEST(DocumentDiffCalculatorTest, DeeplyNestObjectGenerateDiff) {
BSONObjBuilder postBob;
postBob.append("largeField", largeValue);
- auto diff = doc_diff::computeDiff(preObj, postBob.done(), 0);
+ auto diffOutput = doc_diff::computeDiff(preObj, postBob.done(), 0, nullptr);
// Just deleting the deeply nested sub-object should give the post object.
- ASSERT(diff);
- ASSERT_BSONOBJ_BINARY_EQ(*diff, fromjson("{d: {subObj: false}}"));
+ ASSERT(diffOutput);
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, fromjson("{d: {subObj: false}}"));
BSONObjBuilder postBob2;
postBob2.append("largeField", largeValue);
buildDeepObj(&postBob2, "subObj", 0, maxDepth - 1, functionToApply);
// Deleting the deepest field should give the post object.
- diff = doc_diff::computeDiff(preObj, postBob2.done(), 0);
- ASSERT(diff);
- ASSERT(diff->valid());
+ diffOutput = doc_diff::computeDiff(preObj, postBob2.done(), 0, nullptr);
+ ASSERT(diffOutput);
+ ASSERT(diffOutput->diff.valid());
BSONObjBuilder expectedOutputBuilder;
buildDeepObj(&expectedOutputBuilder,
@@ -301,7 +324,7 @@ TEST(DocumentDiffCalculatorTest, DeeplyNestObjectGenerateDiff) {
builder->append("d", BSON("subObj" << false));
}
});
- ASSERT_BSONOBJ_BINARY_EQ(*diff, expectedOutputBuilder.obj());
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, expectedOutputBuilder.obj());
}
TEST(DocumentDiffCalculatorTest, DeepestObjectSubDiff) {
@@ -326,9 +349,9 @@ TEST(DocumentDiffCalculatorTest, DeepestObjectSubDiff) {
auto postObj = postBob.done();
ASSERT(postObj.valid());
- auto diff = doc_diff::computeDiff(preObj, postObj, 0);
- ASSERT(diff);
- ASSERT(diff->valid());
+ auto diffOutput = doc_diff::computeDiff(preObj, postObj, 0, nullptr);
+ ASSERT(diffOutput);
+ ASSERT(diffOutput->diff.valid());
BSONObjBuilder expectedOutputBuilder;
buildDeepObj(&expectedOutputBuilder,
@@ -340,7 +363,7 @@ TEST(DocumentDiffCalculatorTest, DeepestObjectSubDiff) {
builder->append("u", BSON("field" << 2));
}
});
- ASSERT_BSONOBJ_BINARY_EQ(*diff, expectedOutputBuilder.obj());
+ ASSERT_BSONOBJ_BINARY_EQ(diffOutput->diff, expectedOutputBuilder.obj());
}
} // namespace
diff --git a/src/mongo/db/update/document_diff_serialization.cpp b/src/mongo/db/update/document_diff_serialization.cpp
index ba7ac3964d6..9fac816d460 100644
--- a/src/mongo/db/update/document_diff_serialization.cpp
+++ b/src/mongo/db/update/document_diff_serialization.cpp
@@ -268,7 +268,6 @@ public:
: _node(node), _bob(std::move(bob)), _childIt(node.getChildren().begin()) {}
UniqueFrame execute() override {
- invariant(!_node.getChildren().empty());
if (_childIt == _node.getChildren().begin()) {
// If this is the first execution of this frame, append array header and resize field if
// present.
@@ -279,7 +278,6 @@ public:
}
for (; _childIt != _node.getChildren().end(); ++_childIt) {
-
auto&& [idx, child] = *_childIt;
auto idxAsStr = std::to_string(idx);
diff --git a/src/mongo/db/update/document_diff_serialization.h b/src/mongo/db/update/document_diff_serialization.h
index 9d54825298b..5a1697169e8 100644
--- a/src/mongo/db/update/document_diff_serialization.h
+++ b/src/mongo/db/update/document_diff_serialization.h
@@ -269,6 +269,11 @@ public:
return subDiffs;
}
+ const absl::node_hash_map<std::string, std::unique_ptr<Node>, StringMapHasher, StringMapEq>&
+ getChildren() const {
+ return children;
+ }
+
private:
// We store the raw pointer to each of the child node so that we don't have to look up in
// 'children' map every time. Note that the field names of these modifications will reference
diff --git a/src/mongo/db/update/document_diff_test.cpp b/src/mongo/db/update/document_diff_test.cpp
index 229d163a954..87e9aa594a6 100644
--- a/src/mongo/db/update/document_diff_test.cpp
+++ b/src/mongo/db/update/document_diff_test.cpp
@@ -110,16 +110,16 @@ void runTest(std::vector<BSONObj> documents, size_t numSimulations) {
std::vector<BSONObj> diffs;
diffs.reserve(documents.size() - 1);
for (size_t i = 1; i < documents.size(); ++i) {
- const auto diff = computeDiff(
- preDoc, documents[i], update_oplog_entry::kSizeOfDeltaOplogEntryMetadata);
+ const auto diffOutput = computeDiff(
+ preDoc, documents[i], update_oplog_entry::kSizeOfDeltaOplogEntryMetadata, nullptr);
- ASSERT(diff);
- diffs.push_back(*diff);
- const auto postObj = applyDiffTestHelper(preDoc, *diff);
+ ASSERT(diffOutput);
+ diffs.push_back(diffOutput->diff);
+ const auto postObj = applyDiffTestHelper(preDoc, diffOutput->diff);
ASSERT_BSONOBJ_BINARY_EQ(documents[i], postObj);
// Applying the diff the second time also generates the same object.
- ASSERT_BSONOBJ_BINARY_EQ(postObj, applyDiffTestHelper(postObj, *diff));
+ ASSERT_BSONOBJ_BINARY_EQ(postObj, applyDiffTestHelper(postObj, diffOutput->diff));
preDoc = documents[i];
}
diff --git a/src/mongo/db/update/pipeline_executor.cpp b/src/mongo/db/update/pipeline_executor.cpp
index 910470f994d..d5d62bcee23 100644
--- a/src/mongo/db/update/pipeline_executor.cpp
+++ b/src/mongo/db/update/pipeline_executor.cpp
@@ -102,6 +102,7 @@ UpdateExecutor::ApplyResult PipelineExecutor::applyUpdate(ApplyParams applyParam
// post image.
auto ret = ObjectReplaceExecutor::applyReplacementUpdate(
applyParams, transformedDoc, transformedDocHasIdField);
+
// The oplog entry should not have been populated yet.
invariant(ret.oplogEntry.isEmpty());
@@ -109,10 +110,14 @@ UpdateExecutor::ApplyResult PipelineExecutor::applyUpdate(ApplyParams applyParam
if (applyParams.logMode == ApplyParams::LogMode::kGenerateOplogEntry) {
// We're allowed to generate $v: 2 log entries. The $v:2 has certain meta-fields like
// '$v', 'diff'. So we pad some additional byte while computing diff.
- const auto diff = doc_diff::computeDiff(
- originalDoc, transformedDoc, update_oplog_entry::kSizeOfDeltaOplogEntryMetadata);
- if (diff) {
- ret.oplogEntry = update_oplog_entry::makeDeltaOplogEntry(*diff);
+ const auto diffOutput =
+ doc_diff::computeDiff(originalDoc,
+ transformedDoc,
+ update_oplog_entry::kSizeOfDeltaOplogEntryMetadata,
+ applyParams.indexData);
+ if (diffOutput) {
+ ret.oplogEntry = update_oplog_entry::makeDeltaOplogEntry(diffOutput->diff);
+ ret.indexesAffected = diffOutput->indexesAffected;
return ret;
}
}
diff --git a/src/mongo/db/update/pipeline_executor_test.cpp b/src/mongo/db/update/pipeline_executor_test.cpp
index 496b99ef3ce..2fbcb6e2d7c 100644
--- a/src/mongo/db/update/pipeline_executor_test.cpp
+++ b/src/mongo/db/update/pipeline_executor_test.cpp
@@ -74,10 +74,18 @@ public:
return _allowDeltaOplogEntries;
}
-private:
+protected:
bool _allowDeltaOplogEntries = false;
};
+class PipelineExecutorV2ModeTest : public PipelineExecutorTest {
+public:
+ void run() {
+ _allowDeltaOplogEntries = true;
+ UpdateNodeTest::run();
+ }
+};
+
TEST_F(PipelineExecutorTest, Noop) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
@@ -102,12 +110,13 @@ TEST_F(PipelineExecutorTest, ShouldNotCreateIdIfNoIdExistsAndNoneIsSpecified) {
mutablebson::Document doc(fromjson("{c: 1, d: 'largeStringValue'}"));
auto result = exec.applyUpdate(getApplyParams(doc.root()));
ASSERT_FALSE(result.noop);
- ASSERT_TRUE(result.indexesAffected);
ASSERT_EQUALS(fromjson("{c: 1, d: 'largeStringValue', a: 1, b: 2}"), doc);
ASSERT_FALSE(doc.isInPlaceModeEnabled());
if (deltaOplogEntryAllowed()) {
+ ASSERT_FALSE(result.indexesAffected);
ASSERT_BSONOBJ_BINARY_EQ(fromjson("{$v: 2, diff: {i: {a: 1, b: 2}}}"), result.oplogEntry);
} else {
+ ASSERT_TRUE(result.indexesAffected);
ASSERT_BSONOBJ_BINARY_EQ(fromjson("{c: 1, d: 'largeStringValue', a: 1, b: 2}"),
result.oplogEntry);
}
@@ -139,12 +148,14 @@ TEST_F(PipelineExecutorTest, ShouldSucceedWhenImmutableIdIsNotModified) {
addImmutablePath("_id");
auto result = exec.applyUpdate(getApplyParams(doc.root()));
ASSERT_FALSE(result.noop);
- ASSERT_TRUE(result.indexesAffected);
+
ASSERT_EQUALS(fromjson("{_id: 0, c: 1, d: 'largeStringValue', a: 1, b: 2}"), doc);
ASSERT_FALSE(doc.isInPlaceModeEnabled());
if (deltaOplogEntryAllowed()) {
+ ASSERT_FALSE(result.indexesAffected);
ASSERT_BSONOBJ_BINARY_EQ(fromjson("{$v: 2, diff: {i: {a: 1, b: 2 }}}"), result.oplogEntry);
} else {
+ ASSERT_TRUE(result.indexesAffected);
ASSERT_BSONOBJ_BINARY_EQ(fromjson("{_id: 0, c: 1, d: 'largeStringValue', a: 1, b: 2}"),
result.oplogEntry);
}
@@ -159,13 +170,15 @@ TEST_F(PipelineExecutorTest, ComplexDoc) {
mutablebson::Document doc(fromjson("{a: 1, b: [0, 2, 2], e: ['val1', 'val2']}"));
auto result = exec.applyUpdate(getApplyParams(doc.root()));
ASSERT_FALSE(result.noop);
- ASSERT_TRUE(result.indexesAffected);
+
ASSERT_EQUALS(fromjson("{a: 1, b: [0, 1, 2], e: ['val1', 'val2'], c: {d: 1}}"), doc);
ASSERT_FALSE(doc.isInPlaceModeEnabled());
if (deltaOplogEntryAllowed()) {
+ ASSERT_FALSE(result.indexesAffected);
ASSERT_BSONOBJ_BINARY_EQ(fromjson("{$v: 2, diff: {i: {c: {d: 1}}, sb: {a: true, u1: 1} }}"),
result.oplogEntry);
} else {
+ ASSERT_TRUE(result.indexesAffected);
ASSERT_BSONOBJ_BINARY_EQ(fromjson("{a: 1, b: [0, 1, 2], e: ['val1', 'val2'], c: {d: 1}}"),
result.oplogEntry);
}
@@ -409,5 +422,296 @@ TEST_F(PipelineExecutorTest, NoopWithConstants) {
ASSERT_TRUE(result.oplogEntry.isEmpty());
}
+TEST_F(PipelineExecutorV2ModeTest, TestIndexesAffectedWithDeletes) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ BSONObj preImage(
+ fromjson("{f1: {a: {b: {c: 1, paddingField: 'largeValueString'}, c: 1, paddingField: "
+ "'largeValueString'}}, paddingField: 'largeValueString'}"));
+ addIndexedPath("p.a.b");
+ addIndexedPath("f1.a.b");
+ {
+ // When a path in the diff is a prefix of index path.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{fromjson("{$unset: ['f1', 'f2', 'f3']}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify post-image and diff format.
+ ASSERT_EQUALS(doc, fromjson("{paddingField: 'largeValueString'}"));
+ ASSERT_BSONOBJ_BINARY_EQ(result.oplogEntry, fromjson("{$v: 2, diff: {d: {f1: false}}}"));
+ ASSERT(result.indexesAffected);
+ }
+ {
+ // When a path in the diff is same as index path.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{fromjson("{$unset: ['f1.a.p', 'f1.a.c', 'f1.a.b']}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify post-image and diff format.
+ ASSERT_EQUALS(
+ doc,
+ fromjson(
+ "{f1: {a: {paddingField: 'largeValueString'}}, paddingField: 'largeValueString'}"));
+ ASSERT_BSONOBJ_BINARY_EQ(result.oplogEntry,
+ fromjson("{$v: 2, diff: {sf1: {sa: {d: {b: false, c: false}}}}}"));
+ ASSERT(result.indexesAffected);
+ }
+ {
+ // When the index path is a prefix of a path in the diff.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{fromjson("{$unset: 'f1.a.b.c'}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify post-image and diff format.
+ ASSERT_BSONOBJ_BINARY_EQ(result.oplogEntry,
+ fromjson("{$v: 2, diff: {sf1: {sa: {sb: {d: {c: false}}}}}}"));
+ ASSERT_EQUALS(
+ doc,
+ fromjson("{f1: {a: {b: {paddingField: 'largeValueString'}, c: 1, paddingField: "
+ "'largeValueString'}}, paddingField: 'largeValueString'}"));
+ ASSERT(result.indexesAffected);
+ }
+ {
+ // With common parent, but path diverges.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{fromjson("{$unset: 'f1.a.c'}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify post-image and diff format.
+ ASSERT_BSONOBJ_BINARY_EQ(result.oplogEntry,
+ fromjson("{$v: 2, diff: {sf1: {sa: {d: {c: false}}}}}"));
+ ASSERT_EQUALS(
+ doc,
+ fromjson("{f1: {a: {b: {c: 1, paddingField: 'largeValueString'}, paddingField: "
+ "'largeValueString'}}, paddingField: 'largeValueString'}"));
+ ASSERT(!result.indexesAffected);
+ }
+}
+
+TEST_F(PipelineExecutorV2ModeTest, TestIndexesAffectedWithUpdatesAndInserts) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ BSONObj preImage(
+ fromjson("{f1: {a: {b: {c: 1, paddingField: 'largeValueString'}, c: 1, paddingField: "
+ "'largeValueString'}}, paddingField: 'largeValueString'}"));
+ addIndexedPath("p.a.b");
+ addIndexedPath("f1.a.b");
+ addIndexedPath("f1.a.newField");
+ {
+ // When a path in the diff is a prefix of index path.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{fromjson("{$set: {f1: true, f2: true}}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify post-image and diff format.
+ ASSERT_EQUALS(doc, fromjson("{f1: true, paddingField: 'largeValueString', f2: true}"));
+ ASSERT_BSONOBJ_BINARY_EQ(result.oplogEntry,
+ fromjson("{$v: 2, diff: {u: {f1: true}, i: {f2: true}}}"));
+ ASSERT(result.indexesAffected);
+ }
+ {
+ // When a path in the diff is same as index path.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{fromjson("{$set: {'f1.a.newField': true}}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify diff format.
+ ASSERT_BSONOBJ_BINARY_EQ(result.oplogEntry,
+ fromjson("{$v: 2, diff: {sf1: {sa: {i: {newField: true}}}}}"));
+ ASSERT(result.indexesAffected);
+ }
+ {
+ // When the index path is a prefix of a path in the diff.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{fromjson("{$set: {'f1.a.b.c': true}}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify post-image and diff format.
+ ASSERT_BSONOBJ_BINARY_EQ(result.oplogEntry,
+ fromjson("{$v: 2, diff: {sf1: {sa: {sb: {u: {c: true}}}}}}"));
+ ASSERT_EQUALS(
+ doc,
+ fromjson(
+ "{f1: {a: {b: {c: true, paddingField: 'largeValueString'}, c: 1, paddingField: "
+ "'largeValueString'}}, paddingField: 'largeValueString'}"));
+ ASSERT(result.indexesAffected);
+ }
+ {
+ // With common parent, but path diverges.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{fromjson("{$set: {'f1.a.p': true}}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify post-image and diff format.
+ ASSERT_BSONOBJ_BINARY_EQ(result.oplogEntry,
+ fromjson("{$v: 2, diff: {sf1: {sa: {i: {p: true}}}}}"));
+ ASSERT_EQUALS(
+ doc,
+ fromjson("{f1: {a: {b: {c: 1, paddingField: 'largeValueString'}, c: 1, paddingField: "
+ "'largeValueString', p: true}}, paddingField: 'largeValueString'}"));
+ ASSERT(!result.indexesAffected);
+ }
+}
+
+TEST_F(PipelineExecutorV2ModeTest, TestIndexesAffectedWithArraysAlongIndexPath) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ BSONObj preImage(
+ fromjson("{f1: [0, {a: {b: ['someStringValue', {c: 1, paddingField: 'largeValueString'}], "
+ "c: 1, paddingField: 'largeValueString'}}], paddingField: 'largeValueString'}"));
+ addIndexedPath("p.a.b");
+ addIndexedPath("f1.a.2.b");
+
+ {
+ // Test resize.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{
+ fromjson("{$replaceWith: {f1: [0, {a: {b: ['someStringValue'], c: 1, paddingField: "
+ "'largeValueString'}}], paddingField: 'largeValueString'}}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify post-image and diff format.
+ ASSERT_EQUALS(
+ doc,
+ fromjson(
+ "{f1: [0, {a: {b: ['someStringValue'], c: 1, paddingField: 'largeValueString'}}], "
+ "paddingField: 'largeValueString'}"));
+ ASSERT_BSONOBJ_BINARY_EQ(
+ result.oplogEntry,
+ fromjson("{$v: 2, diff: {sf1: {a: true, s1: {sa: {sb: {a: true, l: 1}}}}}}"));
+ ASSERT(result.indexesAffected);
+ }
+ {
+ // When the index path is a prefix of a path in the diff and also involves numeric
+ // components along the way. The numeric components should always be ignored.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{
+ fromjson("{$replaceWith: {f1: [0, {a: {b: ['someStringValue', {c: 1, "
+ "paddingField:'largeValueString',d: 1}], c: 1, paddingField: "
+ "'largeValueString'}}], paddingField: 'largeValueString'}}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify post-image and diff format.
+ ASSERT_EQUALS(doc,
+ fromjson("{f1: [0, {a: {b: ['someStringValue', {c: 1, paddingField: "
+ "'largeValueString', d: 1}], c: 1, paddingField: "
+ "'largeValueString'}}], paddingField: 'largeValueString'}"));
+ ASSERT_BSONOBJ_BINARY_EQ(
+ result.oplogEntry,
+ fromjson(
+ "{$v: 2, diff: {sf1: {a: true, s1: {sa: {sb: {a: true, s1: {i: {d: 1} }}}}}}}"));
+ ASSERT(result.indexesAffected);
+ }
+ {
+ // When inserting a sub-object into array, and the sub-object diverges from the index path.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{
+ fromjson("{$set: {f1: {$concatArrays: ['$f1', [{newField: 1}]]}}}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify post-image and diff format.
+ ASSERT_EQUALS(
+ doc,
+ fromjson(
+ "{f1: [0, {a: {b: ['someStringValue', {c: 1, paddingField: 'largeValueString'}], "
+ "c: 1, paddingField: 'largeValueString'}}, {newField: 1}], paddingField: "
+ "'largeValueString'}"));
+ ASSERT_BSONOBJ_BINARY_EQ(result.oplogEntry,
+ fromjson("{$v: 2, diff: {sf1: {a: true, u2: {newField: 1} }}}"));
+ ASSERT(result.indexesAffected);
+ }
+ {
+ // When a common array path element is updated, but the paths diverge at the last element.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{
+ fromjson("{$replaceWith: {f1: [0, {a: {b: ['someStringValue', {c: 1, paddingField: "
+ "'largeValueString'}], c: 2, paddingField: 'largeValueString'}}], "
+ "paddingField: 'largeValueString'}}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify post-image and diff format.
+ ASSERT_BSONOBJ_BINARY_EQ(
+ result.oplogEntry, fromjson("{$v: 2, diff: {sf1: {a: true, s1: {sa: {u: {c: 2} }}}}}"));
+ ASSERT_EQUALS(
+ doc,
+ fromjson(
+ "{f1: [0, {a: {b: ['someStringValue', {c: 1, paddingField: 'largeValueString'}], "
+ "c: 2, paddingField: 'largeValueString'}}], paddingField: 'largeValueString'}"));
+ ASSERT(!result.indexesAffected);
+ }
+}
+
+TEST_F(PipelineExecutorV2ModeTest, TestIndexesAffectedWithArraysAfterIndexPath) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ BSONObj preImage(
+ fromjson("{f1: {a: {b: {c: [{paddingField: 'largeValueString'}, 1]}, c: 1, paddingField: "
+ "'largeValueString'}}, paddingField: 'largeValueString'}"));
+ addIndexedPath("p.a.b");
+ addIndexedPath("f1.a.2.b");
+
+ {
+ // Test resize.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{
+ fromjson("{$set: {'f1.a.b.c': [{paddingField: 'largeValueString'}]}}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify post-image and diff format.
+ ASSERT_EQUALS(
+ doc,
+ fromjson("{f1: {a: {b: {c: [{paddingField: 'largeValueString'}]}, c: 1, paddingField: "
+ "'largeValueString'}}, paddingField: 'largeValueString'}"));
+ ASSERT_BSONOBJ_BINARY_EQ(
+ result.oplogEntry, fromjson("{$v: 2, diff: {sf1: {sa: {sb: {sc: {a: true, l: 1}}}}}}"));
+ ASSERT(result.indexesAffected);
+ }
+ {
+ // Add an array element.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{
+ fromjson("{$set: {'f1.a.b.c': {$concatArrays: ['$f1.a.b.c', [{newField: 1}]]}}}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify post-image and diff format.
+ ASSERT_EQUALS(doc,
+ fromjson("{f1: {a: {b: {c: [{paddingField: 'largeValueString'}, 1, "
+ "{newField: 1}]}, c: 1, paddingField: 'largeValueString'}}, "
+ "paddingField: 'largeValueString'}"));
+ ASSERT_BSONOBJ_BINARY_EQ(
+ result.oplogEntry,
+ fromjson("{$v: 2, diff: {sf1: {sa: {sb: {sc: {a: true, u2: {newField: 1} }}}}}}"));
+ ASSERT(result.indexesAffected);
+ }
+ {
+ // Updating a sub-array element.
+ auto doc = mutablebson::Document(preImage);
+ const std::vector<BSONObj> pipeline{
+ fromjson("{$set: {'f1.a.b.c': [{paddingField: 'largeValueString'}, 'updatedVal']}}")};
+ PipelineExecutor test(expCtx, pipeline);
+ auto result = test.applyUpdate(getApplyParams(doc.root()));
+
+ // Verify post-image and diff format.
+ ASSERT_EQUALS(doc,
+ fromjson("{f1: {a: {b: {c: [{paddingField: 'largeValueString'}, "
+ "'updatedVal']}, c: 1, paddingField: 'largeValueString'}}, "
+ "paddingField: 'largeValueString'}"));
+ ASSERT_BSONOBJ_BINARY_EQ(
+ result.oplogEntry,
+ fromjson("{$v: 2, diff: {sf1: {sa: {sb: {sc: {a: true, u1: 'updatedVal'}}}}}}"));
+ ASSERT(result.indexesAffected);
+ }
+}
+
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/update/update_driver.cpp b/src/mongo/db/update/update_driver.cpp
index d82a2b1978c..9a729e7bcff 100644
--- a/src/mongo/db/update/update_driver.cpp
+++ b/src/mongo/db/update/update_driver.cpp
@@ -251,9 +251,7 @@ Status UpdateDriver::update(OperationContext* opCtx,
FieldRefSetWithStorage* modifiedPaths) {
// TODO: assert that update() is called at most once in a !_multi case.
- _affectIndices =
- (_updateType == UpdateType::kReplacement || _updateType == UpdateType::kPipeline) &&
- (_indexedFields != nullptr);
+ _affectIndices = _updateType == UpdateType::kReplacement && _indexedFields != nullptr;
_logDoc.reset();