summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJack Mulrow <jack.mulrow@mongodb.com>2017-09-15 17:57:33 -0400
committerJack Mulrow <jack.mulrow@mongodb.com>2017-09-19 10:29:48 -0400
commit9e598d7fbcb9c7e7d7b1ace7c9df47e316deb02f (patch)
treee33ab79da5c19674b7493d10bdd06a23462c4a6c
parent86f4e7955327bc009dd4a25135bb741886d0ccaa (diff)
downloadmongo-9e598d7fbcb9c7e7d7b1ace7c9df47e316deb02f.tar.gz
SERVER-30206 Error if retryable write request has multi update or delete
-rw-r--r--jstests/replsets/transaction_table_oplog_replay.js8
-rw-r--r--jstests/sharding/retryable_writes.js50
-rw-r--r--src/mongo/db/ops/write_ops_exec.cpp8
3 files changed, 62 insertions, 4 deletions
diff --git a/jstests/replsets/transaction_table_oplog_replay.js b/jstests/replsets/transaction_table_oplog_replay.js
index 8587558bc57..dbe039dc087 100644
--- a/jstests/replsets/transaction_table_oplog_replay.js
+++ b/jstests/replsets/transaction_table_oplog_replay.js
@@ -180,10 +180,10 @@
{
delete: "foo",
deletes: [
- {q: {_id: 10}, limit: 0},
- {q: {_id: 20}, limit: 0},
- {q: {_id: 30}, limit: 0},
- {q: {_id: 40}, limit: 0}
+ {q: {_id: 10}, limit: 1},
+ {q: {_id: 20}, limit: 1},
+ {q: {_id: 30}, limit: 1},
+ {q: {_id: 40}, limit: 1}
],
ordered: true,
lsid: {id: UUID()},
diff --git a/jstests/sharding/retryable_writes.js b/jstests/sharding/retryable_writes.js
index 83c0964f845..adaccecc104 100644
--- a/jstests/sharding/retryable_writes.js
+++ b/jstests/sharding/retryable_writes.js
@@ -295,6 +295,54 @@
assert.eq(1, collContents[1].y);
}
+ function runMultiTests(mainConn, priConn) {
+ // Test the behavior of retryable writes with multi=true / limit=0
+ var lsid = {id: UUID()};
+ var testDb = mainConn.getDB('test_multi');
+
+ // Only the update statements with multi=true in a batch fail.
+ var cmd = {
+ update: 'user',
+ updates: [{q: {x: 1}, u: {y: 1}}, {q: {x: 2}, u: {z: 1}, multi: true}],
+ ordered: true,
+ lsid: lsid,
+ txnNumber: NumberLong(1),
+ };
+ var res = assert.commandWorked(testDb.runCommand(cmd));
+ assert.eq(1,
+ res.writeErrors.length,
+ 'expected only one write error, received: ' + tojson(res.writeErrors));
+ assert.eq(1,
+ res.writeErrors[0].index,
+ 'expected the update at index 1 to fail, not the update at index: ' +
+ res.writeErrors[0].index);
+ assert.eq(ErrorCodes.InvalidOptions,
+ res.writeErrors[0].code,
+ 'expected to fail with code ' + ErrorCodes.InvalidOptions + ', received: ' +
+ res.writeErrors[0].code);
+
+ // Only the delete statements with limit=0 in a batch fail.
+ cmd = {
+ delete: 'user',
+ deletes: [{q: {x: 1}, limit: 1}, {q: {y: 1}, limit: 0}],
+ ordered: false,
+ lsid: lsid,
+ txnNumber: NumberLong(1),
+ };
+ res = assert.commandWorked(testDb.runCommand(cmd));
+ assert.eq(1,
+ res.writeErrors.length,
+ 'expected only one write error, received: ' + tojson(res.writeErrors));
+ assert.eq(1,
+ res.writeErrors[0].index,
+ 'expected the delete at index 1 to fail, not the delete at index: ' +
+ res.writeErrors[0].index);
+ assert.eq(ErrorCodes.InvalidOptions,
+ res.writeErrors[0].code,
+ 'expected to fail with code ' + ErrorCodes.InvalidOptions + ', received: ' +
+ res.writeErrors[0].code);
+ }
+
// Tests for replica set
var replTest = new ReplSetTest({nodes: 1});
replTest.startSet();
@@ -304,6 +352,7 @@
runTests(priConn, priConn);
runFailpointTests(priConn, priConn);
+ runMultiTests(priConn, priConn);
replTest.stopSet();
@@ -312,6 +361,7 @@
runTests(st.s0, st.rs0.getPrimary());
runFailpointTests(st.s0, st.rs0.getPrimary());
+ runMultiTests(st.s0, st.rs0.getPrimary());
st.stop();
})();
diff --git a/src/mongo/db/ops/write_ops_exec.cpp b/src/mongo/db/ops/write_ops_exec.cpp
index 24dfef674a7..d3a9813039b 100644
--- a/src/mongo/db/ops/write_ops_exec.cpp
+++ b/src/mongo/db/ops/write_ops_exec.cpp
@@ -521,6 +521,10 @@ static SingleWriteResult performSingleUpdateOp(OperationContext* opCtx,
const NamespaceString& ns,
StmtId stmtId,
const write_ops::UpdateOpEntry& op) {
+ uassert(ErrorCodes::InvalidOptions,
+ "Cannot use (or request) retryable writes with multi=true",
+ !(opCtx->getTxnNumber() && op.getMulti()));
+
globalOpCounters.gotUpdate();
auto& curOp = *CurOp::get(opCtx);
{
@@ -663,6 +667,10 @@ static SingleWriteResult performSingleDeleteOp(OperationContext* opCtx,
const NamespaceString& ns,
StmtId stmtId,
const write_ops::DeleteOpEntry& op) {
+ uassert(ErrorCodes::InvalidOptions,
+ "Cannot use (or request) retryable writes with limit=0",
+ !(opCtx->getTxnNumber() && op.getMulti()));
+
globalOpCounters.gotDelete();
auto& curOp = *CurOp::get(opCtx);
{