diff options
author | Mohammad Dashti <mdashti@gmail.com> | 2023-02-04 01:50:41 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2023-02-04 02:49:00 +0000 |
commit | ea2db11ad2889935f4922905794dc032dafb102a (patch) | |
tree | fa314ad7cc25a03d3591a5116096424044ff45db | |
parent | dfd8a86021ae6d588c4f6fd46dab70176f672e0e (diff) | |
download | mongo-ea2db11ad2889935f4922905794dc032dafb102a.tar.gz |
SERVER-67412 Removed support for parsing '$v:1' oplog entries in change streams
7 files changed, 178 insertions, 134 deletions
diff --git a/src/mongo/db/pipeline/change_stream_event_transform.cpp b/src/mongo/db/pipeline/change_stream_event_transform.cpp index 62c795caaad..0c17457ebc4 100644 --- a/src/mongo/db/pipeline/change_stream_event_transform.cpp +++ b/src/mongo/db/pipeline/change_stream_event_transform.cpp @@ -218,33 +218,11 @@ Document ChangeStreamDefaultEventTransformation::applyTransformation(const Docum {"disambiguatedPaths", showDisambiguatedPaths ? Value(deltaDesc.disambiguatedPaths) : Value()}}); } - } else if (id.missing()) { - operationType = DocumentSourceChangeStream::kUpdateOpType; - checkValueType(input[repl::OplogEntry::kObjectFieldName], - repl::OplogEntry::kObjectFieldName, - BSONType::Object); - - if (_changeStreamSpec.getShowRawUpdateDescription()) { - updateDescription = input[repl::OplogEntry::kObjectFieldName]; - } else { - Document opObject = input[repl::OplogEntry::kObjectFieldName].getDocument(); - Value updatedFields = opObject["$set"]; - Value removedFields = opObject["$unset"]; - - // Extract the field names of $unset document. - std::vector<Value> removedFieldsVector; - if (removedFields.getType() == BSONType::Object) { - auto iter = removedFields.getDocument().fieldIterator(); - while (iter.more()) { - removedFieldsVector.push_back(Value(iter.next().first)); - } - } - - updateDescription = Value( - Document{{"updatedFields", - updatedFields.missing() ? Value(Document()) : updatedFields}, - {"removedFields", removedFieldsVector}}); - } + } else if (!oplogVersion.missing() || id.missing()) { + // This is not a replacement op, and we did not see a valid update version number. + uasserted(6741200, + str::stream() << "Unsupported or missing oplog version, $v: " + << oplogVersion.toString()); } else { operationType = DocumentSourceChangeStream::kReplaceOpType; fullDocument = input[repl::OplogEntry::kObjectFieldName]; diff --git a/src/mongo/db/pipeline/document_source_change_stream_test.cpp b/src/mongo/db/pipeline/document_source_change_stream_test.cpp index cdbae5ee5d3..1c48b6fbf3e 100644 --- a/src/mongo/db/pipeline/document_source_change_stream_test.cpp +++ b/src/mongo/db/pipeline/document_source_change_stream_test.cpp @@ -202,13 +202,21 @@ public: const BSONObj& spec = kDefaultSpec, const boost::optional<Document> expectedInvalidate = {}, const std::vector<repl::OplogEntry> transactionEntries = {}, - std::vector<Document> documentsForLookup = {}) { + std::vector<Document> documentsForLookup = {}, + const boost::optional<std::int32_t> expectedErrorCode = {}) { vector<intrusive_ptr<DocumentSource>> stages = makeStages(entry.getEntry().toBSON(), spec); auto lastStage = stages.back(); getExpCtx()->mongoProcessInterface = std::make_unique<MockMongoInterface>(transactionEntries, std::move(documentsForLookup)); + if (expectedErrorCode) { + ASSERT_THROWS_CODE(lastStage->getNext(), + AssertionException, + ErrorCodes::Error(expectedErrorCode.get())); + return; + } + auto next = lastStage->getNext(); // Match stage should pass the doc down if expectedDoc is given. ASSERT_EQ(next.isAdvanced(), static_cast<bool>(expectedDoc)); @@ -743,7 +751,8 @@ TEST_F(ChangeStreamStageTest, TransformInsertFromMigrateShowMigrations) { } TEST_F(ChangeStreamStageTest, TransformUpdateFields) { - BSONObj o = BSON("$set" << BSON("y" << 1)); + BSONObj diff = BSON("u" << BSON("y" << 1)); + BSONObj o = BSON("diff" << diff << "$v" << 2); BSONObj o2 = BSON("_id" << 1 << "x" << 2); auto updateField = makeOplogEntry(OpTypeEnum::kUpdate, // op type nss, // namespace @@ -752,13 +761,19 @@ TEST_F(ChangeStreamStageTest, TransformUpdateFields) { boost::none, // fromMigrate o2); // o2 - const auto expectedUpdateField = makeExpectedUpdateEvent( - kDefaultTs, nss, o2, D{{"updatedFields", D{{"y", 1}}}, {"removedFields", vector<V>()}}); + const auto expectedUpdateField = makeExpectedUpdateEvent(kDefaultTs, + nss, + o2, + D{{"updatedFields", D{{"y", 1}}}, + {"removedFields", vector<V>()}, + {"truncatedArrays", vector<V>()}}); + checkTransformation(updateField, expectedUpdateField); } TEST_F(ChangeStreamStageTest, TransformUpdateFieldsShowExpandedEvents) { - BSONObj o = BSON("$set" << BSON("y" << 1)); + BSONObj diff = BSON("u" << BSON("y" << 1)); + BSONObj o = BSON("diff" << diff << "$v" << 2); BSONObj o2 = BSON("_id" << 1 << "x" << 2); auto updateField = makeOplogEntry(OpTypeEnum::kUpdate, // op type nss, // namespace @@ -767,12 +782,14 @@ TEST_F(ChangeStreamStageTest, TransformUpdateFieldsShowExpandedEvents) { boost::none, // fromMigrate o2); // o2 - const auto expectedUpdateField = - makeExpectedUpdateEvent(kDefaultTs, - nss, - o2, - D{{"updatedFields", D{{"y", 1}}}, {"removedFields", vector<V>()}}, - true /* expanded events */); + const auto expectedUpdateField = makeExpectedUpdateEvent(kDefaultTs, + nss, + o2, + D{{"updatedFields", D{{"y", 1}}}, + {"removedFields", vector<V>()}, + {"truncatedArrays", vector<V>()}, + {"disambiguatedPaths", D{}}}, + true /* expanded events */); checkTransformation(updateField, expectedUpdateField, kShowExpandedEventsSpec); } @@ -905,7 +922,8 @@ TEST_F(ChangeStreamStageTest, TransformDeltaOplogNestedComplexSubDiffs) { // Legacy documents might not have an _id field; then the document key is the full (post-update) // document. TEST_F(ChangeStreamStageTest, TransformUpdateFieldsLegacyNoId) { - BSONObj o = BSON("$set" << BSON("y" << 1)); + BSONObj diff = BSON("u" << BSON("y" << 1)); + BSONObj o = BSON("diff" << diff << "$v" << 2); BSONObj o2 = BSON("x" << 1 << "y" << 1); auto updateField = makeOplogEntry(OpTypeEnum::kUpdate, // op type nss, // namespace @@ -914,13 +932,18 @@ TEST_F(ChangeStreamStageTest, TransformUpdateFieldsLegacyNoId) { boost::none, // fromMigrate o2); // o2 - const auto expectedUpdateField = makeExpectedUpdateEvent( - kDefaultTs, nss, o2, D{{"updatedFields", D{{"y", 1}}}, {"removedFields", vector<V>()}}); + const auto expectedUpdateField = makeExpectedUpdateEvent(kDefaultTs, + nss, + o2, + D{{"updatedFields", D{{"y", 1}}}, + {"removedFields", vector<V>()}, + {"truncatedArrays", vector<V>()}}); checkTransformation(updateField, expectedUpdateField); } TEST_F(ChangeStreamStageTest, TransformRemoveFields) { - BSONObj o = BSON("$unset" << BSON("y" << 1)); + BSONObj diff = BSON("d" << BSON("y" << false)); + BSONObj o = BSON("diff" << diff << "$v" << 2); BSONObj o2 = BSON("_id" << 1 << "x" << 2); auto removeField = makeOplogEntry(OpTypeEnum::kUpdate, // op type nss, // namespace @@ -930,7 +953,10 @@ TEST_F(ChangeStreamStageTest, TransformRemoveFields) { o2); // o2 const auto expectedUpdateField = makeExpectedUpdateEvent( - kDefaultTs, nss, o2, D{{"updatedFields", D{}}, {"removedFields", {"y"_sd}}}); + kDefaultTs, + nss, + o2, + D{{"updatedFields", D{}}, {"removedFields", {"y"_sd}}, {"truncatedArrays", vector<V>()}}); checkTransformation(removeField, expectedUpdateField); } // namespace @@ -2306,7 +2332,6 @@ TEST_F(ChangeStreamStageTest, PreparedTransactionEndingWithEmptyApplyOps) { TEST_F(ChangeStreamStageTest, TransformApplyOps) { // Doesn't use the checkTransformation() pattern that other tests use since we expect multiple // documents to be returned from one applyOps. - Document applyOpsDoc{ {"applyOps", Value{std::vector<Document>{ @@ -2317,7 +2342,10 @@ TEST_F(ChangeStreamStageTest, TransformApplyOps) { Document{{"op", "u"_sd}, {"ns", nss.ns()}, {"ui", testUuid()}, - {"o", Value{Document{{"$set", Value{Document{{"x", "hallo 2"_sd}}}}}}}, + {"o", + Value{Document{ + {"diff", Value{Document{{"u", Value{Document{{"x", "hallo 2"_sd}}}}}}}, + {"$v", 2}}}}, {"o2", Value{Document{{"_id", 123}}}}}, // Operation on another namespace which should be skipped. Document{{"op", "i"_sd}, @@ -2412,7 +2440,8 @@ TEST_F(ChangeStreamStageTest, ClusterTimeMatchesOplogEntry) { const auto opTime = repl::OpTime(ts, term); // Test the 'clusterTime' field is copied from the oplog entry for an update. - BSONObj o = BSON("$set" << BSON("y" << 1)); + BSONObj diff = BSON("u" << BSON("y" << 1)); + BSONObj o = BSON("diff" << diff << "$v" << 2); BSONObj o2 = BSON("_id" << 1 << "x" << 2); auto updateField = makeOplogEntry(OpTypeEnum::kUpdate, // op type nss, // namespace @@ -2422,8 +2451,12 @@ TEST_F(ChangeStreamStageTest, ClusterTimeMatchesOplogEntry) { o2, // o2 opTime); // opTime - const auto expectedUpdateField = makeExpectedUpdateEvent( - ts, nss, o2, D{{"updatedFields", D{{"y", 1}}}, {"removedFields", vector<V>()}}); + const auto expectedUpdateField = makeExpectedUpdateEvent(ts, + nss, + o2, + D{{"updatedFields", D{{"y", 1}}}, + {"removedFields", vector<V>()}, + {"truncatedArrays", vector<V>()}}); checkTransformation(updateField, expectedUpdateField); // Test the 'clusterTime' field is copied from the oplog entry for a collection drop. @@ -3070,18 +3103,40 @@ TEST_F(ChangeStreamStageDBTest, TransformsEntriesForLegalClientCollectionsWithSy } } -TEST_F(ChangeStreamStageDBTest, TransformUpdateFields) { +TEST_F(ChangeStreamStageDBTest, TransformUpdateFieldsVMissingNotSupported) { + // A missing $v field in the update oplog entry implies $v:1, which is no longer supported. BSONObj o = BSON("$set" << BSON("y" << 1)); BSONObj o2 = BSON("_id" << 1 << "x" << 2); auto updateField = makeOplogEntry(OpTypeEnum::kUpdate, nss, o, testUuid(), boost::none, o2); + checkTransformation(updateField, boost::none, kDefaultSpec, {}, {}, {}, 6741200); +} - const auto expectedUpdateField = makeExpectedUpdateEvent( - kDefaultTs, nss, o2, D{{"updatedFields", D{{"y", 1}}}, {"removedFields", vector<V>()}}); +TEST_F(ChangeStreamStageDBTest, TransformUpdateFieldsNonV2NotSupported) { + BSONObj diff = BSON("u" << BSON("y" << 1)); + BSONObj o = BSON("diff" << diff << "$v" << 3); + BSONObj o2 = BSON("_id" << 1 << "x" << 2); + auto updateField = makeOplogEntry(OpTypeEnum::kUpdate, nss, o, testUuid(), boost::none, o2); + checkTransformation(updateField, boost::none, kDefaultSpec, {}, {}, {}, 6741200); +} + +TEST_F(ChangeStreamStageDBTest, TransformUpdateFields) { + BSONObj diff = BSON("u" << BSON("y" << 1)); + BSONObj o = BSON("diff" << diff << "$v" << 2); + BSONObj o2 = BSON("_id" << 1 << "x" << 2); + auto updateField = makeOplogEntry(OpTypeEnum::kUpdate, nss, o, testUuid(), boost::none, o2); + + const auto expectedUpdateField = makeExpectedUpdateEvent(kDefaultTs, + nss, + o2, + D{{"updatedFields", D{{"y", 1}}}, + {"removedFields", vector<V>()}, + {"truncatedArrays", vector<V>()}}); checkTransformation(updateField, expectedUpdateField); } TEST_F(ChangeStreamStageDBTest, TransformRemoveFields) { - BSONObj o = BSON("$unset" << BSON("y" << 1)); + BSONObj diff = BSON("d" << BSON("y" << false)); + BSONObj o = BSON("diff" << diff << "$v" << 2); BSONObj o2 = BSON("_id" << 1 << "x" << 2); auto removeField = makeOplogEntry(OpTypeEnum::kUpdate, // op type nss, // namespace @@ -3091,7 +3146,10 @@ TEST_F(ChangeStreamStageDBTest, TransformRemoveFields) { o2); // o2 const auto expectedRemoveField = makeExpectedUpdateEvent( - kDefaultTs, nss, o2, D{{"updatedFields", D{}}, {"removedFields", {"y"_sd}}}); + kDefaultTs, + nss, + o2, + D{{"updatedFields", D{}}, {"removedFields", {"y"_sd}}, {"truncatedArrays", vector<V>()}}); checkTransformation(removeField, expectedRemoveField); } diff --git a/src/mongo/db/update/SConscript b/src/mongo/db/update/SConscript index 798c490d7fd..a571468bc3f 100644 --- a/src/mongo/db/update/SConscript +++ b/src/mongo/db/update/SConscript @@ -169,6 +169,7 @@ env.CppUnitTest( 'update_array_node_test.cpp', 'update_driver_test.cpp', 'update_object_node_test.cpp', + 'update_oplog_entry_serialization_test.cpp', 'update_serialization_test.cpp', 'v2_log_builder_test.cpp', ], diff --git a/src/mongo/db/update/update_oplog_entry_serialization.cpp b/src/mongo/db/update/update_oplog_entry_serialization.cpp index daee76db1c2..55c5d1f2d6e 100644 --- a/src/mongo/db/update/update_oplog_entry_serialization.cpp +++ b/src/mongo/db/update/update_oplog_entry_serialization.cpp @@ -29,9 +29,13 @@ #include "mongo/db/update/update_oplog_entry_serialization.h" +#include <fmt/format.h> + #include "mongo/db/update/document_diff_serialization.h" #include "mongo/db/update/update_oplog_entry_version.h" +using namespace fmt::literals; + namespace mongo::update_oplog_entry { BSONObj makeDeltaOplogEntry(const doc_diff::Diff& diff) { BSONObjBuilder builder; @@ -53,19 +57,6 @@ boost::optional<BSONObj> extractDiffFromOplogEntry(const BSONObj& opLog) { } namespace { -BSONElement extractNewValueForFieldFromV1Entry(const BSONObj& oField, StringData fieldName) { - // Check the '$set' section. - auto setElt = oField["$set"]; - if (setElt.ok()) { - // The $set field in a $v:1 entry should always be an object. - invariant(setElt.type() == BSONType::Object); - return setElt.embeddedObject()[fieldName]; - } - - // The field is either in the $unset section, or was not modified at all. - return BSONElement(); -} - BSONElement extractNewValueForFieldFromV2Entry(const BSONObj& oField, StringData fieldName) { auto diffField = oField[kDiffObjectFieldName]; @@ -84,17 +75,6 @@ BSONElement extractNewValueForFieldFromV2Entry(const BSONObj& oField, StringData return BSONElement(); } -FieldRemovedStatus isFieldRemovedByV1Update(const BSONObj& oField, StringData fieldName) { - auto unsetElt = oField["$unset"]; - if (unsetElt.ok()) { - invariant(unsetElt.type() == BSONType::Object); - if (unsetElt.embeddedObject()[fieldName].ok()) { - return FieldRemovedStatus::kFieldRemoved; - } - } - return FieldRemovedStatus::kFieldNotRemoved; -} - FieldRemovedStatus isFieldRemovedByV2Update(const BSONObj& oField, StringData fieldName) { auto diffField = oField[kDiffObjectFieldName]; @@ -122,20 +102,12 @@ UpdateType extractUpdateType(const BSONObj& updateDocument) { // to omit the $v field so that case must be handled carefully. auto vElt = updateDocument[kUpdateOplogEntryVersionFieldName]; - if (!vElt.ok()) { - // We're dealing with a $v:1 entry if the first field name starts with a '$' and there is no - // $v field. - if (updateDocument.firstElementFieldNameStringData().startsWith("$")) { - return UpdateType::kV1Modifier; - } - } else if (vElt.numberInt() == static_cast<int>(UpdateOplogEntryVersion::kUpdateNodeV1)) { - return UpdateType::kV1Modifier; - } else if (vElt.numberInt() == static_cast<int>(UpdateOplogEntryVersion::kDeltaV2)) { + if (vElt.ok() && vElt.numberInt() == static_cast<int>(UpdateOplogEntryVersion::kDeltaV2)) { return UpdateType::kV2Delta; } // Unrecognized oplog entry version. - MONGO_UNREACHABLE_TASSERT(6448500); + tasserted(6448500, str::stream() << "Unsupported or missing oplog version, " << vElt); } BSONElement extractNewValueForField(const BSONObj& oField, StringData fieldName) { @@ -143,9 +115,7 @@ BSONElement extractNewValueForField(const BSONObj& oField, StringData fieldName) auto type = extractUpdateType(oField); - if (type == UpdateType::kV1Modifier) { - return extractNewValueForFieldFromV1Entry(oField, fieldName); - } else if (type == UpdateType::kV2Delta) { + if (type == UpdateType::kV2Delta) { return extractNewValueForFieldFromV2Entry(oField, fieldName); } else if (type == UpdateType::kReplacement) { return oField[fieldName]; @@ -160,9 +130,7 @@ FieldRemovedStatus isFieldRemovedByUpdate(const BSONObj& oField, StringData fiel auto type = extractUpdateType(oField); - if (type == UpdateType::kV1Modifier) { - return isFieldRemovedByV1Update(oField, fieldName); - } else if (type == UpdateType::kV2Delta) { + if (type == UpdateType::kV2Delta) { return isFieldRemovedByV2Update(oField, fieldName); } else if (type == UpdateType::kReplacement) { // The field was definitely *not* removed if it's still in the post image. Otherwise, diff --git a/src/mongo/db/update/update_oplog_entry_serialization.h b/src/mongo/db/update/update_oplog_entry_serialization.h index a438c9ee5e2..018f819071f 100644 --- a/src/mongo/db/update/update_oplog_entry_serialization.h +++ b/src/mongo/db/update/update_oplog_entry_serialization.h @@ -33,8 +33,7 @@ #include "mongo/db/update/update_oplog_entry_version.h" /** - * This provides helpers for creating oplog entries. To create a $v: 1 modifier-style oplog - * entry, a LogBuilder must be used instead. + * This provides helpers for creating oplog entries. */ namespace mongo::update_oplog_entry { static inline constexpr StringData kDiffObjectFieldName = "diff"_sd; @@ -51,7 +50,6 @@ constexpr size_t kSizeOfDeltaOplogEntryMetadata = 15; */ enum class UpdateType { kReplacement, - kV1Modifier, kV2Delta, }; diff --git a/src/mongo/db/update/update_oplog_entry_serialization_test.cpp b/src/mongo/db/update/update_oplog_entry_serialization_test.cpp index 4dd08705f4b..b6ef9b1692e 100644 --- a/src/mongo/db/update/update_oplog_entry_serialization_test.cpp +++ b/src/mongo/db/update/update_oplog_entry_serialization_test.cpp @@ -41,45 +41,56 @@ namespace mongo::update_oplog_entry { namespace { -TEST(UpdateOplogSerializationTest, ReadV1EntryWithVersionField) { +DEATH_TEST_REGEX(UpdateOplogSerializationTest, + ReadV1EntryWithVersionField, + "Tripwire assertion.*6448500") { auto setField = BSON("a" << 1 << "b" << 2); BSONObj o(BSON("$v" << 1 << "$set" << setField)); - ASSERT_BSONELT_EQ(extractNewValueForField(o, "a"), setField["a"]); - ASSERT_BSONELT_EQ(extractNewValueForField(o, "b"), setField["b"]); - - ASSERT(isFieldRemovedByUpdate(o, "a") == FieldRemovedStatus::kFieldNotRemoved); - ASSERT(isFieldRemovedByUpdate(o, "z") == FieldRemovedStatus::kFieldNotRemoved); + extractNewValueForField(o, "a"); } -TEST(UpdateOplogSerializationTest, ReadV1EntryWithoutVersionField) { +DEATH_TEST_REGEX(UpdateOplogSerializationTest, + ReadV1EntryWithoutVersionField_NotSupported1, + "Tripwire assertion.*6448500") { auto setField = BSON("a" << 1 << "b" << 2); BSONObj o(BSON("$set" << setField)); - ASSERT_BSONELT_EQ(extractNewValueForField(o, "a"), setField["a"]); - ASSERT_BSONELT_EQ(extractNewValueForField(o, "b"), setField["b"]); + extractNewValueForField(o, "a"); +} - ASSERT(isFieldRemovedByUpdate(o, "a") == FieldRemovedStatus::kFieldNotRemoved); - ASSERT(isFieldRemovedByUpdate(o, "z") == FieldRemovedStatus::kFieldNotRemoved); +DEATH_TEST_REGEX(UpdateOplogSerializationTest, + ReadV1EntryWithoutVersionField_NotSupported2, + "Tripwire assertion.*6448500") { + auto setField = BSON("a" << 1 << "b" << 2); + BSONObj o(BSON("$set" << setField)); + + isFieldRemovedByUpdate(o, "a"); } -TEST(UpdateOplogSerializationTest, ReadV1EntryWithSetAndUnset) { +DEATH_TEST_REGEX(UpdateOplogSerializationTest, + ReadV1EntryWithSetAndUnset_NotSupported1, + "Tripwire assertion.*6448500") { auto setField = BSON("a" << 1 << "b" << 2); auto unsetField = BSON("c" << true << "d" << true); BSONObj o(BSON("$set" << setField << "$unset" << unsetField)); - ASSERT_BSONELT_EQ(extractNewValueForField(o, "a"), setField["a"]); - ASSERT_BSONELT_EQ(extractNewValueForField(o, "b"), setField["b"]); - ASSERT_BSONELT_EQ(extractNewValueForField(o, "c"), BSONElement()); - ASSERT_BSONELT_EQ(extractNewValueForField(o, "d"), BSONElement()); + extractNewValueForField(o, "a"); +} + +DEATH_TEST_REGEX(UpdateOplogSerializationTest, + ReadV1EntryWithSetAndUnset_NotSupported2, + "Tripwire assertion.*6448500") { + auto setField = BSON("a" << 1 << "b" << 2); + auto unsetField = BSON("c" << true << "d" << true); + BSONObj o(BSON("$set" << setField << "$unset" << unsetField)); - ASSERT(isFieldRemovedByUpdate(o, "a") == FieldRemovedStatus::kFieldNotRemoved); - ASSERT(isFieldRemovedByUpdate(o, "b") == FieldRemovedStatus::kFieldNotRemoved); - ASSERT(isFieldRemovedByUpdate(o, "c") == FieldRemovedStatus::kFieldRemoved); - ASSERT(isFieldRemovedByUpdate(o, "d") == FieldRemovedStatus::kFieldRemoved); + isFieldRemovedByUpdate(o, "a"); } -TEST(UpdateOplogSerializationTest, ReadV1EntryWhichIncludesDottedPath) { +DEATH_TEST_REGEX(UpdateOplogSerializationTest, + ReadV1EntryWhichIncludesDottedPath_NotSupported1, + "Tripwire assertion.*6448500") { // While our function for getting modified fields only supports top-level fields, // there should be no problem if the oplog entry contains modifications to // dotted paths. @@ -88,12 +99,21 @@ TEST(UpdateOplogSerializationTest, ReadV1EntryWhichIncludesDottedPath) { auto unsetField = BSON("d.e.f" << true << "y" << true); BSONObj o(BSON("$set" << setField << "$unset" << unsetField)); - ASSERT_BSONELT_EQ(extractNewValueForField(o, "x"), setField["x"]); - ASSERT_BSONELT_EQ(extractNewValueForField(o, "y"), BSONElement()); + extractNewValueForField(o, "x"); +} + +DEATH_TEST_REGEX(UpdateOplogSerializationTest, + ReadV1EntryWhichIncludesDottedPath_NotSupported2, + "Tripwire assertion.*6448500") { + // While our function for getting modified fields only supports top-level fields, + // there should be no problem if the oplog entry contains modifications to + // dotted paths. + + auto setField = BSON("a.b.c" << 1 << "x" << 2); + auto unsetField = BSON("d.e.f" << true << "y" << true); + BSONObj o(BSON("$set" << setField << "$unset" << unsetField)); - ASSERT(isFieldRemovedByUpdate(o, "x") == FieldRemovedStatus::kFieldNotRemoved); - ASSERT(isFieldRemovedByUpdate(o, "y") == FieldRemovedStatus::kFieldRemoved); - ASSERT(isFieldRemovedByUpdate(o, "z") == FieldRemovedStatus::kFieldNotRemoved); + isFieldRemovedByUpdate(o, "x"); } TEST(UpdateOplogSerializationTest, ReadV2Entry) { @@ -114,12 +134,22 @@ TEST(UpdateOplogSerializationTest, ReadV2Entry) { ASSERT(isFieldRemovedByUpdate(o, "nonexistentField") == FieldRemovedStatus::kFieldNotRemoved); } -TEST(UpdateOplogSerializationTest, ReadV1EntryWithSubfieldModified) { +DEATH_TEST_REGEX(UpdateOplogSerializationTest, + ReadV1EntryWithSubfieldModified_NotSupported1, + "Tripwire assertion.*6448500") { auto setField = BSON("a.b" << 1 << "x" << 2); BSONObj o(BSON("$set" << setField)); // We cannot recover the entire new value for field 'a' so an EOO element is returned. - ASSERT_BSONELT_EQ(extractNewValueForField(o, "a"), BSONElement()); + extractNewValueForField(o, "a"); +} + +DEATH_TEST_REGEX(UpdateOplogSerializationTest, + ReadV1EntryWithSubfieldModified_NotSupported2, + "Tripwire assertion.*6448500") { + auto setField = BSON("a.b" << 1 << "x" << 2); + BSONObj o(BSON("$set" << setField)); + ASSERT(isFieldRemovedByUpdate(o, "a") == FieldRemovedStatus::kFieldNotRemoved); } @@ -132,29 +162,40 @@ TEST(UpdateOplogSerializationTest, ReadV2EntryWithSubfieldModified) { ASSERT(isFieldRemovedByUpdate(o, "a") == FieldRemovedStatus::kFieldNotRemoved); } -TEST(UpdateOplogSerializationTest, ReadReplacementEntry) { +DEATH_TEST_REGEX(UpdateOplogSerializationTest, + ReadReplacementEntry_NotSupported1, + "Tripwire assertion.*6448500") { BSONObj o(BSON("foo" << 1 << "bar" << 2)); - ASSERT_EQ(extractNewValueForField(o, "foo"), o["foo"]); - ASSERT_EQ(extractNewValueForField(o, "bar"), o["bar"]); + isFieldRemovedByUpdate(o, "bar"); +} + +DEATH_TEST_REGEX(UpdateOplogSerializationTest, + ReadReplacementEntry_NotSupported2, + "Tripwire assertion.*6448500") { + BSONObj o(BSON("foo" << 1 << "bar" << 2)); - ASSERT(isFieldRemovedByUpdate(o, "bar") == FieldRemovedStatus::kFieldNotRemoved); - ASSERT(isFieldRemovedByUpdate(o, "baz") == FieldRemovedStatus::kUnknown); + extractNewValueForField(o, "foo"); } -DEATH_TEST(UpdateOplogSerializationTest, CannotExtractDottedField, "cannot contain dots") { + +DEATH_TEST_REGEX(UpdateOplogSerializationTest, CannotExtractDottedField, "cannot contain dots") { extractNewValueForField(BSONObj(), "a.b"); } -DEATH_TEST(UpdateOplogSerializationTest, CannotReadDottedField, "cannot contain dots") { +DEATH_TEST_REGEX(UpdateOplogSerializationTest, CannotReadDottedField, "cannot contain dots") { isFieldRemovedByUpdate(BSONObj(), "a.b"); } -DEATH_TEST(UpdateOplogSerializationTest, CannotExtractFromNonExistentVersion, "Unrecognized") { +DEATH_TEST_REGEX(UpdateOplogSerializationTest, + CannotExtractFromNonExistentVersion, + "Tripwire assertion.*6448500") { extractNewValueForField(BSON("$v" << 10), "a"); } -DEATH_TEST(UpdateOplogSerializationTest, CannotReadNonExistentVersion, "Unrecognized") { +DEATH_TEST_REGEX(UpdateOplogSerializationTest, + CannotReadNonExistentVersion, + "Tripwire assertion.*6448500") { isFieldRemovedByUpdate(BSON("$v" << 10), "a"); } } // namespace diff --git a/src/mongo/db/update/update_oplog_entry_version.h b/src/mongo/db/update/update_oplog_entry_version.h index cbd18e51834..983e7e16276 100644 --- a/src/mongo/db/update/update_oplog_entry_version.h +++ b/src/mongo/db/update/update_oplog_entry_version.h @@ -50,7 +50,7 @@ enum class UpdateOplogEntryVersion { // user facing modifier-style update system remains. When a single update adds // multiple fields, those fields are added in lexicographic order by field name. This system // introduces support for arrayFilters and $[] syntax. - kUpdateNodeV1 = 1, + kUpdateNodeV1_NotSupprted = 1, // Delta style update, introduced in 4.7. When a pipeline based update is executed, the pre and // post images are diffed, producing a delta. The delta is recorded in the oplog. On |