diff options
author | Arun Banala <arun.banala@mongodb.com> | 2020-09-17 19:45:42 +0100 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-09-17 21:38:12 +0000 |
commit | 18545807095a982a8b527ec3eb1e45a447d06719 (patch) | |
tree | bad2ef2160ee81b9594536fde8cdda483b32f800 /src/mongo/db | |
parent | 63813e4afd4d8b7b0309c0ed07d946f128801848 (diff) | |
download | mongo-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.cpp | 11 | ||||
-rw-r--r-- | src/mongo/db/update/document_diff_calculator.cpp | 69 | ||||
-rw-r--r-- | src/mongo/db/update/document_diff_calculator.h | 18 | ||||
-rw-r--r-- | src/mongo/db/update/document_diff_calculator_test.cpp | 223 | ||||
-rw-r--r-- | src/mongo/db/update/document_diff_serialization.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/update/document_diff_serialization.h | 5 | ||||
-rw-r--r-- | src/mongo/db/update/document_diff_test.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/update/pipeline_executor.cpp | 13 | ||||
-rw-r--r-- | src/mongo/db/update/pipeline_executor_test.cpp | 312 | ||||
-rw-r--r-- | src/mongo/db/update/update_driver.cpp | 4 |
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(); |