summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/replsets/bulk_api_wc.js16
-rw-r--r--src/mongo/db/SConscript8
-rw-r--r--src/mongo/db/write_concern_options.cpp64
-rw-r--r--src/mongo/db/write_concern_options_test.cpp157
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