summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Russotto <matthew.russotto@10gen.com>2018-01-08 17:06:56 -0500
committerMatthew Russotto <matthew.russotto@10gen.com>2018-01-08 17:07:32 -0500
commit8b766fdc6c896ba0a4db30d7b9d2f050b464db22 (patch)
tree107cf6c037074c53bdddb61423c4c6cb352b3003
parent27ccd4a3bf1150a1bf4356de8a91e4dff64e0762 (diff)
downloadmongo-8b766fdc6c896ba0a4db30d7b9d2f050b464db22.tar.gz
SERVER-32162 Remove non-atomic (push-based replication) support from doTxn.
-rw-r--r--jstests/auth/lib/commands_lib.js68
-rw-r--r--jstests/core/do_txn_atomicity.js16
-rw-r--r--jstests/core/do_txn_basic.js389
-rw-r--r--src/mongo/db/commands/apply_ops_cmd.cpp3
-rw-r--r--src/mongo/db/commands/do_txn_cmd.cpp150
-rw-r--r--src/mongo/db/commands/oplog_application_checks.cpp14
-rw-r--r--src/mongo/db/commands/oplog_application_checks.h5
-rw-r--r--src/mongo/db/repl/do_txn.cpp250
-rw-r--r--src/mongo/db/repl/do_txn.h2
-rw-r--r--src/mongo/db/repl/do_txn_test.cpp65
10 files changed, 154 insertions, 808 deletions
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js
index eb539d5f5e2..aa67cf95016 100644
--- a/jstests/auth/lib/commands_lib.js
+++ b/jstests/auth/lib/commands_lib.js
@@ -2956,30 +2956,14 @@ var authCommandsLib = {
]
},
{
- testname: "doTxn_empty",
- command: {doTxn: []},
- skipSharded: true,
- testcases: [
- {
- roles: {__system: 1},
- runOnDb: adminDbName,
- },
- {
- roles: {__system: 1},
- runOnDb: firstDbName,
- }
- ]
- },
- {
testname: "doTxn_precondition",
command: {
doTxn: [{
- "ts": Timestamp(1473353037, 1),
"h": NumberLong(0),
"v": 2,
- "op": "n",
- "ns": "",
- "o": {}
+ "op": "i",
+ "ns": firstDbName + ".x",
+ "o": {"_id": ObjectId("57dc3d7da4fce4358afa85b8"), "data": 5}
}],
preCondition: [{ns: firstDbName + ".x", q: {x: 5}, res: []}]
},
@@ -2995,49 +2979,15 @@ var authCommandsLib = {
runOnDb: adminDbName,
privileges: [
{resource: {db: firstDbName, collection: "x"}, actions: ["find"]},
- {
- resource: {cluster: true},
- actions: ["appendOplogNote"],
- removeWhenTestingAuthzFailure: false
- },
- ],
- },
- ]
- },
- {
- testname: "doTxn_noop",
- command: {
- doTxn: [{
- "ts": Timestamp(1473353037, 1),
- "h": NumberLong(0),
- "v": 2,
- "op": "n",
- "ns": "",
- "o": {}
- }]
- },
- skipSharded: true,
- testcases: [
- {
- runOnDb: adminDbName,
- privileges: [
- {resource: {cluster: true}, actions: ["appendOplogNote"]},
+ {resource: {db: firstDbName, collection: "x"}, actions: ["insert"]},
],
},
- {
- runOnDb: firstDbName,
- privileges: [
- {resource: {cluster: true}, actions: ["appendOplogNote"]},
- ],
- expectFailure: true
- }
]
},
{
testname: "doTxn_insert",
command: {
doTxn: [{
- "ts": Timestamp(1474051453, 1),
"h": NumberLong(0),
"v": 2,
"op": "i",
@@ -3067,7 +3017,6 @@ var authCommandsLib = {
command: function(state) {
return {
doTxn: [{
- "ts": Timestamp(1474051453, 1),
"h": NumberLong(0),
"v": 2,
"op": "i",
@@ -3106,7 +3055,6 @@ var authCommandsLib = {
command: function(state) {
return {
doTxn: [{
- "ts": Timestamp(1474051453, 1),
"h": NumberLong(0),
"v": 2,
"op": "i",
@@ -3149,7 +3097,6 @@ var authCommandsLib = {
command: function(state) {
return {
doTxn: [{
- "ts": Timestamp(1474051453, 1),
"h": NumberLong(0),
"v": 2,
"op": "i",
@@ -3188,7 +3135,6 @@ var authCommandsLib = {
command: function(state) {
return {
doTxn: [{
- "ts": Timestamp(1474051453, 1),
"h": NumberLong(0),
"v": 2,
"op": "i",
@@ -3230,7 +3176,6 @@ var authCommandsLib = {
command: function(state) {
return {
doTxn: [{
- "ts": Timestamp(1474051453, 1),
"h": NumberLong(0),
"v": 2,
"op": "i",
@@ -3272,7 +3217,6 @@ var authCommandsLib = {
testname: "doTxn_upsert",
command: {
doTxn: [{
- "ts": Timestamp(1474053682, 1),
"h": NumberLong(0),
"v": 2,
"op": "u",
@@ -3302,7 +3246,6 @@ var authCommandsLib = {
testname: "doTxn_update",
command: {
doTxn: [{
- "ts": Timestamp(1474053682, 1),
"h": NumberLong(0),
"v": 2,
"op": "u",
@@ -3334,7 +3277,6 @@ var authCommandsLib = {
command: function(state) {
return {
doTxn: [{
- "ts": Timestamp(1474053682, 1),
"h": NumberLong(0),
"v": 2,
"op": "u",
@@ -3375,7 +3317,6 @@ var authCommandsLib = {
command: function(state) {
return {
doTxn: [{
- "ts": Timestamp(1474053682, 1),
"h": NumberLong(0),
"v": 2,
"op": "u",
@@ -3414,7 +3355,6 @@ var authCommandsLib = {
testname: "doTxn_delete",
command: {
doTxn: [{
- "ts": Timestamp(1474056194, 1),
"h": NumberLong(0),
"v": 2,
"op": "d",
diff --git a/jstests/core/do_txn_atomicity.js b/jstests/core/do_txn_atomicity.js
index 8dfe6c51d14..5e82d0f1376 100644
--- a/jstests/core/do_txn_atomicity.js
+++ b/jstests/core/do_txn_atomicity.js
@@ -8,24 +8,26 @@
t.drop();
assert.writeOK(t.insert({_id: 1}));
- // Operations including commands should not be atomic, so the insert will succeed.
- assert.commandFailed(db.adminCommand({
+ // Operations including commands are not allowed and should be rejected completely.
+ assert.commandFailedWithCode(db.adminCommand({
doTxn: [
{op: 'i', ns: t.getFullName(), o: {_id: ObjectId(), x: 1}},
{op: 'c', ns: "invalid", o: {create: "t"}},
]
- }));
- assert.eq(t.count({x: 1}), 1);
+ }),
+ ErrorCodes.InvalidOptions);
+ assert.eq(t.count({x: 1}), 0);
// Operations only including CRUD commands should be atomic, so the next insert will fail.
var tooLong = Array(2000).join("hello");
- assert.commandFailed(db.adminCommand({
+ assert.commandFailedWithCode(db.adminCommand({
doTxn: [
{op: 'i', ns: t.getFullName(), o: {_id: ObjectId(), x: 1}},
{op: 'i', ns: t.getFullName(), o: {_id: tooLong, x: 1}},
]
- }));
- assert.eq(t.count({x: 1}), 1);
+ }),
+ ErrorCodes.KeyTooLong);
+ assert.eq(t.count({x: 1}), 0);
// Operations on non-existent databases cannot be atomic.
var newDBName = "do_txn_atomicity";
diff --git a/jstests/core/do_txn_basic.js b/jstests/core/do_txn_basic.js
index c9b1b75ef81..e7b30cf28c7 100644
--- a/jstests/core/do_txn_basic.js
+++ b/jstests/core/do_txn_basic.js
@@ -13,8 +13,9 @@
//
// Empty array of operations.
- assert.commandWorked(db.adminCommand({doTxn: []}),
- 'doTxn should not fail on empty array of operations');
+ assert.commandFailedWithCode(db.adminCommand({doTxn: []}),
+ ErrorCodes.InvalidOptions,
+ 'doTxn should fail on empty array of operations');
// Non-array type for operations.
assert.commandFailed(db.adminCommand({doTxn: "not an array"}),
@@ -33,259 +34,41 @@
'doTxn should fail on operation with empty "op" field value');
// Missing 'ns' field in an operation.
- assert.commandFailed(db.adminCommand({doTxn: [{op: 'c'}]}),
+ assert.commandFailed(db.adminCommand({doTxn: [{op: 'u'}]}),
'doTxn should fail on operation without "ns" field');
// Non-string 'ns' field in an operation.
- assert.commandFailed(db.adminCommand({doTxn: [{op: 'c', ns: 12345}]}),
+ assert.commandFailed(db.adminCommand({doTxn: [{op: 'u', ns: 12345}]}),
'doTxn should fail on operation with non-string "ns" field');
- // Empty 'ns' field value in an operation of type 'n' (noop).
- assert.commandWorked(db.adminCommand({doTxn: [{op: 'n', ns: ''}]}),
- 'doTxn should work on no op operation with empty "ns" field value');
-
// Missing dbname in 'ns' field.
assert.commandFailed(db.adminCommand({doTxn: [{op: 'd', ns: t.getName(), o: {_id: 1}}]}));
- // Missing 'o' field value in an operation of type 'c' (command).
- assert.commandFailed(db.adminCommand({doTxn: [{op: 'c', ns: t.getFullName()}]}),
- 'doTxn should fail on command operation without "o" field');
-
- // Non-object 'o' field value in an operation of type 'c' (command).
- assert.commandFailed(db.adminCommand({doTxn: [{op: 'c', ns: t.getFullName(), o: 'bar'}]}),
- 'doTxn should fail on command operation with non-object "o" field');
-
- // Empty object 'o' field value in an operation of type 'c' (command).
- assert.commandFailed(db.adminCommand({doTxn: [{op: 'c', ns: t.getFullName(), o: {}}]}),
- 'doTxn should fail on command operation with empty object "o" field');
-
- // Unknown key in 'o' field value in an operation of type 'c' (command).
- assert.commandFailed(db.adminCommand({doTxn: [{op: 'c', ns: t.getFullName(), o: {a: 1}}]}),
- 'doTxn should fail on command operation on unknown key in "o" field');
-
- // Empty 'ns' field value in operation type other than 'n'.
- assert.commandFailed(db.adminCommand({doTxn: [{op: 'c', ns: ''}]}),
- 'doTxn should fail on non-"n" operation type with empty "ns" field value');
-
- // Excessively nested doTxn commands gracefully fail.
- assert.commandFailed(db.adminCommand({
- "doTxn": [{
- "ts": {"$timestamp": {"t": 1, "i": 100}},
- "h": 0,
- "v": 2,
- "op": "c",
- "ns": "test.$cmd",
- "o": {
- "doTxn": [{
- "ts": {"$timestamp": {"t": 1, "i": 100}},
- "h": 0,
- "v": 2,
- "op": "c",
- "ns": "test.$cmd",
- "o": {
- "doTxn": [{
- "ts": {"$timestamp": {"t": 1, "i": 100}},
- "h": 0,
- "v": 2,
- "op": "c",
- "ns": "test.$cmd",
- "o": {
- "doTxn": [{
- "ts": {"$timestamp": {"t": 1, "i": 100}},
- "h": 0,
- "v": 2,
- "op": "c",
- "ns": "test.$cmd",
- "o": {
- "doTxn": [{
- "ts": {"$timestamp": {"t": 1, "i": 100}},
- "h": 0,
- "v": 2,
- "op": "c",
- "ns": "test.$cmd",
- "o": {
- "doTxn": [{
- "ts": {"$timestamp": {"t": 1, "i": 100}},
- "h": 0,
- "v": 2,
- "op": "c",
- "ns": "test.$cmd",
- "o": {
- "doTxn": [{
- "ts":
- {"$timestamp": {"t": 1, "i": 100}},
- "h": 0,
- "v": 2,
- "op": "c",
- "ns": "test.$cmd",
- "o": {
- "doTxn": [{
- "ts": {
- "$timestamp":
- {"t": 1, "i": 100}
- },
- "h": 0,
- "v": 2,
- "op": "c",
- "ns": "test.$cmd",
- "o": {
- "doTxn": [{
- "ts": {
- "$timestamp": {
- "t": 1,
- "i": 100
- }
- },
- "h": 0,
- "v": 2,
- "op": "c",
- "ns": "test.$cmd",
- "o": {
- "doTxn": [{
- "ts": {
- "$timestamp":
- {
- "t":
- 1,
- "i":
- 100
- }
- },
- "h": 0,
- "v": 2,
- "op": "c",
- "ns":
- "test.$cmd",
- "o": {
- "doTxn": [{
- "ts": {
- "$timestamp": {
- "t":
- 1,
- "i":
- 100
- }
- },
- "h": 0,
- "v": 2,
- "op":
- "c",
- "ns":
- "test.$cmd",
- "o": {
- "doTxn":
- [
- ]
- }
- }]
- }
- }]
- }
- }]
- }
- }]
- }
- }]
- }
- }]
- }
- }]
- }
- }]
- }
- }]
- }
- }]
- }
- }]
- }),
- "Excessively nested doTxn should be rejected");
-
- // Missing 'o' field value in an operation of type 'i' on 'system.indexes' collection.
- assert.commandFailedWithCode(
- db.adminCommand({doTxn: [{op: 'i', ns: db.getName() + '.system.indexes'}]}),
- ErrorCodes.NoSuchKey,
- 'doTxn should fail on system.indexes insert operation without "o" field');
-
- // Non-object 'o' field value in an operation of type 'i' on 'system.indexes' collection.
- assert.commandFailedWithCode(
- db.adminCommand({doTxn: [{op: 'i', ns: db.getName() + '.system.indexes', o: 'bar'}]}),
- ErrorCodes.TypeMismatch,
- 'doTxn should fail on system.indexes insert operation with non-object "o" field');
-
- // Missing 'ns' field in index spec.
- assert.commandFailedWithCode(
- db.adminCommand({
- doTxn: [{
- op: 'i',
- ns: db.getName() + '.system.indexes',
- o: {
- key: {a: 1},
- name: 'a_1',
- }
- }]
- }),
- ErrorCodes.NoSuchKey,
- 'doTxn should fail on system.indexes insert operation with missing index namespace');
-
- // Non-string 'ns' field in index spec.
- assert.commandFailedWithCode(
- db.adminCommand({
- doTxn: [{
- op: 'i',
- ns: db.getName() + '.system.indexes',
- o: {
- ns: 12345,
- key: {a: 1},
- name: 'a_1',
- }
- }]
- }),
- ErrorCodes.TypeMismatch,
- 'doTxn should fail on system.indexes insert operation with non-string index namespace');
-
- // Invalid 'ns' field in index spec.
- assert.commandFailedWithCode(
- db.adminCommand({
- doTxn: [{
- op: 'i',
- ns: db.getName() + '.system.indexes',
- o: {
- ns: 'invalid_namespace',
- key: {a: 1},
- name: 'a_1',
- }
- }]
- }),
- ErrorCodes.InvalidNamespace,
- 'doTxn should fail on system.indexes insert operation with invalid index namespace');
-
- // Inconsistent database name in index spec namespace.
- assert.commandFailedWithCode(
- db.adminCommand({
- doTxn: [{
- op: 'i',
- ns: db.getName() + '.system.indexes',
- o: {
- ns: 'baddbprefix' + t.getFullName(),
- key: {a: 1},
- name: 'a_1',
- }
- }]
- }),
- ErrorCodes.InvalidNamespace,
- 'doTxn should fail on system.indexes insert operation with index namespace containing ' +
- 'inconsistent database name');
+ // Empty 'ns' field value.
+ assert.commandFailed(db.adminCommand({doTxn: [{op: 'u', ns: ''}]}),
+ 'doTxn should fail with empty "ns" field value');
// Valid 'ns' field value in unknown operation type 'x'.
assert.commandFailed(db.adminCommand({doTxn: [{op: 'x', ns: t.getFullName()}]}),
'doTxn should fail on unknown operation type "x" with valid "ns" value');
+ // Illegal operation type 'n' (no-op).
+ assert.commandFailedWithCode(db.adminCommand({doTxn: [{op: 'n', ns: t.getFullName()}]}),
+ ErrorCodes.InvalidOptions,
+ 'doTxn should fail on "no op" operations.');
+
+ // Illegal operation type 'c' (command).
+ assert.commandFailedWithCode(
+ db.adminCommand(
+ {doTxn: [{op: 'c', ns: t.getCollection('$cmd').getFullName(), o: {applyOps: []}}]}),
+ ErrorCodes.InvalidOptions,
+ 'doTxn should fail on commands.');
+
assert.eq(0, t.find().count(), "Non-zero amount of documents in collection to start");
/**
* Test function for running CRUD operations on non-existent namespaces using various
- * combinations of invalid namespaces (collection/database), allowAtomic and alwaysUpsert.
+ * combinations of invalid namespaces (collection/database)
*
* Leave 'expectedErrorCode' undefined if this command is expected to run successfully.
*/
@@ -294,29 +77,22 @@
const t2 = db.getSiblingDB('do_txn1_no_such_db').getCollection('t');
[t, t2].forEach(coll => {
const op = {op: optype, ns: coll.getFullName(), o: o, o2: o2};
- [false, true].forEach(allowAtomic => {
- [false, true].forEach(alwaysUpsert => {
- const cmd = {doTxn: [op], allowAtomic: allowAtomic, alwaysUpsert: alwaysUpsert};
- jsTestLog('Testing doTxn on non-existent namespace: ' + tojson(cmd));
- if (expectedErrorCode === ErrorCodes.OK) {
- assert.commandWorked(db.adminCommand(cmd));
- } else {
- assert.commandFailedWithCode(db.adminCommand(cmd), expectedErrorCode);
- }
- });
- });
+ const cmd = {doTxn: [op]};
+ jsTestLog('Testing doTxn on non-existent namespace: ' + tojson(cmd));
+ if (expectedErrorCode === ErrorCodes.OK) {
+ assert.commandWorked(db.adminCommand(cmd));
+ } else {
+ assert.commandFailedWithCode(db.adminCommand(cmd), expectedErrorCode);
+ }
});
}
- // Insert and update operations on non-existent collections/databases should return
+ // Insert, delete, and update operations on non-existent collections/databases should return
// NamespaceNotFound.
testCrudOperationOnNonExistentNamespace('i', {_id: 0}, {}, ErrorCodes.NamespaceNotFound);
+ testCrudOperationOnNonExistentNamespace('d', {_id: 0}, {}, ErrorCodes.NamespaceNotFound);
testCrudOperationOnNonExistentNamespace('u', {x: 0}, {_id: 0}, ErrorCodes.NamespaceNotFound);
- // Delete operations on non-existent collections/databases should return OK for idempotency
- // reasons.
- testCrudOperationOnNonExistentNamespace('d', {_id: 0}, {});
-
assert.commandWorked(db.createCollection(t.getName()));
var a = assert.commandWorked(
db.adminCommand({doTxn: [{"op": "i", "ns": t.getFullName(), "o": {_id: 5, x: 17}}]}));
@@ -351,33 +127,13 @@
assert.eq(true, res.results[1], "Bad result value for valid update");
// preCondition fully matches
- res = db.runCommand({
+ res = assert.commandWorked(db.runCommand({
doTxn: [
{op: "u", ns: t.getFullName(), o2: {_id: 5}, o: {$set: {x: 20}}},
{op: "u", ns: t.getFullName(), o2: {_id: 5}, o: {$set: {x: 21}}}
],
preCondition: [{ns: t.getFullName(), q: {_id: 5}, res: {x: 19}}]
- });
-
- // The use of preCondition requires doTxn to run atomically. Therefore, it is incompatible
- // with {allowAtomic: false}.
- assert.commandFailedWithCode(
- db.runCommand({
- doTxn: [{op: 'u', ns: t.getFullName(), o2: {_id: 5}, o: {$set: {x: 22}}}],
- preCondition: [{ns: t.getFullName(), q: {_id: 5}, res: {x: 21}}],
- allowAtomic: false,
- }),
- ErrorCodes.InvalidOptions,
- 'doTxn should fail when preCondition is present and atomicAllowed is false.');
-
- // The use of preCondition is also incompatible with operations that include commands.
- assert.commandFailedWithCode(
- db.runCommand({
- doTxn: [{op: 'c', ns: t.getCollection('$cmd').getFullName(), o: {doTxn: []}}],
- preCondition: [{ns: t.getFullName(), q: {_id: 5}, res: {x: 21}}],
- }),
- ErrorCodes.InvalidOptions,
- 'doTxn should fail when preCondition is present and operations includes commands.');
+ }));
o.x++;
o.x++;
@@ -388,40 +144,40 @@
assert.eq(true, res.results[1], "Bad result value for valid update");
// preCondition doesn't match ns
- res = db.runCommand({
+ res = assert.commandFailed(db.runCommand({
doTxn: [
{op: "u", ns: t.getFullName(), o2: {_id: 5}, o: {$set: {x: 22}}},
{op: "u", ns: t.getFullName(), o2: {_id: 5}, o: {$set: {x: 23}}}
],
preCondition: [{ns: "foo.otherName", q: {_id: 5}, res: {x: 21}}]
- });
+ }));
assert.eq(o, t.findOne(), "preCondition didn't match, but ops were still applied");
// preCondition doesn't match query
- res = db.runCommand({
+ res = assert.commandFailed(db.runCommand({
doTxn: [
{op: "u", ns: t.getFullName(), o2: {_id: 5}, o: {$set: {x: 22}}},
{op: "u", ns: t.getFullName(), o2: {_id: 5}, o: {$set: {x: 23}}}
],
preCondition: [{ns: t.getFullName(), q: {_id: 5}, res: {x: 19}}]
- });
+ }));
assert.eq(o, t.findOne(), "preCondition didn't match, but ops were still applied");
- res = db.runCommand({
+ res = assert.commandFailed(db.runCommand({
doTxn: [
{op: "u", ns: t.getFullName(), o2: {_id: 5}, o: {$set: {x: 22}}},
{op: "u", ns: t.getFullName(), o2: {_id: 6}, o: {$set: {x: 23}}}
]
- });
+ }));
- assert.eq(true, res.results[0], "Valid update failed");
- assert.eq(true, res.results[1], "Valid update failed");
+ assert.eq(false, res.results[0], "Op required upsert, which should be disallowed.");
+ assert.eq(false, res.results[1], "Op required upsert, which should be disallowed.");
// Ops with transaction numbers are valid.
var lsid = {id: UUID()};
- res = db.runCommand({
+ res = assert.commandWorked(db.runCommand({
doTxn: [
{
op: "i",
@@ -434,7 +190,7 @@
{
op: "u",
ns: t.getFullName(),
- o2: {_id: 8},
+ o2: {_id: 7},
o: {$set: {x: 25}},
lsid: lsid,
txnNumber: NumberLong(1),
@@ -449,71 +205,12 @@
stmdId: 0
},
]
- });
+ }));
assert.eq(true, res.results[0], "Valid insert with transaction number failed");
assert.eq(true, res.results[1], "Valid update with transaction number failed");
assert.eq(true, res.results[2], "Valid delete with transaction number failed");
- // Foreground index build.
- res = assert.commandWorked(db.adminCommand({
- doTxn: [{
- "op": "i",
- "ns": db.getName() + ".system.indexes",
- "o": {
- ns: t.getFullName(),
- key: {a: 1},
- name: "a_1",
- }
- }]
- }));
- assert.eq(1, res.applied, "Incorrect number of operations applied");
- assert.eq(true, res.results[0], "Foreground index creation failed");
- var allIndexes = t.getIndexes();
- var spec = GetIndexHelpers.findByName(allIndexes, "a_1");
- assert.neq(null, spec, "Foreground index 'a_1' not found: " + tojson(allIndexes));
- assert.eq(1, spec.v, "Expected v=1 index to be built since 'v' field was omitted");
-
- // Background indexes are created in the foreground when processed by doTxn.
- res = assert.commandWorked(db.adminCommand({
- doTxn: [{
- "op": "i",
- "ns": db.getName() + ".system.indexes",
- "o": {
- ns: t.getFullName(),
- key: {b: 1},
- name: "b_1",
- background: true,
- }
- }]
- }));
- assert.eq(1, res.applied, "Incorrect number of operations applied");
- assert.eq(true, res.results[0], "Background index creation failed");
- allIndexes = t.getIndexes();
- spec = GetIndexHelpers.findByName(allIndexes, "b_1");
- assert.neq(null, spec, "Background index 'b_1' not found: " + tojson(allIndexes));
- assert.eq(1, spec.v, "Expected v=1 index to be built since 'v' field was omitted");
-
- // Foreground v=2 index build.
- res = assert.commandWorked(db.adminCommand({
- doTxn: [{
- "op": "i",
- "ns": db.getName() + ".system.indexes",
- "o": {
- ns: t.getFullName(),
- key: {c: 1},
- name: "c_1",
- v: 2,
- }
- }]
- }));
- assert.eq(1, res.applied, "Incorrect number of operations applied");
- assert.eq(true, res.results[0], "Foreground v=2 index creation failed");
- allIndexes = t.getIndexes();
- spec = GetIndexHelpers.findByName(allIndexes, "c_1");
- assert.neq(null, spec, "Foreground index 'c_1' not found: " + tojson(allIndexes));
- assert.eq(2, spec.v, "Expected v=2 index to be built");
-
// When applying a "u" (update) op, we default to 'UpdateNode' update semantics, and $set
// operations add new fields in lexicographic order.
res = assert.commandWorked(db.adminCommand({
diff --git a/src/mongo/db/commands/apply_ops_cmd.cpp b/src/mongo/db/commands/apply_ops_cmd.cpp
index 4d51368a9e4..5822cf9b61b 100644
--- a/src/mongo/db/commands/apply_ops_cmd.cpp
+++ b/src/mongo/db/commands/apply_ops_cmd.cpp
@@ -219,8 +219,7 @@ public:
const std::string& dbname,
const BSONObj& cmdObj) override {
OplogApplicationValidity validity = validateApplyOpsCommand(cmdObj);
- return OplogApplicationChecks::checkAuthForCommand(
- opCtx, dbname, cmdObj, validity, OplogApplicationCommand::kApplyOpsCmd);
+ return OplogApplicationChecks::checkAuthForCommand(opCtx, dbname, cmdObj, validity);
}
bool run(OperationContext* opCtx,
diff --git a/src/mongo/db/commands/do_txn_cmd.cpp b/src/mongo/db/commands/do_txn_cmd.cpp
index 3ed00c1e736..58965f0283f 100644
--- a/src/mongo/db/commands/do_txn_cmd.cpp
+++ b/src/mongo/db/commands/do_txn_cmd.cpp
@@ -49,6 +49,7 @@
#include "mongo/db/operation_context.h"
#include "mongo/db/repl/do_txn.h"
#include "mongo/db/repl/oplog.h"
+#include "mongo/db/repl/oplog_entry_gen.h"
#include "mongo/db/repl/repl_client_info.h"
#include "mongo/db/repl/replication_coordinator_global.h"
#include "mongo/db/service_context.h"
@@ -59,35 +60,12 @@
namespace mongo {
namespace {
-bool checkCOperationType(const BSONObj& opObj, const StringData opName) {
- BSONElement opTypeElem = opObj["op"];
- checkBSONType(BSONType::String, opTypeElem);
- const StringData opType = opTypeElem.checkAndGetStringData();
-
- if (opType == "c"_sd) {
- BSONElement oElem = opObj["o"];
- checkBSONType(BSONType::Object, oElem);
- BSONObj o = oElem.Obj();
-
- if (o.firstElement().fieldNameStringData() == opName) {
- return true;
- }
- }
- return false;
-};
-
/**
- * Returns kNeedsSuperuser, if the provided doTxn command contains
- * an empty doTxn command or createCollection/renameCollection commands are mixed in doTxn
- * batch. Returns kNeedForceAndUseUUID if an operation contains a UUID, and will create a collection
- * with the user-specified UUID. Returns
- * kNeedsUseUUID if the operation contains a UUID. Returns kOk if no conditions
- * which must be specially handled are detected. May throw exceptions if the input is malformed.
+ * Returns kNeedsUseUUID if the operation contains a UUID. Returns kOk if no conditions
+ * which must be specially handled are detected. Throws an exception if the input is malformed or
+ * if a command is in the list of ops.
*/
-OplogApplicationValidity validateDoTxnCommand(const BSONObj& cmdObj) {
- const size_t maxDoTxnDepth = 10;
- std::stack<std::pair<size_t, BSONObj>> toCheck;
-
+OplogApplicationValidity validateDoTxnCommand(const BSONObj& doTxnObj) {
auto operationContainsUUID = [](const BSONObj& opObj) {
auto anyTopLevelElementIsUUID = [](const BSONObj& opObj) {
for (const BSONElement opElement : opObj) {
@@ -121,73 +99,31 @@ OplogApplicationValidity validateDoTxnCommand(const BSONObj& cmdObj) {
OplogApplicationValidity ret = OplogApplicationValidity::kOk;
- // Insert the top level doTxn command into the stack.
- toCheck.emplace(std::make_pair(0, cmdObj));
-
- while (!toCheck.empty()) {
- size_t depth;
- BSONObj doTxnObj;
- std::tie(depth, doTxnObj) = toCheck.top();
- toCheck.pop();
-
- checkBSONType(BSONType::Array, doTxnObj.firstElement());
- // Check if the doTxn command is empty. This is probably not something that should
- // happen, so require a superuser to do this.
- if (doTxnObj.firstElement().Array().empty()) {
- return OplogApplicationValidity::kNeedsSuperuser;
- }
-
- // createCollection and renameCollection are only allowed to be applied
- // individually. Ensure there is no create/renameCollection in a batch
- // of size greater than 1.
- if (doTxnObj.firstElement().Array().size() > 1) {
- for (const BSONElement& e : doTxnObj.firstElement().Array()) {
- checkBSONType(BSONType::Object, e);
- auto oplogEntry = e.Obj();
- if (checkCOperationType(oplogEntry, "create"_sd) ||
- checkCOperationType(oplogEntry, "renameCollection"_sd)) {
- return OplogApplicationValidity::kNeedsSuperuser;
- }
- }
- }
+ checkBSONType(BSONType::Array, doTxnObj.firstElement());
+ // Check if the doTxn command is empty. There's no good reason for an empty transaction,
+ // so reject it.
+ uassert(ErrorCodes::InvalidOptions,
+ "An empty doTxn command is not allowed.",
+ !doTxnObj.firstElement().Array().empty());
- // For each doTxn command, iterate the ops.
- for (BSONElement element : doTxnObj.firstElement().Array()) {
- checkBSONType(BSONType::Object, element);
- BSONObj opObj = element.Obj();
+ // Iterate the ops.
+ for (BSONElement element : doTxnObj.firstElement().Array()) {
+ checkBSONType(BSONType::Object, element);
+ BSONObj opObj = element.Obj();
- bool opHasUUIDs = operationContainsUUID(opObj);
-
- if (serverGlobalParams.featureCompatibility.getVersion() ==
- ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo34) {
- uassert(ErrorCodes::OplogOperationUnsupported,
- "doTxn with UUID requires upgrading to FeatureCompatibilityVersion 3.6",
- !opHasUUIDs);
- }
-
- // If the op uses any UUIDs at all then the user must possess extra privileges.
- if (opHasUUIDs && ret == OplogApplicationValidity::kOk)
- ret = OplogApplicationValidity::kNeedsUseUUID;
- if (opHasUUIDs && checkCOperationType(opObj, "create"_sd)) {
- // If the op is 'c' and forces the server to ingest a collection
- // with a specific, user defined UUID.
- ret = OplogApplicationValidity::kNeedsForceAndUseUUID;
- }
-
- // If the op contains a nested doTxn...
- if (checkCOperationType(opObj, "doTxn"_sd)) {
- // And we've recursed too far, then bail out.
- uassert(ErrorCodes::FailedToParse, "Too many nested doTxn", depth < maxDoTxnDepth);
-
- // Otherwise, if the op contains an doTxn, but we haven't recursed too far:
- // extract the doTxn command, and insert it into the stack.
- checkBSONType(BSONType::Object, opObj["o"]);
- BSONObj oObj = opObj["o"].Obj();
- toCheck.emplace(std::make_pair(depth + 1, std::move(oObj)));
- }
+ // If the op is a command, it's illegal.
+ BSONElement opTypeElem = opObj["op"];
+ const StringData opTypeStr = opTypeElem.checkAndGetStringData();
+ auto opType = repl::OpType_parse(IDLParserErrorContext("validateDoTxnCommand"), opTypeStr);
+ uassert(ErrorCodes::InvalidOptions,
+ "Commands cannot be applied via doTxn.",
+ opType != repl::OpTypeEnum::kCommand);
+
+ // If the op uses any UUIDs at all then the user must possess extra privileges.
+ if (operationContainsUUID(opObj)) {
+ ret = OplogApplicationValidity::kNeedsUseUUID;
}
}
-
return ret;
}
@@ -212,8 +148,7 @@ public:
const std::string& dbname,
const BSONObj& cmdObj) override {
OplogApplicationValidity validity = validateDoTxnCommand(cmdObj);
- return OplogApplicationChecks::checkAuthForCommand(
- opCtx, dbname, cmdObj, validity, OplogApplicationCommand::kDoTxnCmd);
+ return OplogApplicationChecks::checkAuthForCommand(opCtx, dbname, cmdObj, validity);
}
bool run(OperationContext* opCtx,
@@ -241,36 +176,7 @@ public:
// was acknowledged. To fix this, we should wait for replication of the node’s last applied
// OpTime if the last write operation was a no-op write.
- // We set the OplogApplication::Mode argument based on the mode argument given in the
- // command object. If no mode is given, default to the 'kApplyOpsCmd' mode.
- repl::OplogApplication::Mode oplogApplicationMode =
- repl::OplogApplication::Mode::kApplyOpsCmd; // the default mode.
- std::string oplogApplicationModeString;
- status = bsonExtractStringField(
- cmdObj, DoTxn::kOplogApplicationModeFieldName, &oplogApplicationModeString);
-
- if (status.isOK()) {
- auto modeSW = repl::OplogApplication::parseMode(oplogApplicationModeString);
- if (!modeSW.isOK()) {
- // Unable to parse the mode argument.
- return appendCommandStatus(
- result,
- modeSW.getStatus().withContext(str::stream() << "Could not parse " +
- DoTxn::kOplogApplicationModeFieldName));
- }
- oplogApplicationMode = modeSW.getValue();
- } else if (status != ErrorCodes::NoSuchKey) {
- // NoSuchKey means the user did not supply a mode.
- return appendCommandStatus(result,
- Status(status.code(),
- str::stream() << "Could not parse out "
- << DoTxn::kOplogApplicationModeFieldName
- << ": "
- << status.reason()));
- }
-
- auto doTxnStatus = appendCommandStatus(
- result, doTxn(opCtx, dbname, cmdObj, oplogApplicationMode, &result));
+ auto doTxnStatus = appendCommandStatus(result, doTxn(opCtx, dbname, cmdObj, &result));
return doTxnStatus;
}
diff --git a/src/mongo/db/commands/oplog_application_checks.cpp b/src/mongo/db/commands/oplog_application_checks.cpp
index 39b8b41c2f6..1fbbe738a33 100644
--- a/src/mongo/db/commands/oplog_application_checks.cpp
+++ b/src/mongo/db/commands/oplog_application_checks.cpp
@@ -44,7 +44,6 @@ Status OplogApplicationChecks::checkOperationAuthorization(OperationContext* opC
const std::string& dbname,
const BSONObj& oplogEntry,
AuthorizationSession* authSession,
- OplogApplicationCommand command,
bool alwaysUpsert) {
BSONElement opTypeElem = oplogEntry["op"];
checkBSONType(BSONType::String, opTypeElem);
@@ -76,9 +75,6 @@ Status OplogApplicationChecks::checkOperationAuthorization(OperationContext* opC
BSONObj o = oElem.Obj();
if (opType == "c"_sd) {
- if (command == OplogApplicationCommand::kDoTxnCmd) {
- return Status(ErrorCodes::IllegalOperation, "Commands cannot be applied via doTxn.");
- }
StringData commandName = o.firstElement().fieldNameStringData();
Command* commandInOplogEntry = Command::findCommand(commandName);
if (!commandInOplogEntry) {
@@ -195,8 +191,7 @@ Status OplogApplicationChecks::checkOperation(const BSONElement& e) {
Status OplogApplicationChecks::checkAuthForCommand(OperationContext* opCtx,
const std::string& dbname,
const BSONObj& cmdObj,
- OplogApplicationValidity validity,
- OplogApplicationCommand command) {
+ OplogApplicationValidity validity) {
AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient());
if (validity == OplogApplicationValidity::kNeedsSuperuser) {
std::vector<Privilege> universalPrivileges;
@@ -234,12 +229,7 @@ Status OplogApplicationChecks::checkAuthForCommand(OperationContext* opCtx,
for (const BSONElement& e : cmdObj.firstElement().Array()) {
checkBSONType(BSONType::Object, e);
Status status = OplogApplicationChecks::checkOperationAuthorization(
- opCtx,
- dbname,
- e.Obj(),
- authSession,
- OplogApplicationCommand::kApplyOpsCmd,
- alwaysUpsert);
+ opCtx, dbname, e.Obj(), authSession, alwaysUpsert);
if (!status.isOK()) {
return status;
}
diff --git a/src/mongo/db/commands/oplog_application_checks.h b/src/mongo/db/commands/oplog_application_checks.h
index ec63c6ab505..3dc1d201e1c 100644
--- a/src/mongo/db/commands/oplog_application_checks.h
+++ b/src/mongo/db/commands/oplog_application_checks.h
@@ -52,7 +52,6 @@ class OperationContext;
//
// Only kOk and kNeedsUseUUID are valid for 'doTxn'. All are valid for 'applyOps'.
enum class OplogApplicationValidity { kOk, kNeedsUseUUID, kNeedsForceAndUseUUID, kNeedsSuperuser };
-enum class OplogApplicationCommand { kApplyOpsCmd, kDoTxnCmd };
// OplogApplicationChecks contains helper functions for checking the applyOps and doTxn commands.
class OplogApplicationChecks {
@@ -63,8 +62,7 @@ public:
static Status checkAuthForCommand(OperationContext* opCtx,
const std::string& dbname,
const BSONObj& cmdObj,
- OplogApplicationValidity validity,
- OplogApplicationCommand command);
+ OplogApplicationValidity validity);
/**
* Checks that 'opsElement' is an array and all elements of the array are valid operations.
@@ -82,7 +80,6 @@ private:
const std::string& dbname,
const BSONObj& oplogEntry,
AuthorizationSession* authSession,
- OplogApplicationCommand command,
bool alwaysUpsert);
/**
* Returns OK if 'e' contains a valid operation.
diff --git a/src/mongo/db/repl/do_txn.cpp b/src/mongo/db/repl/do_txn.cpp
index 1bd9d13d8a1..44de9e8590c 100644
--- a/src/mongo/db/repl/do_txn.cpp
+++ b/src/mongo/db/repl/do_txn.cpp
@@ -56,7 +56,6 @@
namespace mongo {
constexpr StringData DoTxn::kPreconditionFieldName;
-constexpr StringData DoTxn::kOplogApplicationModeFieldName;
namespace {
@@ -84,7 +83,6 @@ bool _areOpsCrudOnly(const BSONObj& doTxnCmd) {
// Only consider CRUD operations.
switch (*opType) {
case 'd':
- case 'n':
case 'u':
break;
case 'i':
@@ -102,7 +100,6 @@ bool _areOpsCrudOnly(const BSONObj& doTxnCmd) {
Status _doTxn(OperationContext* opCtx,
const std::string& dbName,
const BSONObj& doTxnCmd,
- repl::OplogApplication::Mode oplogApplicationMode,
BSONObjBuilder* result,
int* numApplied,
BSONArrayBuilder* opsBuilder) {
@@ -113,168 +110,80 @@ Status _doTxn(OperationContext* opCtx,
BSONObjIterator i(ops);
BSONArrayBuilder ab;
- const bool alwaysUpsert =
- doTxnCmd.hasField("alwaysUpsert") ? doTxnCmd["alwaysUpsert"].trueValue() : true;
- const bool haveWrappingWUOW = opCtx->lockState()->inAWriteUnitOfWork();
+ invariant(opCtx->lockState()->inAWriteUnitOfWork());
// Apply each op in the given 'doTxn' command object.
while (i.more()) {
BSONElement e = i.next();
const BSONObj& opObj = e.Obj();
- // Ignore 'n' operations.
- const char* opType = opObj["op"].valuestrsafe();
- if (*opType == 'n')
- continue;
-
const NamespaceString nss(opObj["ns"].String());
// Need to check this here, or OldClientContext may fail an invariant.
- if (*opType != 'c' && !nss.isValid())
+ if (!nss.isValid())
return {ErrorCodes::InvalidNamespace, "invalid ns: " + nss.ns()};
Status status(ErrorCodes::InternalError, "");
- if (haveWrappingWUOW) {
- invariant(opCtx->lockState()->isW());
- invariant(*opType != 'c');
-
- auto db = dbHolder().get(opCtx, nss.ns());
- if (!db) {
- // Retry in non-atomic mode, since MMAP cannot implicitly create a new database
- // within an active WriteUnitOfWork.
- uasserted(ErrorCodes::AtomicityFailure,
- "cannot create a database in atomic doTxn mode; will retry without "
- "atomicity");
- }
-
- // When processing an update on a non-existent collection, applyOperation_inlock()
- // returns UpdateOperationFailed on updates and allows the collection to be
- // implicitly created on upserts. We detect both cases here and fail early with
- // NamespaceNotFound.
- // Additionally for inserts, we fail early on non-existent collections.
- auto collection = db->getCollection(opCtx, nss);
- if (!collection && !nss.isSystemDotIndexes() && (*opType == 'i' || *opType == 'u')) {
- uasserted(
- ErrorCodes::AtomicityFailure,
- str::stream()
- << "cannot apply insert or update operation on a non-existent namespace "
- << nss.ns()
- << " in atomic doTxn mode: "
- << redact(opObj));
- }
+ invariant(opCtx->lockState()->isW());
- // Cannot specify timestamp values in an atomic doTxn.
- if (opObj.hasField("ts")) {
- uasserted(ErrorCodes::AtomicityFailure,
- "cannot apply an op with a timestamp in atomic doTxn mode; "
- "will retry without atomicity");
- }
+ auto db = dbHolder().get(opCtx, nss.ns());
+ if (!db) {
+ uasserted(ErrorCodes::NamespaceNotFound,
+ str::stream() << "cannot apply insert, delete, or update operation on a "
+ "non-existent namespace "
+ << nss.ns()
+ << ": "
+ << mongo::redact(opObj));
+ }
- OldClientContext ctx(opCtx, nss.ns());
-
- status = repl::applyOperation_inlock(
- opCtx, ctx.db(), opObj, alwaysUpsert, oplogApplicationMode);
- if (!status.isOK())
- return status;
-
- // Append completed op, including UUID if available, to 'opsBuilder'.
- if (opsBuilder) {
- if (opObj.hasField("ui") || nss.isSystemDotIndexes() ||
- !(collection && collection->uuid())) {
- // No changes needed to operation document.
- opsBuilder->append(opObj);
- } else {
- // Operation document has no "ui" field and collection has a UUID.
- auto uuid = collection->uuid();
- BSONObjBuilder opBuilder;
- opBuilder.appendElements(opObj);
- uuid->appendToBuilder(&opBuilder, "ui");
- opsBuilder->append(opBuilder.obj());
- }
- }
- } else {
- try {
- status = writeConflictRetry(
- opCtx,
- "doTxn",
- nss.ns(),
- [opCtx, nss, opObj, opType, alwaysUpsert, oplogApplicationMode] {
- if (*opType == 'c') {
- invariant(opCtx->lockState()->isW());
- return repl::applyCommand_inlock(opCtx, opObj, oplogApplicationMode);
- }
+ // When processing an update on a non-existent collection, applyOperation_inlock()
+ // returns UpdateOperationFailed on updates and allows the collection to be
+ // implicitly created on upserts. We detect both cases here and fail early with
+ // NamespaceNotFound.
+ // Additionally for inserts, we fail early on non-existent collections.
+ auto collection = db->getCollection(opCtx, nss);
+ if (!collection && db->getViewCatalog()->lookup(opCtx, nss.ns())) {
+ uasserted(ErrorCodes::CommandNotSupportedOnView,
+ str::stream() << "doTxn not supported on a view: " << redact(opObj));
+ }
+ if (!collection) {
+ uasserted(ErrorCodes::NamespaceNotFound,
+ str::stream() << "cannot apply operation on a non-existent namespace "
+ << nss.ns()
+ << " with doTxn: "
+ << redact(opObj));
+ }
- AutoGetCollection autoColl(opCtx, nss, MODE_IX);
- if (!autoColl.getCollection() && !nss.isSystemDotIndexes()) {
- // For idempotency reasons, return success on delete operations.
- if (*opType == 'd') {
- return Status::OK();
- }
- uasserted(ErrorCodes::NamespaceNotFound,
- str::stream()
- << "cannot apply insert or update operation on a "
- "non-existent namespace "
- << nss.ns()
- << ": "
- << mongo::redact(opObj));
- }
+ // Cannot specify timestamp values in an atomic doTxn.
+ if (opObj.hasField("ts")) {
+ uasserted(ErrorCodes::AtomicityFailure,
+ "cannot apply an op with a timestamp with doTxn; ");
+ }
- OldClientContext ctx(opCtx, nss.ns());
+ OldClientContext ctx(opCtx, nss.ns());
- if (!nss.isSystemDotIndexes()) {
- return repl::applyOperation_inlock(
- opCtx, ctx.db(), opObj, alwaysUpsert, oplogApplicationMode);
- }
+ // Setting alwaysUpsert to true makes sense only during oplog replay, and doTxn commands
+ // should not be executed during oplog replay.
+ const bool alwaysUpsert = false;
+ status = repl::applyOperation_inlock(
+ opCtx, ctx.db(), opObj, alwaysUpsert, repl::OplogApplication::Mode::kApplyOpsCmd);
+ if (!status.isOK())
+ return status;
- auto fieldO = opObj["o"];
- BSONObj indexSpec;
- NamespaceString indexNss;
- std::tie(indexSpec, indexNss) =
- repl::prepForApplyOpsIndexInsert(fieldO, opObj, nss);
- if (!indexSpec["collation"]) {
- // If the index spec does not include a collation, explicitly specify
- // the simple collation, so the index does not inherit the collection
- // default collation.
- auto indexVersion = indexSpec["v"];
- // The index version is populated by prepForApplyOpsIndexInsert().
- invariant(indexVersion);
- if (indexVersion.isNumber() &&
- (indexVersion.numberInt() >=
- static_cast<int>(IndexDescriptor::IndexVersion::kV2))) {
- BSONObjBuilder bob;
- bob.append("collation", CollationSpec::kSimpleSpec);
- bob.appendElements(indexSpec);
- indexSpec = bob.obj();
- }
- }
- BSONObjBuilder command;
- command.append("createIndexes", indexNss.coll());
- {
- BSONArrayBuilder indexes(command.subarrayStart("indexes"));
- indexes.append(indexSpec);
- indexes.doneFast();
- }
- const BSONObj commandObj = command.done();
-
- DBDirectClient client(opCtx);
- BSONObj infoObj;
- client.runCommand(nss.db().toString(), commandObj, infoObj);
-
- // Uassert to stop doTxn only when building indexes, but not for CRUD
- // ops.
- uassertStatusOK(getStatusFromCommandResult(infoObj));
-
- return Status::OK();
- });
- } catch (const DBException& ex) {
- ab.append(false);
- result->append("applied", ++(*numApplied));
- result->append("code", ex.code());
- result->append("codeName", ErrorCodes::errorString(ex.code()));
- result->append("errmsg", ex.what());
- result->append("results", ab.arr());
- return Status(ErrorCodes::UnknownError, ex.what());
+ // Append completed op, including UUID if available, to 'opsBuilder'.
+ if (opsBuilder) {
+ if (opObj.hasField("ui") || nss.isSystemDotIndexes() ||
+ !(collection && collection->uuid())) {
+ // No changes needed to operation document.
+ opsBuilder->append(opObj);
+ } else {
+ // Operation document has no "ui" field and collection has a UUID.
+ auto uuid = collection->uuid();
+ BSONObjBuilder opBuilder;
+ opBuilder.appendElements(opObj);
+ uuid->appendToBuilder(&opBuilder, "ui");
+ opsBuilder->append(opBuilder.obj());
}
}
@@ -364,34 +273,12 @@ Status _checkPrecondition(OperationContext* opCtx,
Status doTxn(OperationContext* opCtx,
const std::string& dbName,
const BSONObj& doTxnCmd,
- repl::OplogApplication::Mode oplogApplicationMode,
BSONObjBuilder* result) {
- bool allowAtomic = false;
- uassertStatusOK(
- bsonExtractBooleanFieldWithDefault(doTxnCmd, "allowAtomic", true, &allowAtomic));
- auto areOpsCrudOnly = _areOpsCrudOnly(doTxnCmd);
- auto isAtomic = allowAtomic && areOpsCrudOnly;
+ uassert(
+ ErrorCodes::InvalidOptions, "doTxn supports only CRUD opts.", _areOpsCrudOnly(doTxnCmd));
auto hasPrecondition = _hasPrecondition(doTxnCmd);
- if (hasPrecondition) {
- uassert(ErrorCodes::InvalidOptions,
- "Cannot use preCondition with {allowAtomic: false}.",
- allowAtomic);
- uassert(ErrorCodes::InvalidOptions,
- "Cannot use preCondition when operations include commands.",
- areOpsCrudOnly);
- }
-
- boost::optional<Lock::GlobalWrite> globalWriteLock;
- boost::optional<Lock::DBLock> dbWriteLock;
-
- // There's only one case where we are allowed to take the database lock instead of the global
- // lock - no preconditions; only CRUD ops; and non-atomic mode.
- if (!hasPrecondition && areOpsCrudOnly && !allowAtomic) {
- dbWriteLock.emplace(opCtx, dbName, MODE_IX);
- } else {
- globalWriteLock.emplace(opCtx);
- }
+ Lock::GlobalWrite globalWriteLock(opCtx);
auto replCoord = repl::ReplicationCoordinator::get(opCtx);
bool userInitiatedWritesAndNotPrimary =
@@ -402,7 +289,6 @@ Status doTxn(OperationContext* opCtx,
str::stream() << "Not primary while applying ops to database " << dbName);
if (hasPrecondition) {
- invariant(isAtomic);
auto status = _checkPrecondition(opCtx, doTxnCmd, result);
if (!status.isOK()) {
return status;
@@ -410,12 +296,6 @@ Status doTxn(OperationContext* opCtx,
}
int numApplied = 0;
- if (!isAtomic) {
- return _doTxn(opCtx, dbName, doTxnCmd, oplogApplicationMode, result, &numApplied, nullptr);
- }
-
- // Perform write ops atomically
- invariant(globalWriteLock);
try {
writeConflictRetry(opCtx, "doTxn", dbName, [&] {
@@ -430,13 +310,8 @@ Status doTxn(OperationContext* opCtx,
{
// Suppress replication for atomic operations until end of doTxn.
repl::UnreplicatedWritesBlock uwb(opCtx);
- uassertStatusOK(_doTxn(opCtx,
- dbName,
- doTxnCmd,
- oplogApplicationMode,
- &intermediateResult,
- &numApplied,
- opsBuilder.get()));
+ uassertStatusOK(_doTxn(
+ opCtx, dbName, doTxnCmd, &intermediateResult, &numApplied, opsBuilder.get()));
}
// Generate oplog entry for all atomic ops collectively.
if (opCtx->writesAreReplicated()) {
@@ -475,11 +350,6 @@ Status doTxn(OperationContext* opCtx,
result->appendElements(intermediateResult.obj());
});
} catch (const DBException& ex) {
- if (ex.code() == ErrorCodes::AtomicityFailure) {
- // Retry in non-atomic mode.
- return _doTxn(
- opCtx, dbName, doTxnCmd, oplogApplicationMode, result, &numApplied, nullptr);
- }
BSONArrayBuilder ab;
++numApplied;
for (int j = 0; j < numApplied; j++)
diff --git a/src/mongo/db/repl/do_txn.h b/src/mongo/db/repl/do_txn.h
index 022474bcbf1..0b6d6c1182c 100644
--- a/src/mongo/db/repl/do_txn.h
+++ b/src/mongo/db/repl/do_txn.h
@@ -36,7 +36,6 @@ class OperationContext;
class DoTxn {
public:
static constexpr StringData kPreconditionFieldName = "preCondition"_sd;
- static constexpr StringData kOplogApplicationModeFieldName = "oplogApplicationMode"_sd;
};
/**
@@ -52,7 +51,6 @@ public:
Status doTxn(OperationContext* opCtx,
const std::string& dbName,
const BSONObj& doTxnCmd,
- repl::OplogApplication::Mode oplogApplicationMode,
BSONObjBuilder* result);
} // namespace mongo
diff --git a/src/mongo/db/repl/do_txn_test.cpp b/src/mongo/db/repl/do_txn_test.cpp
index cb50d3a1d57..de0de6ab4b9 100644
--- a/src/mongo/db/repl/do_txn_test.cpp
+++ b/src/mongo/db/repl/do_txn_test.cpp
@@ -136,11 +136,10 @@ Status getStatusFromDoTxnResult(const BSONObj& result) {
TEST_F(DoTxnTest, AtomicDoTxnWithNoOpsReturnsSuccess) {
auto opCtx = cc().makeOperationContext();
- auto mode = OplogApplication::Mode::kApplyOpsCmd;
BSONObjBuilder resultBuilder;
auto cmdObj = BSON("doTxn" << BSONArray());
auto expectedCmdObj = BSON("applyOps" << BSONArray());
- ASSERT_OK(doTxn(opCtx.get(), "test", cmdObj, mode, &resultBuilder));
+ ASSERT_OK(doTxn(opCtx.get(), "test", cmdObj, &resultBuilder));
ASSERT_BSONOBJ_EQ(expectedCmdObj, _opObserver->onApplyOpsCmdObj);
}
@@ -185,13 +184,11 @@ BSONObj makeApplyOpsWithInsertOperation(const NamespaceString& nss,
TEST_F(DoTxnTest, AtomicDoTxnInsertIntoNonexistentCollectionReturnsNamespaceNotFoundInResult) {
auto opCtx = cc().makeOperationContext();
- auto mode = OplogApplication::Mode::kApplyOpsCmd;
NamespaceString nss("test.t");
auto documentToInsert = BSON("_id" << 0);
auto cmdObj = makeDoTxnWithInsertOperation(nss, boost::none, documentToInsert);
BSONObjBuilder resultBuilder;
- ASSERT_EQUALS(ErrorCodes::UnknownError,
- doTxn(opCtx.get(), "test", cmdObj, mode, &resultBuilder));
+ ASSERT_EQUALS(ErrorCodes::UnknownError, doTxn(opCtx.get(), "test", cmdObj, &resultBuilder));
auto result = resultBuilder.obj();
auto status = getStatusFromDoTxnResult(result);
ASSERT_EQUALS(ErrorCodes::NamespaceNotFound, status);
@@ -199,7 +196,6 @@ TEST_F(DoTxnTest, AtomicDoTxnInsertIntoNonexistentCollectionReturnsNamespaceNotF
TEST_F(DoTxnTest, AtomicDoTxnInsertIntoCollectionWithoutUuid) {
auto opCtx = cc().makeOperationContext();
- auto mode = OplogApplication::Mode::kApplyOpsCmd;
NamespaceString nss("test.t");
// Collection has no uuid.
@@ -210,13 +206,12 @@ TEST_F(DoTxnTest, AtomicDoTxnInsertIntoCollectionWithoutUuid) {
auto cmdObj = makeDoTxnWithInsertOperation(nss, boost::none, documentToInsert);
auto expectedCmdObj = makeApplyOpsWithInsertOperation(nss, boost::none, documentToInsert);
BSONObjBuilder resultBuilder;
- ASSERT_OK(doTxn(opCtx.get(), "test", cmdObj, mode, &resultBuilder));
+ ASSERT_OK(doTxn(opCtx.get(), "test", cmdObj, &resultBuilder));
ASSERT_BSONOBJ_EQ(expectedCmdObj, _opObserver->onApplyOpsCmdObj);
}
TEST_F(DoTxnTest, AtomicDoTxnInsertWithUuidIntoCollectionWithUuid) {
auto opCtx = cc().makeOperationContext();
- auto mode = OplogApplication::Mode::kApplyOpsCmd;
NamespaceString nss("test.t");
auto uuid = UUID::gen();
@@ -229,13 +224,12 @@ TEST_F(DoTxnTest, AtomicDoTxnInsertWithUuidIntoCollectionWithUuid) {
auto cmdObj = makeDoTxnWithInsertOperation(nss, uuid, documentToInsert);
auto expectedCmdObj = makeApplyOpsWithInsertOperation(nss, uuid, documentToInsert);
BSONObjBuilder resultBuilder;
- ASSERT_OK(doTxn(opCtx.get(), "test", cmdObj, mode, &resultBuilder));
+ ASSERT_OK(doTxn(opCtx.get(), "test", cmdObj, &resultBuilder));
ASSERT_BSONOBJ_EQ(expectedCmdObj, _opObserver->onApplyOpsCmdObj);
}
TEST_F(DoTxnTest, AtomicDoTxnInsertWithUuidIntoCollectionWithoutUuid) {
auto opCtx = cc().makeOperationContext();
- auto mode = OplogApplication::Mode::kApplyOpsCmd;
NamespaceString nss("test.t");
auto uuid = UUID::gen();
@@ -249,8 +243,7 @@ TEST_F(DoTxnTest, AtomicDoTxnInsertWithUuidIntoCollectionWithoutUuid) {
auto documentToInsert = BSON("_id" << 0);
auto cmdObj = makeDoTxnWithInsertOperation(nss, uuid, documentToInsert);
BSONObjBuilder resultBuilder;
- ASSERT_EQUALS(ErrorCodes::UnknownError,
- doTxn(opCtx.get(), "test", cmdObj, mode, &resultBuilder));
+ ASSERT_EQUALS(ErrorCodes::UnknownError, doTxn(opCtx.get(), "test", cmdObj, &resultBuilder));
auto result = resultBuilder.obj();
auto status = getStatusFromDoTxnResult(result);
ASSERT_EQUALS(ErrorCodes::NamespaceNotFound, status);
@@ -258,7 +251,6 @@ TEST_F(DoTxnTest, AtomicDoTxnInsertWithUuidIntoCollectionWithoutUuid) {
TEST_F(DoTxnTest, AtomicDoTxnInsertWithoutUuidIntoCollectionWithUuid) {
auto opCtx = cc().makeOperationContext();
- auto mode = OplogApplication::Mode::kApplyOpsCmd;
NamespaceString nss("test.t");
auto uuid = UUID::gen();
@@ -270,7 +262,7 @@ TEST_F(DoTxnTest, AtomicDoTxnInsertWithoutUuidIntoCollectionWithUuid) {
auto documentToInsert = BSON("_id" << 0);
auto cmdObj = makeDoTxnWithInsertOperation(nss, boost::none, documentToInsert);
BSONObjBuilder resultBuilder;
- ASSERT_OK(doTxn(opCtx.get(), "test", cmdObj, mode, &resultBuilder));
+ ASSERT_OK(doTxn(opCtx.get(), "test", cmdObj, &resultBuilder));
// Insert operation provided by caller did not contain collection uuid but doTxn() should add
// the uuid to the oplog entry.
@@ -278,51 +270,6 @@ TEST_F(DoTxnTest, AtomicDoTxnInsertWithoutUuidIntoCollectionWithUuid) {
ASSERT_BSONOBJ_EQ(expectedCmdObj, _opObserver->onApplyOpsCmdObj);
}
-TEST_F(DoTxnTest, DoTxnPropagatesOplogApplicationMode) {
- auto opCtx = cc().makeOperationContext();
-
- // Increase log component verbosity to check for op application messages.
- logger::globalLogDomain()->setMinimumLoggedSeverity(logger::LogComponent::kReplication,
- logger::LogSeverity::Debug(3));
-
- // Test that the 'doTxn' function passes the oplog application mode through correctly to the
- // underlying op application functions.
- NamespaceString nss("test.coll");
- auto uuid = UUID::gen();
-
- // Create a collection for us to insert documents into.
- CollectionOptions collectionOptions;
- collectionOptions.uuid = uuid;
- ASSERT_OK(_storage->createCollection(opCtx.get(), nss, collectionOptions));
-
- BSONObjBuilder resultBuilder;
-
- // Make sure the oplog application mode is passed through via 'doTxn' correctly.
- startCapturingLogMessages();
-
- auto docToInsert0 = BSON("_id" << 0);
- auto cmdObj = makeDoTxnWithInsertOperation(nss, uuid, docToInsert0);
-
- ASSERT_OK(doTxn(opCtx.get(),
- nss.coll().toString(),
- cmdObj,
- OplogApplication::Mode::kInitialSync,
- &resultBuilder));
- ASSERT_EQUALS(1, countLogLinesContaining("oplog application mode: InitialSync"));
-
- auto docToInsert1 = BSON("_id" << 1);
- cmdObj = makeDoTxnWithInsertOperation(nss, uuid, docToInsert1);
-
- ASSERT_OK(doTxn(opCtx.get(),
- nss.coll().toString(),
- cmdObj,
- OplogApplication::Mode::kSecondary,
- &resultBuilder));
- ASSERT_EQUALS(1, countLogLinesContaining("oplog application mode: Secondary"));
-
- stopCapturingLogMessages();
-}
-
} // namespace
} // namespace repl
} // namespace mongo