Skip to content

Commit 21c4f06

Browse files
committed
SERVER-6558 Add writeConcern option to findAndModify command
1 parent 196e937 commit 21c4f06

File tree

7 files changed

+163
-9
lines changed

7 files changed

+163
-9
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//
2+
// Tests writeConcerns with findAndModify command
3+
//
4+
(function() {
5+
'use strict';
6+
7+
var nodeCount = 3;
8+
var rst = new ReplSetTest({ nodes: nodeCount });
9+
rst.startSet({ nojournal: "" });
10+
rst.initiate();
11+
12+
var primary = rst.getPrimary();
13+
var coll = primary.getCollection("test.find_and_modify_wc");
14+
coll.remove({});
15+
16+
// insert some documents
17+
var docs = [];
18+
for (var i = 1; i <= 5; ++i) {
19+
docs.push({ i: i, j: 2*i });
20+
}
21+
var res = coll.runCommand({ insert: coll.getName(),
22+
documents: docs,
23+
writeConcern: { w: nodeCount } });
24+
assert(res.ok);
25+
assert.eq(5, coll.count());
26+
27+
// use for updates in subsequent runCommand calls
28+
var reqUpdate = {
29+
findAndModify: coll.getName(),
30+
query: { i: 3 },
31+
update: { $inc: { j: 1 } },
32+
writeConcern: { w: 'majority' }
33+
};
34+
35+
// Verify findAndModify returns old document new: false
36+
var res = coll.runCommand(reqUpdate);
37+
assert(res.ok);
38+
assert(res.value);
39+
// (2 * res.value.i) == 6 == res.value.j (old document)
40+
assert.eq(2 * res.value.i, res.value.j);
41+
assert(!res.writeConcernError);
42+
43+
// Verify findAndModify returns new document with new: true
44+
reqUpdate.new = true;
45+
res = coll.runCommand(reqUpdate);
46+
assert(res.ok);
47+
assert(res.value);
48+
// (2 * res.value.i + 2) == 8 == res.value.j (new document after two updates)
49+
assert.eq(2 * res.value.i + 2, res.value.j);
50+
assert(!res.writeConcernError);
51+
52+
// Verify findAndModify remove works
53+
res = coll.runCommand({
54+
findAndModify: coll.getName(),
55+
sort: { i: 1 },
56+
remove: true,
57+
writeConcern: { w: nodeCount }
58+
});
59+
assert.eq(res.value.i, 1);
60+
assert.eq(coll.count(), 4);
61+
assert(!res.writeConcernError);
62+
63+
// Verify findAndModify returns writeConcernError
64+
// when given invalid writeConcerns
65+
[
66+
{ w: 'invalid' },
67+
{ w: nodeCount + 1 }
68+
].forEach(function(wc) {
69+
reqUpdate.writeConcern = wc;
70+
res = coll.runCommand(reqUpdate);
71+
72+
assert(res.writeConcernError);
73+
assert(res.writeConcernError.code);
74+
assert(res.writeConcernError.errmsg);
75+
});
76+
77+
rst.stopSet();
78+
79+
})();

src/mongo/db/commands.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#include "mongo/db/jsobj.h"
5252
#include "mongo/db/namespace_string.h"
5353
#include "mongo/db/server_parameters.h"
54+
#include "mongo/s/write_ops/wc_error_detail.h"
5455
#include "mongo/util/log.h"
5556

5657
namespace mongo {
@@ -261,6 +262,15 @@ namespace mongo {
261262
}
262263
}
263264

265+
void Command::appendCommandWCStatus(BSONObjBuilder& result, const Status& status) {
266+
if (!status.isOK()) {
267+
WCErrorDetail wcError;
268+
wcError.setErrCode(status.code());
269+
wcError.setErrMessage(status.reason());
270+
result.append("writeConcernError", wcError.toBSON());
271+
}
272+
}
273+
264274
Status Command::getStatusFromCommandResult(const BSONObj& result) {
265275
return mongo::getStatusFromCommandResult(result);
266276
}

src/mongo/db/commands.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ namespace mongo {
4848
class BSONObjBuilder;
4949
class Client;
5050
class Database;
51-
class Timer;
5251
class OperationContext;
52+
class Timer;
5353

5454
namespace mutablebson {
5555
class Document;
@@ -315,6 +315,12 @@ namespace mutablebson {
315315
BSONArray firstBatch,
316316
BSONObjBuilder* builder);
317317

318+
/**
319+
* Helper for setting a writeConcernError field in the command result object if
320+
* a writeConcern error occurs.
321+
*/
322+
static void appendCommandWCStatus(BSONObjBuilder& result, const Status& status);
323+
318324
// Set by command line. Controls whether or not testing-only commands should be available.
319325
static int testCommandsEnabled;
320326

src/mongo/db/commands/find_and_modify.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include "mongo/db/ops/update_lifecycle_impl.h"
4848
#include "mongo/db/query/get_executor.h"
4949
#include "mongo/db/repl/replication_coordinator_global.h"
50+
#include "mongo/db/write_concern.h"
5051
#include "mongo/util/log.h"
5152

5253
namespace mongo {
@@ -114,6 +115,12 @@ namespace mongo {
114115
return false;
115116
}
116117

118+
StatusWith<WriteConcernOptions> wcResult = extractWriteConcern(cmdObj);
119+
if (!wcResult.isOK()) {
120+
return appendCommandStatus(result, wcResult.getStatus());
121+
}
122+
setupSynchronousCommit(wcResult.getValue(), txn);
123+
117124
bool ok = false;
118125
MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN {
119126
errmsg = "";
@@ -175,6 +182,13 @@ namespace mongo {
175182
errmsg);
176183
}
177184

185+
WriteConcernResult res;
186+
wcResult = waitForWriteConcern(txn,
187+
wcResult.getValue(),
188+
txn->getClient()->getLastOp(),
189+
&res);
190+
appendCommandWCStatus(result, wcResult.getStatus());
191+
178192
return ok;
179193
}
180194

src/mongo/db/commands/write_commands/batch_executor.cpp

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -756,14 +756,6 @@ namespace mongo {
756756
Collection* _collection;
757757
};
758758

759-
void setupSynchronousCommit( const WriteConcernOptions& writeConcern,
760-
OperationContext* txn ) {
761-
if ( writeConcern.syncMode == WriteConcernOptions::JOURNAL ||
762-
writeConcern.syncMode == WriteConcernOptions::FSYNC ) {
763-
txn->recoveryUnit()->goingToAwaitCommit();
764-
}
765-
}
766-
767759
void WriteBatchExecutor::bulkExecute( const BatchedCommandRequest& request,
768760
const WriteConcernOptions& writeConcern,
769761
std::vector<BatchedUpsertDetail*>* upsertedIds,

src/mongo/db/write_concern.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "mongo/db/write_concern.h"
3232

3333
#include "mongo/base/counter.h"
34+
#include "mongo/bson/util/bson_extract.h"
3435
#include "mongo/db/commands/server_status_metric.h"
3536
#include "mongo/db/global_environment_experiment.h"
3637
#include "mongo/db/operation_context.h"
@@ -52,6 +53,41 @@ namespace mongo {
5253
static ServerStatusMetricField<Counter64> gleWtimeoutsDisplay("getLastError.wtimeouts",
5354
&gleWtimeouts );
5455

56+
void setupSynchronousCommit(const WriteConcernOptions& writeConcern,
57+
OperationContext* txn) {
58+
if ( writeConcern.syncMode == WriteConcernOptions::JOURNAL ||
59+
writeConcern.syncMode == WriteConcernOptions::FSYNC ) {
60+
txn->recoveryUnit()->goingToAwaitCommit();
61+
}
62+
}
63+
64+
StatusWith<WriteConcernOptions> extractWriteConcern(const BSONObj& cmdObj) {
65+
BSONElement writeConcernElement;
66+
Status wcStatus = bsonExtractTypedField(cmdObj,
67+
"writeConcern",
68+
Object,
69+
&writeConcernElement);
70+
71+
if (!wcStatus.isOK()) {
72+
if (wcStatus == ErrorCodes::NoSuchKey) {
73+
return repl::getGlobalReplicationCoordinator()->getGetLastErrorDefault();
74+
}
75+
return wcStatus;
76+
}
77+
78+
WriteConcernOptions writeConcern;
79+
wcStatus = writeConcern.parse(writeConcernElement.Obj());
80+
81+
if (wcStatus.isOK()) {
82+
wcStatus = validateWriteConcern(writeConcern);
83+
}
84+
if (!wcStatus.isOK()) {
85+
return wcStatus;
86+
}
87+
88+
return writeConcern;
89+
}
90+
5591
Status validateWriteConcern( const WriteConcernOptions& writeConcern ) {
5692
const bool isJournalEnabled = getGlobalEnvironment()->getGlobalStorageEngine()->isDurable();
5793

src/mongo/db/write_concern.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,23 @@
3434
namespace mongo {
3535

3636
class OperationContext;
37+
template <typename T> class StatusWith;
38+
39+
/**
40+
* If "writeConcern" indicates a durable commit level,
41+
* marks the RecoveryUnit associated with "txn" appropriately.
42+
* Provides a hint to the storage engine that
43+
* particular operations will be waiting for their changes to become durable.
44+
*/
45+
void setupSynchronousCommit(const WriteConcernOptions& writeConcern,
46+
OperationContext* txn);
47+
48+
/**
49+
* Attempts to extract a writeConcern from cmdObj.
50+
* Verifies that the writeConcern is of type Object (BSON type) and
51+
* that the resulting writeConcern is valid for this particular host.
52+
*/
53+
StatusWith<WriteConcernOptions> extractWriteConcern(const BSONObj& cmdObj);
3754

3855
/**
3956
* Verifies that a WriteConcern is valid for this particular host.

0 commit comments

Comments
 (0)