summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMark Benvenuto <mark.benvenuto@mongodb.com>2017-06-30 14:33:01 -0400
committerMark Benvenuto <mark.benvenuto@mongodb.com>2017-06-30 14:33:01 -0400
commit957ee43d371f36e68a30b0b9e06556741f5a909d (patch)
tree7a115728424d6376d0c1ac8bd17c02ce4431c6a7 /src
parent2c895be9d7f506432b8377bbbde357452a92e9d2 (diff)
downloadmongo-957ee43d371f36e68a30b0b9e06556741f5a909d.tar.gz
SERVER-29779 Add OpMsgRequest parsing and serialization to IDL
Diffstat (limited to 'src')
-rw-r--r--src/mongo/idl/idl_parser.cpp29
-rw-r--r--src/mongo/idl/idl_parser.h20
-rw-r--r--src/mongo/idl/idl_test.cpp539
-rw-r--r--src/mongo/idl/unittest.idl34
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