diff options
author | George Wangensteen <george.wangensteen@mongodb.com> | 2020-11-04 15:39:42 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-11-19 23:55:38 +0000 |
commit | 304316278656273caffdd461ead2ef02e2ac7743 (patch) | |
tree | bc277a6e06a1827eea38888cfddf8e784c9c827c /src/mongo | |
parent | 5247cf436984268be6231d90c6d140259d43b83f (diff) | |
download | mongo-304316278656273caffdd461ead2ef02e2ac7743.tar.gz |
SERVER-51371 Strict IDL definition for WriteConcern
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/read_write_concern_provenance.h | 1 | ||||
-rw-r--r-- | src/mongo/db/read_write_concern_provenance_base.idl | 11 | ||||
-rw-r--r-- | src/mongo/db/write_concern_options.cpp | 74 | ||||
-rw-r--r-- | src/mongo/db/write_concern_options.h | 2 | ||||
-rw-r--r-- | src/mongo/idl/basic_types.h | 43 | ||||
-rw-r--r-- | src/mongo/idl/basic_types.idl | 59 | ||||
-rw-r--r-- | src/mongo/idl/idl_test.cpp | 41 |
8 files changed, 204 insertions, 29 deletions
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index 97bc4e7bf1f..488ccbe28a8 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -400,6 +400,7 @@ env.Library( ], LIBDEPS=[ '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/idl/basic_types', '$BUILD_DIR/mongo/idl/idl_parser', ], LIBDEPS_PRIVATE=[ @@ -414,6 +415,7 @@ env.Library( ], LIBDEPS=[ '$BUILD_DIR/mongo/bson/util/bson_extract', + '$BUILD_DIR/mongo/idl/basic_types', 'read_write_concern_provenance', ], ) diff --git a/src/mongo/db/read_write_concern_provenance.h b/src/mongo/db/read_write_concern_provenance.h index 54ecf7426ea..686d1843d6d 100644 --- a/src/mongo/db/read_write_concern_provenance.h +++ b/src/mongo/db/read_write_concern_provenance.h @@ -34,6 +34,7 @@ #include "mongo/bson/bsonelement.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/db/read_write_concern_provenance_base_gen.h" +#include "mongo/idl/basic_types_gen.h" namespace mongo { diff --git a/src/mongo/db/read_write_concern_provenance_base.idl b/src/mongo/db/read_write_concern_provenance_base.idl index e77d8a4c6be..4695d5f0a34 100644 --- a/src/mongo/db/read_write_concern_provenance_base.idl +++ b/src/mongo/db/read_write_concern_provenance_base.idl @@ -32,17 +32,6 @@ global: imports: - "mongo/idl/basic_types.idl" - -enums: - ReadWriteConcernProvenanceSource: - description: "Provenance sources" - type: string - values: - clientSupplied: "clientSupplied" - implicitDefault: "implicitDefault" - customDefault: "customDefault" - getLastErrorDefaults: "getLastErrorDefaults" - structs: ReadWriteConcernProvenanceBase: description: "Represents the 'provenance' (ie. original source) of a read or write concern." diff --git a/src/mongo/db/write_concern_options.cpp b/src/mongo/db/write_concern_options.cpp index b25140c65cb..17e30eddff3 100644 --- a/src/mongo/db/write_concern_options.cpp +++ b/src/mongo/db/write_concern_options.cpp @@ -29,6 +29,8 @@ #include "mongo/platform/basic.h" +#include <type_traits> + #include "mongo/db/write_concern_options.h" #include "mongo/base/status.h" @@ -36,6 +38,7 @@ #include "mongo/bson/util/bson_extract.h" #include "mongo/db/field_parser.h" #include "mongo/db/repl/repl_set_config.h" +#include "mongo/idl/basic_types_gen.h" #include "mongo/util/str.h" namespace mongo { @@ -197,13 +200,11 @@ WriteConcernOptions WriteConcernOptions::deserializerForIDL(const BSONObj& obj) } StatusWith<WriteConcernOptions> WriteConcernOptions::extractWCFromCommand(const BSONObj& cmdObj) { - WriteConcernOptions writeConcern; - // Return the default write concern if no write concern is provided. We check for the existence // of the write concern field up front in order to avoid the expense of constructing an error // status in bsonExtractTypedField() below. if (!cmdObj.hasField(kWriteConcernField)) { - return writeConcern; + return WriteConcernOptions(); } BSONElement writeConcernElement; @@ -216,15 +217,70 @@ StatusWith<WriteConcernOptions> WriteConcernOptions::extractWCFromCommand(const BSONObj writeConcernObj = writeConcernElement.Obj(); // Empty write concern is interpreted to default. if (writeConcernObj.isEmpty()) { - return writeConcern; + return WriteConcernOptions(); + } + + // Ensure that the write concern document complies with the strict IDL definition, found in + // idl/basic_types.idl. + try { + auto writeConcernIdl = WriteConcernIdl::parse( + IDLParserErrorContext("WriteConcernOptions::extractWCFromCommand"), writeConcernObj); + // Convert the IDL parsed WriteConcern into a WriteConcernOptions object. + auto sw = convertFromIdl(writeConcernIdl); + if (!sw.isOK()) { + return sw.getStatus(); + } + auto writeConcernOptions = sw.getValue(); + writeConcernOptions.usedDefault = false; + return writeConcernOptions; + } catch (const DBException& ex) { + return ex.toStatus(); + } +} + +StatusWith<WriteConcernOptions> WriteConcernOptions::convertFromIdl( + const WriteConcernIdl& writeConcernIdl) { + WriteConcernOptions writeConcern; + auto parsedW = writeConcernIdl.getWriteConcernW(); + if (!parsedW.usedDefault()) { + writeConcern.usedDefaultW = false; + auto wVal = parsedW.getValue(); + if (auto wNum = stdx::get_if<std::int64_t>(&wVal)) { + if (*wNum < 0 || + *wNum > + static_cast<std::decay_t<decltype(*wNum)>>(repl::ReplSetConfig::kMaxMembers)) { + uasserted(ErrorCodes::FailedToParse, + str::stream() << "w has to be a non-negative number and not greater than " + << repl::ReplSetConfig::kMaxMembers); + } + writeConcern.wNumNodes = static_cast<decltype(writeConcern.wNumNodes)>(*wNum); + } else { + auto wMode = stdx::get_if<std::string>(&wVal); + invariant(wMode); + writeConcern.wNumNodes = 0; // Have to reset from default 1. + writeConcern.wMode = std::move(*wMode); + } + } + + auto j = writeConcernIdl.getJ(); + auto fsync = writeConcernIdl.getFsync(); + if (j && fsync && *j && *fsync) { + // If j and fsync are both set to true + return Status{ErrorCodes::FailedToParse, "fsync and j options cannot be used together"}; + } + if (j && *j) { + writeConcern.syncMode = SyncMode::JOURNAL; + } else if (fsync && *fsync) { + writeConcern.syncMode = SyncMode::FSYNC; + } else if (j) { + // j has been set to false + writeConcern.syncMode = SyncMode::NONE; } - auto sw = parse(writeConcernObj); - if (!sw.isOK()) { - return sw.getStatus(); + writeConcern.wTimeout = writeConcernIdl.getWtimeout(); + if (auto source = writeConcernIdl.getSource()) { + writeConcern._provenance = ReadWriteConcernProvenance(*source); } - writeConcern = sw.getValue(); - writeConcern.usedDefault = false; return writeConcern; } diff --git a/src/mongo/db/write_concern_options.h b/src/mongo/db/write_concern_options.h index 7bc696f728d..870646fb528 100644 --- a/src/mongo/db/write_concern_options.h +++ b/src/mongo/db/write_concern_options.h @@ -33,6 +33,7 @@ #include "mongo/db/jsobj.h" #include "mongo/db/read_write_concern_provenance.h" +#include "mongo/idl/basic_types_gen.h" namespace mongo { @@ -143,6 +144,7 @@ public: private: ReadWriteConcernProvenance _provenance; + static StatusWith<WriteConcernOptions> convertFromIdl(const WriteConcernIdl& writeConcernIdl); }; } // namespace mongo diff --git a/src/mongo/idl/basic_types.h b/src/mongo/idl/basic_types.h index 01e38a5cb99..fb574d44a19 100644 --- a/src/mongo/idl/basic_types.h +++ b/src/mongo/idl/basic_types.h @@ -33,6 +33,7 @@ #include "mongo/base/string_data.h" #include "mongo/bson/bsonobjbuilder.h" +#include "mongo/stdx/variant.h" namespace mongo { @@ -132,4 +133,44 @@ public: private: BSONElement _element; }; -} // namespace mongo
\ No newline at end of file +class WriteConcernW { +public: + static WriteConcernW deserializeWriteConcernW(BSONElement wEl) { + if (wEl.isNumber()) { + return WriteConcernW{wEl.safeNumberLong()}; + } else if (wEl.type() == BSONType::String) { + return WriteConcernW{wEl.valuestrsafe()}; + } else if (wEl.eoo() || wEl.type() == BSONType::jstNULL || + wEl.type() == BSONType::Undefined) { + return WriteConcernW{}; + } + uasserted(ErrorCodes::FailedToParse, "w has to be a number or string"); + } + + void serializeWriteConcernW(BSONObjBuilder* builder) const { + if (auto stringVal = stdx::get_if<std::string>(&_w)) { + builder->append("w", *stringVal); + return; + } + auto intVal = stdx::get_if<std::int64_t>(&_w); + invariant(intVal); + builder->appendIntOrLL("w", *intVal); + } + + WriteConcernW() : _w{1}, _usedDefault{true} {}; + + bool usedDefault() const { + return _usedDefault; + } + + stdx::variant<std::string, std::int64_t> getValue() const { + return _w; + } + +private: + WriteConcernW(std::int64_t w) : _w{w}, _usedDefault{false} {}; + WriteConcernW(std::string&& w) : _w(std::move(w)), _usedDefault{false} {}; + stdx::variant<std::string, std::int64_t> _w; + bool _usedDefault; +}; +} // namespace mongo diff --git a/src/mongo/idl/basic_types.idl b/src/mongo/idl/basic_types.idl index 2b5a4719fec..f2aea90db79 100644 --- a/src/mongo/idl/basic_types.idl +++ b/src/mongo/idl/basic_types.idl @@ -202,8 +202,33 @@ types: serializer: mongo::IDLAnyType::serializeToBSON deserializer: mongo::IDLAnyType::parseFromBSON -structs: + writeConcernW: + bson_serialization_type: + - string + - int + - decimal + - double + - long + description: >- + A string or integer representing the 'w' option in a document specifying write concern. + See https://docs.mongodb.com/manual/reference/write-concern/" + cpp_type: "mongo::WriteConcernW" + default: "mongo::WriteConcernW()" + serializer: "mongo::WriteConcernW::serializeWriteConcernW" + deserializer: "mongo::WriteConcernW::deserializeWriteConcernW" +enums: + ReadWriteConcernProvenanceSource: + description: "Provenance sources" + type: string + values: + clientSupplied: "clientSupplied" + implicitDefault: "implicitDefault" + customDefault: "customDefault" + getLastErrorDefaults: "getLastErrorDefaults" + + +structs: OkReply: description: "Shared by commands that reply with just {ok: 1} and no additional information" strict: true @@ -230,3 +255,35 @@ structs: type: safeInt64 optional: true validator: { gte: 0 } + WriteConcernIdl: + description: "WriteConcern object parser" + strict: true + fields: + w: + type: writeConcernW + cpp_name: writeConcernW + j: + type: safeBool + optional: true + wtimeout: + type: safeInt64 + default: 0 + fsync: + type: safeBool + optional: true + # Fields with names wElectionId, wOpTime, and getLastError are accepted in the WriteConcern document for + # backwards-compatibility reasons, but their values are entirely ignored. + wElectionId: + type: any + ignore: true + wOpTime: + type: any + ignore: true + getLastError: + type: any + ignore: true + provenance: + description: "The source for this provenance" + cpp_name: source + type: ReadWriteConcernProvenanceSource + optional: true diff --git a/src/mongo/idl/idl_test.cpp b/src/mongo/idl/idl_test.cpp index b411ee280ec..da7e8ee6f18 100644 --- a/src/mongo/idl/idl_test.cpp +++ b/src/mongo/idl/idl_test.cpp @@ -552,13 +552,40 @@ TEST(IDLStructTests, TestNonStrictStruct) { } } -// Test some basic properties of the OkReply struct. TODO SERVER-51380 replace with a more -// thorough/integration-oriented test once some basic OkReplying-commands like ping get implemented. -TEST(IDLStructTests, TestOkReplyStruct) { - OkReply okReply; - - // Ensure that the serialized OkReply struct is the empty BSON object. - ASSERT_BSONOBJ_EQ(okReply.toBSON(), BSONObj()); +TEST(IDLStructTests, WriteConcernTest) { + IDLParserErrorContext ctxt("root"); + // Numeric w value + { + auto writeConcernDoc = BSON("w" << 1 << "j" << true << "wtimeout" << 5000); + auto writeConcernStruct = WriteConcernIdl::parse(ctxt, writeConcernDoc); + BSONObjBuilder builder; + writeConcernStruct.serialize(&builder); + ASSERT_BSONOBJ_EQ(builder.obj(), writeConcernDoc); + } + // String w value + { + auto writeConcernDoc = BSON("w" + << "majority" + << "j" << true << "wtimeout" << 5000); + auto writeConcernStruct = WriteConcernIdl::parse(ctxt, writeConcernDoc); + BSONObjBuilder builder; + writeConcernStruct.serialize(&builder); + ASSERT_BSONOBJ_EQ(builder.obj(), writeConcernDoc); + } + // Ignore options wElectionId, wOpTime, getLastError + { + auto writeConcernDoc = BSON("w" + << "majority" + << "j" << true << "wtimeout" << 5000 << "wElectionId" << 12345 + << "wOpTime" << 98765 << "getLastError" << true); + auto writeConcernDocWithoutIgnoredFields = BSON("w" + << "majority" + << "j" << true << "wtimeout" << 5000); + auto writeConcernStruct = WriteConcernIdl::parse(ctxt, writeConcernDoc); + BSONObjBuilder builder; + writeConcernStruct.serialize(&builder); + ASSERT_BSONOBJ_EQ(builder.obj(), writeConcernDocWithoutIgnoredFields); + } } /// Struct default comparison tests |