diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/idl/idl_parser.cpp | 29 | ||||
-rw-r--r-- | src/mongo/idl/idl_parser.h | 20 | ||||
-rw-r--r-- | src/mongo/idl/idl_test.cpp | 539 | ||||
-rw-r--r-- | src/mongo/idl/unittest.idl | 34 |
4 files changed, 597 insertions, 25 deletions
diff --git a/src/mongo/idl/idl_parser.cpp b/src/mongo/idl/idl_parser.cpp index c65e04ba531..1de7730b780 100644 --- a/src/mongo/idl/idl_parser.cpp +++ b/src/mongo/idl/idl_parser.cpp @@ -26,12 +26,14 @@ * then also delete it in the license file. */ +#include <algorithm> #include <stack> #include <string> #include "mongo/idl/idl_parser.h" #include "mongo/bson/bsonobjbuilder.h" +#include "mongo/db/commands.h" #include "mongo/util/mongoutils/str.h" namespace mongo { @@ -58,6 +60,9 @@ std::string toCommaDelimitedList(const std::vector<BSONType>& types) { } // namespace +constexpr StringData IDLParserErrorContext::kOpMsgDollarDBDefault; +constexpr StringData IDLParserErrorContext::kOpMsgDollarDB; + bool IDLParserErrorContext::checkAndAssertType(const BSONElement& element, BSONType type) const { auto elementType = element.type(); @@ -169,11 +174,15 @@ std::string IDLParserErrorContext::getElementPath(StringData fieldName) const { } } -void IDLParserErrorContext::throwDuplicateField(const BSONElement& element) const { - std::string path = getElementPath(element); +void IDLParserErrorContext::throwDuplicateField(StringData fieldName) const { + std::string path = getElementPath(fieldName); uasserted(40413, str::stream() << "BSON field '" << path << "' is a duplicate field"); } +void IDLParserErrorContext::throwDuplicateField(const BSONElement& element) const { + throwDuplicateField(element.fieldNameStringData()); +} + void IDLParserErrorContext::throwMissingField(StringData fieldName) const { std::string path = getElementPath(fieldName); uasserted(40414, @@ -232,6 +241,22 @@ NamespaceString IDLParserErrorContext::parseNSCollectionRequired(StringData dbNa return nss; } +void IDLParserErrorContext::appendGenericCommandArguments( + const BSONObj& commandPassthroughFields, + const std::vector<StringData>& knownFields, + BSONObjBuilder* builder) { + + for (const auto& element : commandPassthroughFields) { + + StringData name = element.fieldNameStringData(); + // Include a passthrough field as long the IDL class has not defined it. + if (Command::isGenericArgument(name) && + std::find(knownFields.begin(), knownFields.end(), name) == knownFields.end()) { + builder->append(element); + } + } +} + std::vector<StringData> transformVector(const std::vector<std::string>& input) { return std::vector<StringData>(begin(input), end(input)); } diff --git a/src/mongo/idl/idl_parser.h b/src/mongo/idl/idl_parser.h index bc6b0674fd4..f5f6ba0f90e 100644 --- a/src/mongo/idl/idl_parser.h +++ b/src/mongo/idl/idl_parser.h @@ -29,6 +29,7 @@ #pragma once #include <string> +#include <vector> #include "mongo/base/disallow_copying.h" #include "mongo/base/string_data.h" @@ -52,6 +53,12 @@ class IDLParserErrorContext { MONGO_DISALLOW_COPYING(IDLParserErrorContext); public: + /** + * String constants for well-known IDL fields. + */ + static constexpr auto kOpMsgDollarDB = "$db"_sd; + static constexpr auto kOpMsgDollarDBDefault = "admin"_sd; + IDLParserErrorContext(StringData fieldName) : _currentField(fieldName), _predecessor(nullptr) {} IDLParserErrorContext(StringData fieldName, const IDLParserErrorContext* predecessor) @@ -94,6 +101,11 @@ public: MONGO_COMPILER_NORETURN void throwDuplicateField(const BSONElement& element) const; /** + * Throw an error message about the BSONElement being a duplicate field. + */ + MONGO_COMPILER_NORETURN void throwDuplicateField(StringData fieldName) const; + + /** * Throw an error message about the required field missing from the document. */ MONGO_COMPILER_NORETURN void throwMissingField(StringData fieldName) const; @@ -125,6 +137,14 @@ public: */ static NamespaceString parseNSCollectionRequired(StringData dbName, const BSONElement& element); + /** + * Take all the well known command generic arguments from commandPassthroughFields, but ignore + * fields that are already part of the command and append the rest to builder. + */ + static void appendGenericCommandArguments(const BSONObj& commandPassthroughFields, + const std::vector<StringData>& knownFields, + BSONObjBuilder* builder); + private: /** * See comment on getElementPath below. diff --git a/src/mongo/idl/idl_test.cpp b/src/mongo/idl/idl_test.cpp index 2d004ca9549..bf98582d7b5 100644 --- a/src/mongo/idl/idl_test.cpp +++ b/src/mongo/idl/idl_test.cpp @@ -32,6 +32,7 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/idl/unittest_gen.h" #include "mongo/unittest/unittest.h" +#include "mongo/util/net/op_msg.h" using namespace mongo::idl::test; @@ -70,6 +71,12 @@ bool isEquals(const std::vector<std::array<std::uint8_t, 16>>& left, left.data(), left.data() + left.size(), right.data(), right.data() + right.size()); } +BSONObj appendDB(const BSONObj& obj, StringData dbName) { + BSONObjBuilder builder; + builder.appendElements(obj); + builder.append("$db", dbName); + return builder.obj(); +} // Use a separate function to get better error messages when types do not match. template <typename T1, typename T2> @@ -1433,6 +1440,11 @@ TEST(IDLEnum, TestStringEnumNegative) { } } +OpMsgRequest makeOMR(BSONObj obj) { + OpMsgRequest request; + request.body = obj; + return request; +} // Positive: demonstrate a command wit concatenate with db TEST(IDLCommand, TestConcatentateWithDb) { @@ -1442,9 +1454,11 @@ TEST(IDLCommand, TestConcatentateWithDb) { << "field1" << 3 << "field2" - << "five"); + << "five" + << "$db" + << "db"); - auto testStruct = BasicConcatenateWithDbCommand::parse(ctxt, "db", testDoc); + auto testStruct = BasicConcatenateWithDbCommand::parse(ctxt, makeOMR(testDoc)); ASSERT_EQUALS(testStruct.getField1(), 3); ASSERT_EQUALS(testStruct.getField2(), "five"); ASSERT_EQUALS(testStruct.getNamespace(), NamespaceString("db.coll1")); @@ -1454,22 +1468,38 @@ TEST(IDLCommand, TestConcatentateWithDb) { // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; - testStruct.serialize(&builder); - auto loopbackDoc = builder.obj(); + OpMsgRequest reply = testStruct.serialize(BSONObj()); - ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); + ASSERT_BSONOBJ_EQ(testDoc, reply.body); } - // Positive: Test we can serialize from nothing the same document + // Positive: Test we can serialize from nothing the same document except for $db { + auto testDocWithoutDb = BSON(BasicConcatenateWithDbCommand::kCommandName << "coll1" + << "field1" + << 3 + << "field2" + << "five"); + BSONObjBuilder builder; BasicConcatenateWithDbCommand one_new(NamespaceString("db.coll1")); one_new.setField1(3); one_new.setField2("five"); - one_new.serialize(&builder); + one_new.serialize(BSONObj(), &builder); auto serializedDoc = builder.obj(); - ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); + ASSERT_BSONOBJ_EQ(testDocWithoutDb, serializedDoc); + } + + // Positive: Test we can serialize from nothing the same document + { + BSONObjBuilder builder; + BasicConcatenateWithDbCommand one_new(NamespaceString("db.coll1")); + one_new.setField1(3); + one_new.setField2("five"); + OpMsgRequest reply = one_new.serialize(BSONObj()); + + ASSERT_BSONOBJ_EQ(testDoc, reply.body); } } @@ -1480,8 +1510,10 @@ TEST(IDLCommand, TestConcatentateWithDbSymbol) { { auto testDoc = BSON("BasicConcatenateWithDbCommand" << BSONSymbol("coll1") << "field1" << 3 << "field2" - << "five"); - auto testStruct = BasicConcatenateWithDbCommand::parse(ctxt, "db", testDoc); + << "five" + << "$db" + << "db"); + auto testStruct = BasicConcatenateWithDbCommand::parse(ctxt, makeOMR(testDoc)); ASSERT_EQUALS(testStruct.getNamespace(), NamespaceString("db.coll1")); } } @@ -1497,28 +1529,28 @@ TEST(IDLCommand, TestConcatentateWithDbNegative) { << 1 << "field2" << "five"); - ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, "db", testDoc), UserException); + ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, makeOMR(testDoc)), UserException); } // Negative - namespace field wrong order { auto testDoc = BSON("field1" << 3 << "BasicConcatenateWithDbCommand" << 1 << "field2" << "five"); - ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, "db", testDoc), UserException); + ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, makeOMR(testDoc)), UserException); } // Negative - namespace missing { auto testDoc = BSON("field1" << 3 << "field2" << "five"); - ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, "db", testDoc), UserException); + ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, makeOMR(testDoc)), UserException); } // Negative - wrong type { auto testDoc = BSON("BasicConcatenateWithDbCommand" << 1 << "field1" << 3 << "field2" << "five"); - ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, "db", testDoc), UserException); + ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, makeOMR(testDoc)), UserException); } // Negative - bad ns with embedded null @@ -1526,7 +1558,7 @@ TEST(IDLCommand, TestConcatentateWithDbNegative) { StringData sd1("db\0foo", 6); auto testDoc = BSON("BasicConcatenateWithDbCommand" << sd1 << "field1" << 3 << "field2" << "five"); - ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, "db", testDoc), UserException); + ASSERT_THROWS(BasicConcatenateWithDbCommand::parse(ctxt, makeOMR(testDoc)), UserException); } } @@ -1537,14 +1569,16 @@ TEST(IDLCommand, TestIgnore) { auto testDoc = BSON("BasicIgnoredCommand" << 1 << "field1" << 3 << "field2" << "five"); - auto testStruct = BasicIgnoredCommand::parse(ctxt, testDoc); + auto testStruct = BasicIgnoredCommand::parse(ctxt, makeOMR(testDoc)); ASSERT_EQUALS(testStruct.getField1(), 3); ASSERT_EQUALS(testStruct.getField2(), "five"); + auto testDocWithDB = appendDB(testDoc, "admin"); + // Positive: Test we can roundtrip from the just parsed document { BSONObjBuilder builder; - testStruct.serialize(&builder); + testStruct.serialize(BSONObj(), &builder); auto loopbackDoc = builder.obj(); ASSERT_BSONOBJ_EQ(testDoc, loopbackDoc); @@ -1556,10 +1590,9 @@ TEST(IDLCommand, TestIgnore) { BasicIgnoredCommand one_new; one_new.setField1(3); one_new.setField2("five"); - one_new.serialize(&builder); + OpMsgRequest reply = one_new.serialize(BSONObj()); - auto serializedDoc = builder.obj(); - ASSERT_BSONOBJ_EQ(testDoc, serializedDoc); + ASSERT_BSONOBJ_EQ(testDocWithDB, reply.body); } } @@ -1572,23 +1605,483 @@ TEST(IDLCommand, TestIgnoredNegative) { auto testDoc = BSON( "BasicIgnoredCommand" << 1 << "field1" << 3 << "BasicIgnoredCommand" << 1 << "field2" << "five"); - ASSERT_THROWS(BasicIgnoredCommand::parse(ctxt, testDoc), UserException); + ASSERT_THROWS(BasicIgnoredCommand::parse(ctxt, makeOMR(testDoc)), UserException); } // Negative - namespace field wrong order { auto testDoc = BSON("field1" << 3 << "BasicIgnoredCommand" << 1 << "field2" << "five"); - ASSERT_THROWS(BasicIgnoredCommand::parse(ctxt, testDoc), UserException); + ASSERT_THROWS(BasicIgnoredCommand::parse(ctxt, makeOMR(testDoc)), UserException); } // Negative - namespace missing { auto testDoc = BSON("field1" << 3 << "field2" << "five"); - ASSERT_THROWS(BasicIgnoredCommand::parse(ctxt, testDoc), UserException); + ASSERT_THROWS(BasicIgnoredCommand::parse(ctxt, makeOMR(testDoc)), UserException); + } +} + +// Positive: Test a command read and written to OpMsgRequest works +TEST(IDLDocSequence, TestBasic) { + IDLParserErrorContext ctxt("root"); + + auto testTempDoc = BSON("DocSequenceCommand" + << "coll1" + << "field1" + << 3 + << "field2" + << "five" + << "structs" + << BSON_ARRAY(BSON("value" + << "hello") + << BSON("value" + << "world")) + << "objects" + << BSON_ARRAY(BSON("foo" << 1))); + + OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); + + auto testStruct = DocSequenceCommand::parse(ctxt, request); + ASSERT_EQUALS(testStruct.getField1(), 3); + ASSERT_EQUALS(testStruct.getField2(), "five"); + ASSERT_EQUALS(testStruct.getNamespace(), NamespaceString("db.coll1")); + + ASSERT_EQUALS(2UL, testStruct.getStructs().size()); + ASSERT_EQUALS("hello", testStruct.getStructs()[0].getValue()); + ASSERT_EQUALS("world", testStruct.getStructs()[1].getValue()); + + assert_same_types<decltype(testStruct.getNamespace()), const NamespaceString&>(); + + // Positive: Test we can roundtrip just the body from the just parsed document + { + OpMsgRequest loopbackRequest = testStruct.serialize(BSONObj()); + + ASSERT_BSONOBJ_EQ(request.body, loopbackRequest.body); + } + + // Positive: Test we can serialize from nothing the same document + { + BSONObjBuilder builder; + DocSequenceCommand one_new(NamespaceString("db.coll1")); + one_new.setField1(3); + one_new.setField2("five"); + + std::vector<One_string> strings; + One_string one_string; + one_string.setValue("hello"); + strings.push_back(one_string); + One_string one_string2; + one_string2.setValue("world"); + strings.push_back(one_string2); + one_new.setStructs(strings); + + std::vector<BSONObj> objects; + objects.push_back(BSON("foo" << 1)); + one_new.setObjects(objects); + + OpMsgRequest serializeRequest = one_new.serialize(BSONObj()); + + ASSERT_BSONOBJ_EQ(request.body, serializeRequest.body); + } +} + +// Positive: Test a OpMsgRequest read without $db +TEST(IDLDocSequence, TestMissingDB) { + IDLParserErrorContext ctxt("root"); + + auto testTempDoc = BSON("DocSequenceCommand" + << "coll1" + << "field1" + << 3 + << "field2" + << "five" + << "structs" + << BSON_ARRAY(BSON("value" + << "hello")) + << "objects" + << BSON_ARRAY(BSON("foo" << 1))); + + OpMsgRequest request; + request.body = testTempDoc; + + auto testStruct = DocSequenceCommand::parse(ctxt, request); + ASSERT_EQUALS(testStruct.getField1(), 3); + ASSERT_EQUALS(testStruct.getField2(), "five"); + ASSERT_EQUALS(testStruct.getNamespace(), NamespaceString("admin.coll1")); + + ASSERT_EQUALS(1UL, testStruct.getStructs().size()); + ASSERT_EQUALS("hello", testStruct.getStructs()[0].getValue()); + + assert_same_types<decltype(testStruct.getNamespace()), const NamespaceString&>(); +} + +// Positive: Test a command read and written to OpMsgRequest with content in DocumentSequence works +TEST(IDLDocSequence, TestDocSequence) { + IDLParserErrorContext ctxt("root"); + + auto testTempDoc = BSON("DocSequenceCommand" + << "coll1" + << "field1" + << 3 + << "field2" + << "five"); + + OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); + request.sequences.push_back({"structs", + {BSON("value" + << "hello"), + BSON("value" + << "world")}}); + request.sequences.push_back({"objects", {BSON("foo" << 1)}}); + + auto testStruct = DocSequenceCommand::parse(ctxt, request); + ASSERT_EQUALS(testStruct.getField1(), 3); + ASSERT_EQUALS(testStruct.getField2(), "five"); + ASSERT_EQUALS(testStruct.getNamespace(), NamespaceString("db.coll1")); + + ASSERT_EQUALS(2UL, testStruct.getStructs().size()); + ASSERT_EQUALS("hello", testStruct.getStructs()[0].getValue()); + ASSERT_EQUALS("world", testStruct.getStructs()[1].getValue()); +} + +// Negative: Bad Doc Sequences +TEST(IDLDocSequence, TestBadDocSequences) { + IDLParserErrorContext ctxt("root"); + + auto testTempDoc = BSON("DocSequenceCommand" + << "coll1" + << "field1" + << 3 + << "field2" + << "five"); + + // Negative: Duplicate fields in doc sequence + { + OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); + request.sequences.push_back({"structs", + {BSON("value" + << "hello"), + BSON("value" + << "world")}}); + request.sequences.push_back({"structs", {BSON("foo" << 1)}}); + + ASSERT_THROWS(DocSequenceCommand::parse(ctxt, request), UserException); + } + + // Negative: Extra field in document sequence + { + OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); + request.sequences.push_back({"structs", + {BSON("value" + << "hello"), + BSON("value" + << "world")}}); + request.sequences.push_back({"objects", {BSON("foo" << 1)}}); + request.sequences.push_back({"extra", {BSON("foo" << 1)}}); + + ASSERT_THROWS(DocSequenceCommand::parse(ctxt, request), UserException); + } + + // Negative: Missing field in both document sequence and body + { + OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); + request.sequences.push_back({"objects", {BSON("foo" << 1)}}); + + ASSERT_THROWS(DocSequenceCommand::parse(ctxt, request), UserException); + } + + // Negative: Missing field in both document sequence and body + { + OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); + request.sequences.push_back({"structs", + {BSON("value" + << "hello"), + BSON("value" + << "world")}}); + + ASSERT_THROWS(DocSequenceCommand::parse(ctxt, request), UserException); } } +// Negative: Duplicate field across body and document sequence +TEST(IDLDocSequence, TestDuplicateDocSequences) { + IDLParserErrorContext ctxt("root"); + + // Negative: Duplicate fields in doc sequence and body + { + auto testTempDoc = BSON("DocSequenceCommand" + << "coll1" + << "field1" + << 3 + << "field2" + << "five" + << "structs" + << BSON_ARRAY(BSON("value" + << "hello") + << BSON("value" + << "world")) + << "objects" + << BSON_ARRAY(BSON("foo" << 1))); + + OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); + request.sequences.push_back({"structs", + {BSON("value" + << "hello"), + BSON("value" + << "world")}}); + + ASSERT_THROWS(DocSequenceCommand::parse(ctxt, request), UserException); + } + + // Negative: Duplicate fields in doc sequence and body + { + auto testTempDoc = BSON("DocSequenceCommand" + << "coll1" + << "field1" + << 3 + << "field2" + << "five" + << "structs" + << BSON_ARRAY(BSON("value" + << "hello") + << BSON("value" + << "world")) + << "objects" + << BSON_ARRAY(BSON("foo" << 1))); + + OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); + request.sequences.push_back({"objects", {BSON("foo" << 1)}}); + + ASSERT_THROWS(DocSequenceCommand::parse(ctxt, request), UserException); + } +} + +// Positive: Test empty document sequence +TEST(IDLDocSequence, TestEmptySequence) { + IDLParserErrorContext ctxt("root"); + + // Negative: Duplicate fields in doc sequence and body + { + auto testTempDoc = BSON("DocSequenceCommand" + << "coll1" + << "field1" + << 3 + << "field2" + << "five" + << "structs" + << BSON_ARRAY(BSON("value" + << "hello") + << BSON("value" + << "world")) + << "objects" + << BSON_ARRAY(BSON("foo" << 1))); + + OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); + request.sequences.push_back({"structs", {}}); + + ASSERT_THROWS(DocSequenceCommand::parse(ctxt, request), UserException); + } + + // Positive: Empty document sequence + { + auto testTempDoc = BSON("DocSequenceCommand" + << "coll1" + << "field1" + << 3 + << "field2" + << "five" + << "objects" + << BSON_ARRAY(BSON("foo" << 1))); + + OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); + request.sequences.push_back({"structs", {}}); + + auto testStruct = DocSequenceCommand::parse(ctxt, request); + + ASSERT_EQUALS(0UL, testStruct.getStructs().size()); + } +} + +// Positive: Test all the OpMsg well known fields are ignored +TEST(IDLDocSequence, TestWellKnownFieldsAreIgnored) { + IDLParserErrorContext ctxt("root"); + + auto knownFields = {"$audit", + "$client", + "$configServerState", + "$oplogQueryData", + "$queryOptions", + "$readPreference", + "$replData", + "$logicalTime", + "maxTimeMS", + "readConcern", + "shardVersion", + "tracking_info", + "writeConcern"}; + + for (auto knownField : knownFields) { + auto testTempDoc = BSON("DocSequenceCommand" + << "coll1" + << "field1" + << 3 + << "field2" + << "five" + << knownField + << "extra" + << "structs" + << BSON_ARRAY(BSON("value" + << "hello") + << BSON("value" + << "world")) + << "objects" + << BSON_ARRAY(BSON("foo" << 1))); + + OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); + auto testStruct = DocSequenceCommand::parse(ctxt, request); + ASSERT_EQUALS(2UL, testStruct.getStructs().size()); + } +} + +// Positive: Test all the OpMsg well known fields are passed through except $db. +TEST(IDLDocSequence, TestWellKnownFieldsPassthrough) { + IDLParserErrorContext ctxt("root"); + + auto knownFields = {"$audit", + "$client", + "$configServerState", + "$oplogQueryData", + "$queryOptions", + "$readPreference", + "$replData", + "$logicalTime", + "maxTimeMS", + "readConcern", + "shardVersion", + "tracking_info", + "writeConcern"}; + + for (auto knownField : knownFields) { + auto testTempDoc = BSON("DocSequenceCommand" + << "coll1" + << "field1" + << 3 + << "field2" + << "five" + << "structs" + << BSON_ARRAY(BSON("value" + << "hello") + << BSON("value" + << "world")) + << "objects" + << BSON_ARRAY(BSON("foo" << 1)) + + << "$db" + << "db" + << knownField + << "extra"); + + OpMsgRequest request; + request.body = testTempDoc; + auto testStruct = DocSequenceCommand::parse(ctxt, request); + ASSERT_EQUALS(2UL, testStruct.getStructs().size()); + + auto reply = testStruct.serialize(testTempDoc); + ASSERT_BSONOBJ_EQ(request.body, reply.body); + } +} + +// Postive: Extra Fields in non-strict parser +TEST(IDLDocSequence, TestNonStrict) { + IDLParserErrorContext ctxt("root"); + + // Positive: Extra field in document sequence + { + auto testTempDoc = BSON("DocSequenceCommandNonStrict" + << "coll1" + << "field1" + << 3 + << "field2" + << "five"); + + OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); + request.sequences.push_back({"structs", + {BSON("value" + << "hello"), + BSON("value" + << "world")}}); + request.sequences.push_back({"objects", {BSON("foo" << 1)}}); + request.sequences.push_back({"extra", {BSON("foo" << 1)}}); + + ASSERT_THROWS(DocSequenceCommandNonStrict::parse(ctxt, request), UserException); + } + + // Positive: Extra field in body + { + auto testTempDoc = BSON("DocSequenceCommandNonStrict" + << "coll1" + << "field1" + << 3 + << "field2" + << "five" + << "extra" + << 1); + + OpMsgRequest request = OpMsgRequest::fromDBAndBody("db", testTempDoc); + request.sequences.push_back({"structs", + {BSON("value" + << "hello"), + BSON("value" + << "world")}}); + request.sequences.push_back({"objects", {BSON("foo" << 1)}}); + + ASSERT_THROWS(DocSequenceCommandNonStrict::parse(ctxt, request), UserException); + } +} + +// Postive: Test a Command known field does not propagate from passthrough to the final BSON if it +// is included as a field in the command. +TEST(IDLCommand, TestKnownFieldDuplicate) { + IDLParserErrorContext ctxt("root"); + + auto testPassthrough = BSON("$db" + << "foo" + << "maxTimeMS" + << 6 + << "$client" + << "foo"); + + auto testDoc = BSON("KnownFieldCommand" + << "coll1" + << "$db" + << "db" + << "field1" + << 28 + << "maxTimeMS" + << 42); + + auto testStruct = KnownFieldCommand::parse(ctxt, makeOMR(testDoc)); + ASSERT_EQUALS(28, testStruct.getField1()); + ASSERT_EQUALS(42, testStruct.getMaxTimeMS()); + + auto expectedDoc = BSON("KnownFieldCommand" + << "coll1" + + << "field1" + << 28 + << "maxTimeMS" + << 42 + << "$db" + << "db" + + << "$client" + << "foo"); + + ASSERT_BSONOBJ_EQ(expectedDoc, testStruct.serialize(testPassthrough).body); +} + + } // namespace } // namespace mongo diff --git a/src/mongo/idl/unittest.idl b/src/mongo/idl/unittest.idl index b58bd804875..2dc12729163 100644 --- a/src/mongo/idl/unittest.idl +++ b/src/mongo/idl/unittest.idl @@ -496,3 +496,37 @@ commands: fields: field1: int field2: string + + KnownFieldCommand: + description: UnitTest for a command that has a field that is special known generic command field + namespace: concatenate_with_db + fields: + field1: int + maxTimeMS: int + + DocSequenceCommand: + description: UnitTest for a basic command with fields marked with supports_doc_sequence + namespace: concatenate_with_db + fields: + field1: int + field2: string + structs: + type: array<one_string> + supports_doc_sequence: true + objects: + type: array<object> + supports_doc_sequence: true + + DocSequenceCommandNonStrict: + description: UnitTest for a basic command with fields marked with supports_doc_sequence and non-strict parsing + namespace: concatenate_with_db + strict: false + fields: + field1: int + field2: string + structs: + type: array<one_string> + supports_doc_sequence: true + objects: + type: array<object> + supports_doc_sequence: true |