summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Alabi <alabidan@gmail.com>2015-03-16 13:11:40 -0400
committerDaniel Alabi <alabidan@gmail.com>2015-03-16 13:27:16 -0400
commitbb9b4e27c8c31542af52d80f4c3751c435909d69 (patch)
treefd06e28b14d194b27cfbe4753572337bc9d91d40
parente89ad4970c81ce122867e6af70d09cd164052022 (diff)
downloadmongo-bb9b4e27c8c31542af52d80f4c3751c435909d69.tar.gz
SERVER-6558 Add writeConcern option to findAndModify command
-rw-r--r--jstests/replsets/find_and_modify_wc.js79
-rw-r--r--src/mongo/db/commands.cpp10
-rw-r--r--src/mongo/db/commands.h8
-rw-r--r--src/mongo/db/commands/find_and_modify.cpp14
-rw-r--r--src/mongo/db/commands/write_commands/batch_executor.cpp8
-rw-r--r--src/mongo/db/write_concern.cpp36
-rw-r--r--src/mongo/db/write_concern.h17
7 files changed, 163 insertions, 9 deletions
diff --git a/jstests/replsets/find_and_modify_wc.js b/jstests/replsets/find_and_modify_wc.js
new file mode 100644
index 00000000000..21725c0e6d8
--- /dev/null
+++ b/jstests/replsets/find_and_modify_wc.js
@@ -0,0 +1,79 @@
+//
+// Tests writeConcerns with findAndModify command
+//
+(function() {
+ 'use strict';
+
+ var nodeCount = 3;
+ var rst = new ReplSetTest({ nodes: nodeCount });
+ rst.startSet({ nojournal: "" });
+ rst.initiate();
+
+ var primary = rst.getPrimary();
+ var coll = primary.getCollection("test.find_and_modify_wc");
+ coll.remove({});
+
+ // insert some documents
+ var docs = [];
+ for (var i = 1; i <= 5; ++i) {
+ docs.push({ i: i, j: 2*i });
+ }
+ var res = coll.runCommand({ insert: coll.getName(),
+ documents: docs,
+ writeConcern: { w: nodeCount } });
+ assert(res.ok);
+ assert.eq(5, coll.count());
+
+ // use for updates in subsequent runCommand calls
+ var reqUpdate = {
+ findAndModify: coll.getName(),
+ query: { i: 3 },
+ update: { $inc: { j: 1 } },
+ writeConcern: { w: 'majority' }
+ };
+
+ // Verify findAndModify returns old document new: false
+ var res = coll.runCommand(reqUpdate);
+ assert(res.ok);
+ assert(res.value);
+ // (2 * res.value.i) == 6 == res.value.j (old document)
+ assert.eq(2 * res.value.i, res.value.j);
+ assert(!res.writeConcernError);
+
+ // Verify findAndModify returns new document with new: true
+ reqUpdate.new = true;
+ res = coll.runCommand(reqUpdate);
+ assert(res.ok);
+ assert(res.value);
+ // (2 * res.value.i + 2) == 8 == res.value.j (new document after two updates)
+ assert.eq(2 * res.value.i + 2, res.value.j);
+ assert(!res.writeConcernError);
+
+ // Verify findAndModify remove works
+ res = coll.runCommand({
+ findAndModify: coll.getName(),
+ sort: { i: 1 },
+ remove: true,
+ writeConcern: { w: nodeCount }
+ });
+ assert.eq(res.value.i, 1);
+ assert.eq(coll.count(), 4);
+ assert(!res.writeConcernError);
+
+ // Verify findAndModify returns writeConcernError
+ // when given invalid writeConcerns
+ [
+ { w: 'invalid' },
+ { w: nodeCount + 1 }
+ ].forEach(function(wc) {
+ reqUpdate.writeConcern = wc;
+ res = coll.runCommand(reqUpdate);
+
+ assert(res.writeConcernError);
+ assert(res.writeConcernError.code);
+ assert(res.writeConcernError.errmsg);
+ });
+
+ rst.stopSet();
+
+})();
diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp
index fe3bda293d8..8c95e60b919 100644
--- a/src/mongo/db/commands.cpp
+++ b/src/mongo/db/commands.cpp
@@ -51,6 +51,7 @@
#include "mongo/db/jsobj.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/server_parameters.h"
+#include "mongo/s/write_ops/wc_error_detail.h"
#include "mongo/util/log.h"
namespace mongo {
@@ -261,6 +262,15 @@ namespace mongo {
}
}
+ void Command::appendCommandWCStatus(BSONObjBuilder& result, const Status& status) {
+ if (!status.isOK()) {
+ WCErrorDetail wcError;
+ wcError.setErrCode(status.code());
+ wcError.setErrMessage(status.reason());
+ result.append("writeConcernError", wcError.toBSON());
+ }
+ }
+
Status Command::getStatusFromCommandResult(const BSONObj& result) {
return mongo::getStatusFromCommandResult(result);
}
diff --git a/src/mongo/db/commands.h b/src/mongo/db/commands.h
index f256f3ceac5..7c9b2f66b55 100644
--- a/src/mongo/db/commands.h
+++ b/src/mongo/db/commands.h
@@ -48,8 +48,8 @@ namespace mongo {
class BSONObjBuilder;
class Client;
class Database;
- class Timer;
class OperationContext;
+ class Timer;
namespace mutablebson {
class Document;
@@ -315,6 +315,12 @@ namespace mutablebson {
BSONArray firstBatch,
BSONObjBuilder* builder);
+ /**
+ * Helper for setting a writeConcernError field in the command result object if
+ * a writeConcern error occurs.
+ */
+ static void appendCommandWCStatus(BSONObjBuilder& result, const Status& status);
+
// Set by command line. Controls whether or not testing-only commands should be available.
static int testCommandsEnabled;
diff --git a/src/mongo/db/commands/find_and_modify.cpp b/src/mongo/db/commands/find_and_modify.cpp
index 2cba4a3960f..4e2ebd6bf3d 100644
--- a/src/mongo/db/commands/find_and_modify.cpp
+++ b/src/mongo/db/commands/find_and_modify.cpp
@@ -47,6 +47,7 @@
#include "mongo/db/ops/update_lifecycle_impl.h"
#include "mongo/db/query/get_executor.h"
#include "mongo/db/repl/replication_coordinator_global.h"
+#include "mongo/db/write_concern.h"
#include "mongo/util/log.h"
namespace mongo {
@@ -114,6 +115,12 @@ namespace mongo {
return false;
}
+ StatusWith<WriteConcernOptions> wcResult = extractWriteConcern(cmdObj);
+ if (!wcResult.isOK()) {
+ return appendCommandStatus(result, wcResult.getStatus());
+ }
+ setupSynchronousCommit(wcResult.getValue(), txn);
+
bool ok = false;
MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN {
errmsg = "";
@@ -175,6 +182,13 @@ namespace mongo {
errmsg);
}
+ WriteConcernResult res;
+ wcResult = waitForWriteConcern(txn,
+ wcResult.getValue(),
+ txn->getClient()->getLastOp(),
+ &res);
+ appendCommandWCStatus(result, wcResult.getStatus());
+
return ok;
}
diff --git a/src/mongo/db/commands/write_commands/batch_executor.cpp b/src/mongo/db/commands/write_commands/batch_executor.cpp
index ae0c16e30f6..48f1f0a1be4 100644
--- a/src/mongo/db/commands/write_commands/batch_executor.cpp
+++ b/src/mongo/db/commands/write_commands/batch_executor.cpp
@@ -756,14 +756,6 @@ namespace mongo {
Collection* _collection;
};
- void setupSynchronousCommit( const WriteConcernOptions& writeConcern,
- OperationContext* txn ) {
- if ( writeConcern.syncMode == WriteConcernOptions::JOURNAL ||
- writeConcern.syncMode == WriteConcernOptions::FSYNC ) {
- txn->recoveryUnit()->goingToAwaitCommit();
- }
- }
-
void WriteBatchExecutor::bulkExecute( const BatchedCommandRequest& request,
const WriteConcernOptions& writeConcern,
std::vector<BatchedUpsertDetail*>* upsertedIds,
diff --git a/src/mongo/db/write_concern.cpp b/src/mongo/db/write_concern.cpp
index 97db25a5c5b..db55f59e220 100644
--- a/src/mongo/db/write_concern.cpp
+++ b/src/mongo/db/write_concern.cpp
@@ -31,6 +31,7 @@
#include "mongo/db/write_concern.h"
#include "mongo/base/counter.h"
+#include "mongo/bson/util/bson_extract.h"
#include "mongo/db/commands/server_status_metric.h"
#include "mongo/db/global_environment_experiment.h"
#include "mongo/db/operation_context.h"
@@ -52,6 +53,41 @@ namespace mongo {
static ServerStatusMetricField<Counter64> gleWtimeoutsDisplay("getLastError.wtimeouts",
&gleWtimeouts );
+ void setupSynchronousCommit(const WriteConcernOptions& writeConcern,
+ OperationContext* txn) {
+ if ( writeConcern.syncMode == WriteConcernOptions::JOURNAL ||
+ writeConcern.syncMode == WriteConcernOptions::FSYNC ) {
+ txn->recoveryUnit()->goingToAwaitCommit();
+ }
+ }
+
+ StatusWith<WriteConcernOptions> extractWriteConcern(const BSONObj& cmdObj) {
+ BSONElement writeConcernElement;
+ Status wcStatus = bsonExtractTypedField(cmdObj,
+ "writeConcern",
+ Object,
+ &writeConcernElement);
+
+ if (!wcStatus.isOK()) {
+ if (wcStatus == ErrorCodes::NoSuchKey) {
+ return repl::getGlobalReplicationCoordinator()->getGetLastErrorDefault();
+ }
+ return wcStatus;
+ }
+
+ WriteConcernOptions writeConcern;
+ wcStatus = writeConcern.parse(writeConcernElement.Obj());
+
+ if (wcStatus.isOK()) {
+ wcStatus = validateWriteConcern(writeConcern);
+ }
+ if (!wcStatus.isOK()) {
+ return wcStatus;
+ }
+
+ return writeConcern;
+ }
+
Status validateWriteConcern( const WriteConcernOptions& writeConcern ) {
const bool isJournalEnabled = getGlobalEnvironment()->getGlobalStorageEngine()->isDurable();
diff --git a/src/mongo/db/write_concern.h b/src/mongo/db/write_concern.h
index 98d7893a2e1..19513e4f969 100644
--- a/src/mongo/db/write_concern.h
+++ b/src/mongo/db/write_concern.h
@@ -34,6 +34,23 @@
namespace mongo {
class OperationContext;
+ template <typename T> class StatusWith;
+
+ /**
+ * If "writeConcern" indicates a durable commit level,
+ * marks the RecoveryUnit associated with "txn" appropriately.
+ * Provides a hint to the storage engine that
+ * particular operations will be waiting for their changes to become durable.
+ */
+ void setupSynchronousCommit(const WriteConcernOptions& writeConcern,
+ OperationContext* txn);
+
+ /**
+ * Attempts to extract a writeConcern from cmdObj.
+ * Verifies that the writeConcern is of type Object (BSON type) and
+ * that the resulting writeConcern is valid for this particular host.
+ */
+ StatusWith<WriteConcernOptions> extractWriteConcern(const BSONObj& cmdObj);
/**
* Verifies that a WriteConcern is valid for this particular host.