diff options
-rw-r--r-- | jstests/replsets/bulk_api_wc.js | 16 | ||||
-rw-r--r-- | src/mongo/db/SConscript | 8 | ||||
-rw-r--r-- | src/mongo/db/write_concern_options.cpp | 64 | ||||
-rw-r--r-- | src/mongo/db/write_concern_options_test.cpp | 157 |
4 files changed, 227 insertions, 18 deletions
diff --git a/jstests/replsets/bulk_api_wc.js b/jstests/replsets/bulk_api_wc.js index 4b029555ce1..3c9d3b27538 100644 --- a/jstests/replsets/bulk_api_wc.js +++ b/jstests/replsets/bulk_api_wc.js @@ -35,13 +35,27 @@ var executeTests = function() { }); // + // Fail due to unrecognized write concern field. + coll.remove({}); + var bulk = coll.initializeOrderedBulkOp(); + bulk.insert({a: 1}); + bulk.insert({a: 2}); + var result = assert.throws(function() { + bulk.execute({x: 1}); + }); + assert.eq(ErrorCodes.FailedToParse, result.code, 'unexpected error code: ' + tojson(result)); + assert.eq('unrecognized write concern field: x', + result.errmsg, + 'unexpected error message: ' + tojson(result)); + + // // Fail with write error, no write concern error even though it would fail on apply for ordered coll.remove({}); var bulk = coll.initializeOrderedBulkOp(); bulk.insert({a: 1}); bulk.insert({a: 2}); bulk.insert({a: 2}); - var result = assert.throws(function() { + result = assert.throws(function() { bulk.execute({w: 'invalid'}); }); assert.eq(result.nInserted, 2); diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index ba8c1e855bd..e76cb918b17 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -458,6 +458,14 @@ env.Library( ], ) +env.CppUnitTest( + target= 'write_concern_options_test', + source= 'write_concern_options_test.cpp', + LIBDEPS=[ + 'write_concern_options', + ], +) + env.Library( target='service_context', source=[ diff --git a/src/mongo/db/write_concern_options.cpp b/src/mongo/db/write_concern_options.cpp index 95b411d9baa..028d03d4635 100644 --- a/src/mongo/db/write_concern_options.cpp +++ b/src/mongo/db/write_concern_options.cpp @@ -30,8 +30,10 @@ #include "mongo/db/write_concern_options.h" #include "mongo/base/status.h" +#include "mongo/base/string_data.h" #include "mongo/bson/util/bson_extract.h" #include "mongo/db/field_parser.h" +#include "mongo/util/mongoutils/str.h" namespace mongo { @@ -47,6 +49,14 @@ namespace { */ enum WriteConcern { W_NONE = 0, W_NORMAL = 1 }; +constexpr StringData kJFieldName = "j"_sd; +constexpr StringData kFSyncFieldName = "fsync"_sd; +constexpr StringData kWFieldName = "w"_sd; +constexpr StringData kWTimeoutFieldName = "wtimeout"_sd; +constexpr StringData kGetLastErrorFieldName = "getLastError"_sd; +constexpr StringData kWOpTimeFieldName = "wOpTime"_sd; +constexpr StringData kWElectionIdFieldName = "wElectionId"_sd; + } // namespace const int WriteConcernOptions::kNoTimeout(0); @@ -79,18 +89,41 @@ Status WriteConcernOptions::parse(const BSONObj& obj) { return Status(ErrorCodes::FailedToParse, "write concern object cannot be empty"); } - BSONElement jEl = obj["j"]; - if (!jEl.eoo() && !jEl.isNumber() && jEl.type() != Bool) { - return Status(ErrorCodes::FailedToParse, "j must be numeric or a boolean value"); + BSONElement jEl; + BSONElement fsyncEl; + BSONElement wEl; + + + for (auto e : obj) { + const auto fieldName = e.fieldNameStringData(); + if (fieldName == kJFieldName) { + jEl = e; + if (!jEl.isNumber() && jEl.type() != Bool) { + return Status(ErrorCodes::FailedToParse, "j must be numeric or a boolean value"); + } + } else if (fieldName == kFSyncFieldName) { + fsyncEl = e; + if (!fsyncEl.isNumber() && fsyncEl.type() != Bool) { + return Status(ErrorCodes::FailedToParse, + "fsync must be numeric or a boolean value"); + } + } else if (fieldName == kWFieldName) { + wEl = e; + } else if (fieldName == kWTimeoutFieldName) { + wTimeout = e.numberInt(); + } else if (fieldName == kWElectionIdFieldName) { + // Ignore. + } else if (fieldName == kWOpTimeFieldName) { + // Ignore. + } else if (fieldName.equalCaseInsensitive(kGetLastErrorFieldName)) { + // Ignore GLE field. + } else { + return Status(ErrorCodes::FailedToParse, + str::stream() << "unrecognized write concern field: " << fieldName); + } } const bool j = jEl.trueValue(); - - BSONElement fsyncEl = obj["fsync"]; - if (!fsyncEl.eoo() && !fsyncEl.isNumber() && fsyncEl.type() != Bool) { - return Status(ErrorCodes::FailedToParse, "fsync must be numeric or a boolean value"); - } - const bool fsync = fsyncEl.trueValue(); if (j && fsync) @@ -104,19 +137,16 @@ Status WriteConcernOptions::parse(const BSONObj& obj) { syncMode = SyncMode::NONE; } - BSONElement e = obj["w"]; - if (e.isNumber()) { - wNumNodes = e.numberInt(); - } else if (e.type() == String) { - wMode = e.valuestrsafe(); - } else if (e.eoo() || e.type() == jstNULL || e.type() == Undefined) { + if (wEl.isNumber()) { + wNumNodes = wEl.numberInt(); + } else if (wEl.type() == String) { + wMode = wEl.valuestrsafe(); + } else if (wEl.eoo() || wEl.type() == jstNULL || wEl.type() == Undefined) { wNumNodes = 1; } else { return Status(ErrorCodes::FailedToParse, "w has to be a number or a string"); } - wTimeout = obj["wtimeout"].numberInt(); - return Status::OK(); } diff --git a/src/mongo/db/write_concern_options_test.cpp b/src/mongo/db/write_concern_options_test.cpp new file mode 100644 index 00000000000..0085653ce0b --- /dev/null +++ b/src/mongo/db/write_concern_options_test.cpp @@ -0,0 +1,157 @@ +/** + * Copyright 2016 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/jsobj.h" +#include "mongo/db/write_concern_options.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { + +TEST(WriteConcernOptionsTest, ParseReturnsFailedToParseOnEmptyDocument) { + auto status = WriteConcernOptions().parse({}); + ASSERT_EQUALS(ErrorCodes::FailedToParse, status); + ASSERT_EQUALS("write concern object cannot be empty", status.reason()); +} + +TEST(WriteConcernOptionsTest, ParseReturnsFailedToParseOnInvalidJValue) { + auto status = WriteConcernOptions().parse(BSON("j" + << "abc")); + ASSERT_EQUALS(ErrorCodes::FailedToParse, status); + ASSERT_EQUALS("j must be numeric or a boolean value", status.reason()); +} + +TEST(WriteConcernOptionsTest, ParseReturnsFailedToParseOnInvalidFSyncValue) { + auto status = WriteConcernOptions().parse(BSON("fsync" + << "abc")); + ASSERT_EQUALS(ErrorCodes::FailedToParse, status); + ASSERT_EQUALS("fsync must be numeric or a boolean value", status.reason()); +} + +TEST(WriteConcernOptionsTest, ParseReturnsFailedToParseIfBothJAndFSyncAreTrue) { + auto status = WriteConcernOptions().parse(BSON("j" << true << "fsync" << true)); + ASSERT_EQUALS(ErrorCodes::FailedToParse, status); + ASSERT_EQUALS("fsync and j options cannot be used together", status.reason()); +} + +TEST(WriteConcernOptionsTest, ParseSetsSyncModeToJournelIfJIsTrue) { + WriteConcernOptions options; + ASSERT_OK(options.parse(BSON("j" << true))); + ASSERT_TRUE(WriteConcernOptions::SyncMode::JOURNAL == options.syncMode); + ASSERT_EQUALS(1, options.wNumNodes); + ASSERT_EQUALS("", options.wMode); + ASSERT_EQUALS(0, options.wTimeout); +} + +TEST(WriteConcernOptionsTest, ParseSetsSyncModeToFSyncIfFSyncIsTrue) { + WriteConcernOptions options; + ASSERT_OK(options.parse(BSON("fsync" << true))); + ASSERT_TRUE(WriteConcernOptions::SyncMode::FSYNC == options.syncMode); + ASSERT_EQUALS(1, options.wNumNodes); + ASSERT_EQUALS("", options.wMode); + ASSERT_EQUALS(0, options.wTimeout); +} + +TEST(WriteConcernOptionsTest, ParseSetsSyncModeToNoneIfJIsFalse) { + WriteConcernOptions options; + ASSERT_OK(options.parse(BSON("j" << false))); + ASSERT_TRUE(WriteConcernOptions::SyncMode::NONE == options.syncMode); + ASSERT_EQUALS(1, options.wNumNodes); + ASSERT_EQUALS("", options.wMode); + ASSERT_EQUALS(0, options.wTimeout); +} + +TEST(WriteConcernOptionsTest, ParseLeavesSyncModeAsUnsetIfFSyncIsFalse) { + WriteConcernOptions options; + ASSERT_OK(options.parse(BSON("fsync" << false))); + ASSERT_TRUE(WriteConcernOptions::SyncMode::UNSET == options.syncMode); + ASSERT_EQUALS(1, options.wNumNodes); + ASSERT_EQUALS("", options.wMode); + ASSERT_EQUALS(0, options.wTimeout); +} + +TEST(WriteConcernOptionsTest, ParseReturnsFailedToParseIfWIsNotNumberOrString) { + auto status = WriteConcernOptions().parse(BSON("w" << BSONObj())); + ASSERT_EQUALS(ErrorCodes::FailedToParse, status); + ASSERT_EQUALS("w has to be a number or a string", status.reason()); +} + +TEST(WriteConcernOptionsTest, ParseSetsWNumNodesIfWIsANumber) { + WriteConcernOptions options; + ASSERT_OK(options.parse(BSON("w" << 3))); + ASSERT_TRUE(WriteConcernOptions::SyncMode::UNSET == options.syncMode); + ASSERT_EQUALS(3, options.wNumNodes); + ASSERT_EQUALS("", options.wMode); + ASSERT_EQUALS(0, options.wTimeout); +} + +TEST(WriteConcernOptionsTest, ParseSetsWTimeoutToZeroIfWTimeoutIsNotANumber) { + WriteConcernOptions options; + ASSERT_OK(options.parse(BSON("wtimeout" + << "abc"))); + ASSERT_TRUE(WriteConcernOptions::SyncMode::UNSET == options.syncMode); + ASSERT_EQUALS("", options.wMode); + ASSERT_EQUALS(1, options.wNumNodes); + ASSERT_EQUALS(0, options.wTimeout); +} + +TEST(WriteConcernOptionsTest, ParseWTimeoutAsNumber) { + WriteConcernOptions options; + ASSERT_OK(options.parse(BSON("wtimeout" << 123))); + ASSERT_TRUE(WriteConcernOptions::SyncMode::UNSET == options.syncMode); + ASSERT_EQUALS("", options.wMode); + ASSERT_EQUALS(1, options.wNumNodes); + ASSERT_EQUALS(123, options.wTimeout); +} + +TEST(WriteConcernOptionsTest, ParseReturnsFailedToParseOnUnknownField) { + auto status = WriteConcernOptions().parse(BSON("x" << 123)); + ASSERT_EQUALS(ErrorCodes::FailedToParse, status); + ASSERT_EQUALS("unrecognized write concern field: x", status.reason()); +} + +void _testIgnoreWriteConcernField(const char* fieldName) { + WriteConcernOptions options; + ASSERT_OK(options.parse(BSON(fieldName << 1))); + ASSERT_TRUE(WriteConcernOptions::SyncMode::UNSET == options.syncMode); + ASSERT_EQUALS("", options.wMode); + ASSERT_EQUALS(1, options.wNumNodes); + ASSERT_EQUALS(0, options.wTimeout); +} +TEST(WriteConcernOptionsTest, ParseIgnoresSpecialFields) { + _testIgnoreWriteConcernField("wElectionId"); + _testIgnoreWriteConcernField("wOpTime"); + _testIgnoreWriteConcernField("getLastError"); + _testIgnoreWriteConcernField("getlasterror"); + _testIgnoreWriteConcernField("GETLastErrOR"); +} + +} // namespace +} // namespace mongo |