summaryrefslogtreecommitdiff
path: root/jstests
diff options
context:
space:
mode:
authorMatthew Russotto <matthew.russotto@10gen.com>2018-02-20 11:56:59 -0500
committerMatthew Russotto <matthew.russotto@10gen.com>2018-03-01 08:27:54 -0500
commitae6827297917916173c4d522cabce8fe8fecbf27 (patch)
tree2b8a9b55dd35b70f010a03b90288a1c95d722760 /jstests
parentc713377e4b99475563f26de19f88c45ece8a0546 (diff)
downloadmongo-ae6827297917916173c4d522cabce8fe8fecbf27.tar.gz
SERVER-32320 Push doTxn oplog entry generation into logOp on transaction commit.
Diffstat (limited to 'jstests')
-rw-r--r--jstests/auth/lib/commands_lib.js120
-rw-r--r--jstests/core/bypass_doc_validation.js14
-rw-r--r--jstests/core/collation.js32
-rw-r--r--jstests/core/do_txn_basic.js189
-rw-r--r--jstests/core/do_txn_oneshot.js80
-rw-r--r--jstests/core/do_txn_oplog_entry.js (renamed from jstests/core/do_txn_atomicity.js)34
-rw-r--r--jstests/core/json_schema/misc_validation.js11
-rw-r--r--jstests/core/views/views_all_commands.js13
8 files changed, 375 insertions, 118 deletions
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js
index 588fd06a6fb..d185de225e6 100644
--- a/jstests/auth/lib/commands_lib.js
+++ b/jstests/auth/lib/commands_lib.js
@@ -57,25 +57,28 @@ to be authorized to run the command.
4) skipSharded
-Add "skipSharded: true" if you want to run the test only on a standalone.
+Add "skipSharded: true" if you want to run the test only ony in a non-sharded configuration.
-5) skipStandalone
+5) skipUnlessSharded
-Add "skipStandalone: true" if you want to run the test only in sharded
+Add "skipUnlessSharded: true" if you want to run the test only in sharded
configuration.
-6) setup
+6) skipUnlessReplicaSet
+Add "skipUnlessReplicaSet: true" if you want to run the test only when replica sets are in use.
+
+7) setup
The setup function, if present, is called before testing whether a
particular role authorizes a command for a particular database.
-7) teardown
+8) teardown
The teardown function, if present, is called immediately after
testint whether a particular role authorizes a command for a
particular database.
-8) privileges
+9) privileges
An array of privileges used when testing user-defined roles. The test case tests that a user with
the specified privileges is authorized to run the command, and that having only a subset of the
@@ -178,6 +181,8 @@ var roles_all = {
};
load("jstests/libs/uuid_util.js");
+// For isReplSet
+load("jstests/libs/fixture_helpers.js");
var authCommandsLib = {
@@ -198,7 +203,7 @@ var authCommandsLib = {
{
testname: "addShard",
command: {addShard: "x"},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -221,7 +226,7 @@ var authCommandsLib = {
u: {_id: {ns: "test.x", min: 1}, ns: "test.x"}
}]
},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [{
runOnDb: "config",
roles: roles_clusterManager,
@@ -2264,7 +2269,7 @@ var authCommandsLib = {
{
testname: "balancerStart",
command: {balancerStart: 1},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -2285,7 +2290,7 @@ var authCommandsLib = {
{
testname: "balancerStop",
command: {balancerStop: 1},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -2306,7 +2311,7 @@ var authCommandsLib = {
{
testname: "balancerStatus",
command: {balancerStatus: 1},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -2917,9 +2922,12 @@ var authCommandsLib = {
"ns": firstDbName + ".x",
"o": {"_id": ObjectId("57dc3d7da4fce4358afa85b8"), "data": 5}
}],
- preCondition: [{ns: firstDbName + ".x", q: {x: 5}, res: []}]
+ preCondition: [{ns: firstDbName + ".x", q: {x: 5}, res: []}],
+ txnNumber: NumberLong(0),
+ lsid: {id: UUID()}
},
skipSharded: true,
+ skipUnlessReplicaSet: true,
setup: function(db) {
db.getSisterDB(firstDbName).x.save({});
},
@@ -2943,9 +2951,12 @@ var authCommandsLib = {
"op": "i",
"ns": firstDbName + ".x",
"o": {"_id": ObjectId("57dc3d7da4fce4358afa85b8"), "data": 5}
- }]
+ }],
+ txnNumber: NumberLong(0),
+ lsid: {id: UUID()}
},
skipSharded: true,
+ skipUnlessReplicaSet: true,
setup: function(db) {
db.getSisterDB(firstDbName).x.save({});
},
@@ -2971,10 +2982,13 @@ var authCommandsLib = {
"ns": state.collName,
"ui": state.uuid,
"o": {"_id": ObjectId("57dc3d7da4fce4358afa85b8"), "data": 5}
- }]
+ }],
+ txnNumber: NumberLong(0),
+ lsid: {id: UUID()}
};
},
skipSharded: true,
+ skipUnlessReplicaSet: true,
setup: function(db) {
var sibling = db.getSisterDB(firstDbName);
sibling.runCommand({create: "x"});
@@ -3008,10 +3022,13 @@ var authCommandsLib = {
// Given a nonexistent UUID. The command should fail.
"ui": UUID("71f1d1d7-68ca-493e-a7e9-f03c94e2e960"),
"o": {"_id": ObjectId("57dc3d7da4fce4358afa85b8"), "data": 5}
- }]
+ }],
+ txnNumber: NumberLong(0),
+ lsid: {id: UUID()}
};
},
skipSharded: true,
+ skipUnlessReplicaSet: true,
setup: function(db) {
var sibling = db.getSisterDB(firstDbName);
sibling.runCommand({create: "x"});
@@ -3047,10 +3064,13 @@ var authCommandsLib = {
"ns": state.collName,
"ui": state.uuid,
"o": {"_id": ObjectId("57dc3d7da4fce4358afa85b8"), "data": 5}
- }]
+ }],
+ txnNumber: NumberLong(0),
+ lsid: {id: UUID()}
};
},
skipSharded: true,
+ skipUnlessReplicaSet: true,
setup: function(db) {
var sibling = db.getSisterDB(firstDbName);
sibling.runCommand({create: "x"});
@@ -3084,10 +3104,13 @@ var authCommandsLib = {
firstDbName + ".y", // Specify wrong name but correct uuid. Should work.
"ui": state.x_uuid, // The insert should on x
"o": {"_id": ObjectId("57dc3d7da4fce4358afa85b8"), "data": 5}
- }]
+ }],
+ txnNumber: NumberLong(0),
+ lsid: {id: UUID()}
};
},
skipSharded: true,
+ skipUnlessReplicaSet: true,
setup: function(db) {
db.getSisterDB(firstDbName).x.drop();
db.getSisterDB(firstDbName).y.drop();
@@ -3123,10 +3146,13 @@ var authCommandsLib = {
firstDbName + ".y", // Specify wrong name but correct uuid. Should work.
"ui": state.x_uuid, // The insert should on x
"o": {"_id": ObjectId("57dc3d7da4fce4358afa85b8"), "data": 5}
- }]
+ }],
+ txnNumber: NumberLong(0),
+ lsid: {id: UUID()}
};
},
skipSharded: true,
+ skipUnlessReplicaSet: true,
setup: function(db) {
db.getSisterDB(firstDbName).x.drop();
db.getSisterDB(firstDbName).y.drop();
@@ -3161,9 +3187,12 @@ var authCommandsLib = {
"ns": firstDbName + ".x",
"o2": {"_id": 1},
"o": {"_id": 1, "data": 8}
- }]
+ }],
+ txnNumber: NumberLong(0),
+ lsid: {id: UUID()}
},
skipSharded: true,
+ skipUnlessReplicaSet: true,
setup: function(db) {
db.getSisterDB(firstDbName).x.save({_id: 1, data: 1});
},
@@ -3189,9 +3218,11 @@ var authCommandsLib = {
"o2": {"_id": 1},
"o": {"_id": 1, "data": 8}
}],
- alwaysUpsert: false
+ txnNumber: NumberLong(0),
+ lsid: {id: UUID()}
},
skipSharded: true,
+ skipUnlessReplicaSet: true,
setup: function(db) {
db.getSisterDB(firstDbName).x.save({_id: 1, data: 1});
},
@@ -3219,10 +3250,12 @@ var authCommandsLib = {
"o2": {"_id": 1},
"o": {"_id": 1, "data": 8}
}],
- alwaysUpsert: false
+ txnNumber: NumberLong(0),
+ lsid: {id: UUID()}
};
},
skipSharded: true,
+ skipUnlessReplicaSet: true,
setup: function(db) {
var sibling = db.getSisterDB(firstDbName);
sibling.x.save({_id: 1, data: 1});
@@ -3257,10 +3290,12 @@ var authCommandsLib = {
"o2": {"_id": 1},
"o": {"_id": 1, "data": 8}
}],
- alwaysUpsert: false
+ txnNumber: NumberLong(0),
+ lsid: {id: UUID()}
};
},
skipSharded: true,
+ skipUnlessReplicaSet: true,
setup: function(db) {
var sibling = db.getSisterDB(firstDbName);
sibling.x.save({_id: 1, data: 1});
@@ -3285,8 +3320,13 @@ var authCommandsLib = {
},
{
testname: "doTxn_delete",
- command: {doTxn: [{"op": "d", "ns": firstDbName + ".x", "o": {"_id": 1}}]},
+ command: {
+ doTxn: [{"op": "d", "ns": firstDbName + ".x", "o": {"_id": 1}}],
+ txnNumber: NumberLong(0),
+ lsid: {id: UUID()}
+ },
skipSharded: true,
+ skipUnlessReplicaSet: true,
setup: function(db) {
db.getSisterDB(firstDbName).x.save({_id: 1, data: 1});
},
@@ -3403,7 +3443,7 @@ var authCommandsLib = {
{
testname: "enableSharding",
command: {enableSharding: "x"},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -3742,7 +3782,7 @@ var authCommandsLib = {
{
testname: "flushRouterConfig",
command: {flushRouterConfig: 1},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -4269,7 +4309,7 @@ var authCommandsLib = {
{
testname: "killOp", // sharded version
command: {killOp: 1, op: "shard1:123"},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -4437,7 +4477,7 @@ var authCommandsLib = {
{
testname: "listShards",
command: {listShards: 1},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -4540,7 +4580,7 @@ var authCommandsLib = {
{
testname: "s_mergeChunks",
command: {mergeChunks: "test.x", bounds: [{i: 0}, {i: 5}]},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -4570,7 +4610,7 @@ var authCommandsLib = {
{
testname: "s_moveChunk",
command: {moveChunk: "test.x"},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -4600,7 +4640,7 @@ var authCommandsLib = {
{
testname: "movePrimary",
command: {movePrimary: "x"},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -4615,7 +4655,7 @@ var authCommandsLib = {
{
testname: "netstat",
command: {netstat: "x"},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -4903,7 +4943,7 @@ var authCommandsLib = {
{
testname: "removeShard",
command: {removeShard: "x"},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -5239,7 +5279,7 @@ var authCommandsLib = {
{
testname: "shardCollection",
command: {shardCollection: "test.x"},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -5274,7 +5314,7 @@ var authCommandsLib = {
{
testname: "split",
command: {split: "test.x"},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -5531,7 +5571,7 @@ var authCommandsLib = {
{
testname: "addShardToZone",
command: {addShardToZone: shard0name, zone: 'z'},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -5551,7 +5591,7 @@ var authCommandsLib = {
{
testname: "removeShardFromZone",
command: {removeShardFromZone: shard0name, zone: 'z'},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -5574,7 +5614,7 @@ var authCommandsLib = {
{
testname: "updateZoneKeyRange",
command: {updateZoneKeyRange: 'test.foo', min: {x: 1}, max: {x: 5}, zone: 'z'},
- skipStandalone: true,
+ skipUnlessSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -5665,7 +5705,11 @@ var authCommandsLib = {
return [];
}
// others shouldn't run in a standalone environment
- if (t.skipStandalone && !this.isMongos(conn)) {
+ if (t.skipUnlessSharded && !this.isMongos(conn)) {
+ return [];
+ }
+ // some tests require replica sets to be enabled.
+ if (t.skipUnlessReplicaSet && !FixtureHelpers.isReplSet(conn.getDB("admin"))) {
return [];
}
diff --git a/jstests/core/bypass_doc_validation.js b/jstests/core/bypass_doc_validation.js
index 107c4f2aba4..bed22676dc4 100644
--- a/jstests/core/bypass_doc_validation.js
+++ b/jstests/core/bypass_doc_validation.js
@@ -17,6 +17,8 @@
// For isMMAPv1.
load("jstests/concurrency/fsm_workload_helpers/server_types.js");
+ // For isReplSet
+ load("jstests/libs/fixture_helpers.js");
function assertFailsValidation(res) {
if (res instanceof WriteResult || res instanceof BulkWriteResult) {
@@ -53,12 +55,16 @@
assert.eq(1, coll.count({_id: 9}));
}
- // Test doTxn with a simple insert if not on mongos and not on MMAPv1.
- if (!isMongos && !isMMAPv1(db)) {
+ // Test doTxn with a simple insert if a replica set, not on mongos and not on MMAPv1.
+ if (FixtureHelpers.isReplSet(db) && !isMongos && !isMMAPv1(db)) {
+ const session = db.getMongo().startSession();
+ const sessionDb = session.getDatabase(myDb.getName());
const op = [{op: 'i', ns: coll.getFullName(), o: {_id: 10}}];
- assertFailsValidation(myDb.runCommand({doTxn: op, bypassDocumentValidation: false}));
+ assertFailsValidation(sessionDb.runCommand(
+ {doTxn: op, bypassDocumentValidation: false, txnNumber: NumberLong("0")}));
assert.eq(0, coll.count({_id: 10}));
- assert.commandWorked(myDb.runCommand({doTxn: op, bypassDocumentValidation: true}));
+ assert.commandWorked(sessionDb.runCommand(
+ {doTxn: op, bypassDocumentValidation: true, txnNumber: NumberLong("1")}));
assert.eq(1, coll.count({_id: 10}));
}
diff --git a/jstests/core/collation.js b/jstests/core/collation.js
index 3a089dde4ee..2b3996ccc96 100644
--- a/jstests/core/collation.js
+++ b/jstests/core/collation.js
@@ -11,6 +11,8 @@
load("jstests/libs/get_index_helpers.js");
// For isMMAPv1.
load("jstests/concurrency/fsm_workload_helpers/server_types.js");
+ // For isReplSet
+ load("jstests/libs/fixture_helpers.js");
var coll = db.collation;
coll.drop();
@@ -1973,39 +1975,47 @@
}
// doTxn
- if (!isMongos && !isMMAPv1(db)) {
+ if (FixtureHelpers.isReplSet(db) && !isMongos && !isMMAPv1(db)) {
+ const session = db.getMongo().startSession();
+ const sessionDb = session.getDatabase(db.getName());
coll.drop();
assert.commandWorked(
db.createCollection("collation", {collation: {locale: "en_US", strength: 2}}));
assert.writeOK(coll.insert({_id: "foo", x: 5, str: "bar"}));
// preCondition.q respects collection default collation.
- assert.commandFailed(db.runCommand({
+ assert.commandFailed(sessionDb.runCommand({
doTxn: [{op: "u", ns: coll.getFullName(), o2: {_id: "foo"}, o: {$set: {x: 6}}}],
- preCondition: [{ns: coll.getFullName(), q: {_id: "not foo"}, res: {str: "bar"}}]
+ preCondition: [{ns: coll.getFullName(), q: {_id: "not foo"}, res: {str: "bar"}}],
+ txnNumber: NumberLong("0")
}));
assert.eq(5, coll.findOne({_id: "foo"}).x);
- assert.commandWorked(db.runCommand({
+ assert.commandWorked(sessionDb.runCommand({
doTxn: [{op: "u", ns: coll.getFullName(), o2: {_id: "foo"}, o: {$set: {x: 6}}}],
- preCondition: [{ns: coll.getFullName(), q: {_id: "FOO"}, res: {str: "bar"}}]
+ preCondition: [{ns: coll.getFullName(), q: {_id: "FOO"}, res: {str: "bar"}}],
+ txnNumber: NumberLong("1")
}));
assert.eq(6, coll.findOne({_id: "foo"}).x);
// preCondition.res respects collection default collation.
- assert.commandFailed(db.runCommand({
+ assert.commandFailed(sessionDb.runCommand({
doTxn: [{op: "u", ns: coll.getFullName(), o2: {_id: "foo"}, o: {$set: {x: 7}}}],
- preCondition: [{ns: coll.getFullName(), q: {_id: "foo"}, res: {str: "not bar"}}]
+ preCondition: [{ns: coll.getFullName(), q: {_id: "foo"}, res: {str: "not bar"}}],
+ txnNumber: NumberLong("2")
}));
assert.eq(6, coll.findOne({_id: "foo"}).x);
- assert.commandWorked(db.runCommand({
+ assert.commandWorked(sessionDb.runCommand({
doTxn: [{op: "u", ns: coll.getFullName(), o2: {_id: "foo"}, o: {$set: {x: 7}}}],
- preCondition: [{ns: coll.getFullName(), q: {_id: "foo"}, res: {str: "BAR"}}]
+ preCondition: [{ns: coll.getFullName(), q: {_id: "foo"}, res: {str: "BAR"}}],
+ txnNumber: NumberLong("3")
}));
assert.eq(7, coll.findOne({_id: "foo"}).x);
// <operation>.o2 respects collection default collation.
- assert.commandWorked(db.runCommand(
- {doTxn: [{op: "u", ns: coll.getFullName(), o2: {_id: "FOO"}, o: {$set: {x: 8}}}]}));
+ assert.commandWorked(sessionDb.runCommand({
+ doTxn: [{op: "u", ns: coll.getFullName(), o2: {_id: "FOO"}, o: {$set: {x: 8}}}],
+ txnNumber: NumberLong("4")
+ }));
assert.eq(8, coll.findOne({_id: "foo"}).x);
}
diff --git a/jstests/core/do_txn_basic.js b/jstests/core/do_txn_basic.js
index 8d12ec377d0..199b4948c8e 100644
--- a/jstests/core/do_txn_basic.js
+++ b/jstests/core/do_txn_basic.js
@@ -6,6 +6,8 @@
load("jstests/libs/get_index_helpers.js");
// For isMMAPv1.
load("jstests/concurrency/fsm_workload_helpers/server_types.js");
+ // For isReplSet
+ load("jstests/libs/fixture_helpers.js");
const t = db.do_txn1;
@@ -18,6 +20,14 @@
jsTestLog("Skipping test as the storage engine does not support doTxn.");
return;
}
+ if (!FixtureHelpers.isReplSet(db)) {
+ jsTestLog("Skipping test as doTxn requires a replSet and replication is not enabled.");
+ return;
+ }
+
+ var session = db.getMongo().startSession();
+ db = session.getDatabase("test");
+ var txnNumber = 0;
t.drop();
@@ -26,56 +36,71 @@
//
// Empty array of operations.
- assert.commandFailedWithCode(db.adminCommand({doTxn: []}),
+ assert.commandFailedWithCode(db.adminCommand({doTxn: [], txnNumber: NumberLong(txnNumber++)}),
ErrorCodes.InvalidOptions,
'doTxn should fail on empty array of operations');
// Non-array type for operations.
- assert.commandFailedWithCode(db.adminCommand({doTxn: "not an array"}),
- ErrorCodes.TypeMismatch,
- 'doTxn should fail on non-array type for operations');
+ assert.commandFailedWithCode(
+ db.adminCommand({doTxn: "not an array", txnNumber: NumberLong(txnNumber++)}),
+ ErrorCodes.TypeMismatch,
+ 'doTxn should fail on non-array type for operations');
// Missing 'op' field in an operation.
- assert.commandFailedWithCode(db.adminCommand({doTxn: [{ns: t.getFullName(), o: {_id: 0}}]}),
- ErrorCodes.FailedToParse,
- 'doTxn should fail on operation without "op" field');
-
- // Non-string 'op' field in an operation.
assert.commandFailedWithCode(
- db.adminCommand({doTxn: [{op: 12345, ns: t.getFullName(), o: {_id: 0}}]}),
+ db.adminCommand(
+ {doTxn: [{ns: t.getFullName(), o: {_id: 0}}], txnNumber: NumberLong(txnNumber++)}),
ErrorCodes.FailedToParse,
- 'doTxn should fail on operation with non-string "op" field');
+ 'doTxn should fail on operation without "op" field');
+
+ // Non-string 'op' field in an operation.
+ assert.commandFailedWithCode(db.adminCommand({
+ doTxn: [{op: 12345, ns: t.getFullName(), o: {_id: 0}}],
+ txnNumber: NumberLong(txnNumber++)
+ }),
+ ErrorCodes.FailedToParse,
+ 'doTxn should fail on operation with non-string "op" field');
// Empty 'op' field value in an operation.
- assert.commandFailedWithCode(
- db.adminCommand({doTxn: [{op: '', ns: t.getFullName(), o: {_id: 0}}]}),
- ErrorCodes.FailedToParse,
- 'doTxn should fail on operation with empty "op" field value');
+ assert.commandFailedWithCode(db.adminCommand({
+ doTxn: [{op: '', ns: t.getFullName(), o: {_id: 0}}],
+ txnNumber: NumberLong(txnNumber++)
+ }),
+ ErrorCodes.FailedToParse,
+ 'doTxn should fail on operation with empty "op" field value');
// Missing 'ns' field in an operation.
- assert.commandFailedWithCode(db.adminCommand({doTxn: [{op: 'u', o: {_id: 0}}]}),
- ErrorCodes.FailedToParse,
- 'doTxn should fail on operation without "ns" field');
+ assert.commandFailedWithCode(
+ db.adminCommand({doTxn: [{op: 'u', o: {_id: 0}}], txnNumber: NumberLong(txnNumber++)}),
+ ErrorCodes.FailedToParse,
+ 'doTxn should fail on operation without "ns" field');
// Missing 'o' field in an operation.
- assert.commandFailedWithCode(db.adminCommand({doTxn: [{op: 'u', ns: t.getFullName()}]}),
- ErrorCodes.FailedToParse,
- 'doTxn should fail on operation without "o" field');
+ assert.commandFailedWithCode(
+ db.adminCommand(
+ {doTxn: [{op: 'u', ns: t.getFullName()}], txnNumber: NumberLong(txnNumber++)}),
+ ErrorCodes.FailedToParse,
+ 'doTxn should fail on operation without "o" field');
// Non-string 'ns' field in an operation.
- assert.commandFailedWithCode(db.adminCommand({doTxn: [{op: 'u', ns: 12345, o: {_id: 0}}]}),
- ErrorCodes.FailedToParse,
- 'doTxn should fail on operation with non-string "ns" field');
+ assert.commandFailedWithCode(
+ db.adminCommand(
+ {doTxn: [{op: 'u', ns: 12345, o: {_id: 0}}], txnNumber: NumberLong(txnNumber++)}),
+ ErrorCodes.FailedToParse,
+ 'doTxn should fail on operation with non-string "ns" field');
// Missing dbname in 'ns' field.
assert.commandFailedWithCode(
- db.adminCommand({doTxn: [{op: 'd', ns: t.getName(), o: {_id: 1}}]}),
+ db.adminCommand(
+ {doTxn: [{op: 'd', ns: t.getName(), o: {_id: 1}}], txnNumber: NumberLong(txnNumber++)}),
ErrorCodes.InvalidNamespace,
'doTxn should fail with a missing dbname in the "ns" field value');
// Empty 'ns' field value.
- assert.commandFailed(db.adminCommand({doTxn: [{op: 'u', ns: '', o: {_id: 0}}]}),
- 'doTxn should fail with empty "ns" field value');
+ assert.commandFailed(
+ db.adminCommand(
+ {doTxn: [{op: 'u', ns: '', o: {_id: 0}}], txnNumber: NumberLong(txnNumber++)}),
+ 'doTxn should fail with empty "ns" field value');
// Valid 'ns' field value in unknown operation type 'x'.
assert.commandFailedWithCode(
@@ -84,17 +109,69 @@
'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(), o: {_id: 0}}]}),
- ErrorCodes.InvalidOptions,
- 'doTxn should fail on "no op" operations.');
+ assert.commandFailedWithCode(db.adminCommand({
+ doTxn: [{op: 'n', ns: t.getFullName(), o: {_id: 0}}],
+ txnNumber: NumberLong(txnNumber++)
+ }),
+ 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: []}}],
+ txnNumber: NumberLong(txnNumber++)
+ }),
+ ErrorCodes.InvalidOptions,
+ 'doTxn should fail on commands.');
+
+ // No transaction number in an otherwise valid operation.
assert.commandFailedWithCode(
- db.adminCommand(
- {doTxn: [{op: 'c', ns: t.getCollection('$cmd').getFullName(), o: {applyOps: []}}]}),
+ db.adminCommand({doTxn: [{"op": "i", "ns": t.getFullName(), "o": {_id: 5, x: 17}}]}),
ErrorCodes.InvalidOptions,
- 'doTxn should fail on commands.');
+ 'doTxn should fail when no transaction number is given.');
+
+ // Session IDs and transaction numbers on sub-ops are not allowed
+ var lsid = {id: UUID()};
+ res = assert.commandFailedWithCode(
+ db.runCommand({
+ doTxn: [{
+ op: "i",
+ ns: t.getFullName(),
+ o: {_id: 7, x: 24},
+ lsid: lsid,
+ txnNumber: NumberLong(1),
+ }],
+ txnNumber: NumberLong(txnNumber++)
+ }),
+ ErrorCodes.FailedToParse,
+ 'doTxn should fail when inner transaction contains session id.');
+
+ res = assert.commandFailedWithCode(
+ db.runCommand({
+ doTxn: [{
+ op: "u",
+ ns: t.getFullName(),
+ o2: {_id: 7},
+ o: {$set: {x: 25}},
+ txnNumber: NumberLong(1),
+ }],
+ txnNumber: NumberLong(txnNumber++)
+ }),
+ ErrorCodes.FailedToParse,
+ 'doTxn should fail when inner transaction contains transaction number.');
+
+ res = assert.commandFailedWithCode(
+ db.runCommand({
+ doTxn: [{
+ op: "d",
+ ns: t.getFullName(),
+ o: {_id: 7},
+ stmtId: 0,
+ }],
+ txnNumber: NumberLong(txnNumber++)
+ }),
+ ErrorCodes.FailedToParse,
+ 'doTxn should fail when inner transaction contains statement id.');
// Malformed operation with unexpected field 'x'.
assert.commandFailedWithCode(
@@ -115,7 +192,7 @@
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};
- const cmd = {doTxn: [op]};
+ const cmd = {doTxn: [op], txnNumber: NumberLong(txnNumber++)};
jsTestLog('Testing doTxn on non-existent namespace: ' + tojson(cmd));
if (expectedErrorCode === ErrorCodes.OK) {
assert.commandWorked(db.adminCommand(cmd));
@@ -132,13 +209,17 @@
testCrudOperationOnNonExistentNamespace('u', {x: 0}, {_id: 0}, ErrorCodes.NamespaceNotFound);
assert.commandWorked(db.createCollection(t.getName()));
- var a = assert.commandWorked(
- db.adminCommand({doTxn: [{"op": "i", "ns": t.getFullName(), "o": {_id: 5, x: 17}}]}));
+ var a = assert.commandWorked(db.adminCommand({
+ doTxn: [{"op": "i", "ns": t.getFullName(), "o": {_id: 5, x: 17}}],
+ txnNumber: NumberLong(txnNumber++)
+ }));
assert.eq(1, t.find().count(), "Valid insert failed");
assert.eq(true, a.results[0], "Bad result value for valid insert");
- a = assert.commandWorked(
- db.adminCommand({doTxn: [{"op": "i", "ns": t.getFullName(), "o": {_id: 5, x: 17}}]}));
+ a = assert.commandWorked(db.adminCommand({
+ doTxn: [{"op": "i", "ns": t.getFullName(), "o": {_id: 5, x: 17}}],
+ txnNumber: NumberLong(txnNumber++)
+ }));
assert.eq(1, t.find().count(), "Duplicate insert failed");
assert.eq(true, a.results[0], "Bad result value for duplicate insert");
@@ -146,14 +227,17 @@
assert.eq(o, t.findOne(), "Mismatching document inserted.");
// 'o' field is an empty array.
- assert.commandFailed(db.adminCommand({doTxn: [{op: 'i', ns: t.getFullName(), o: []}]}),
- 'doTxn should fail on insert of object with empty array element');
+ assert.commandFailed(
+ db.adminCommand(
+ {doTxn: [{op: 'i', ns: t.getFullName(), o: []}], txnNumber: NumberLong(txnNumber++)}),
+ 'doTxn should fail on insert of object with empty array element');
var res = assert.commandWorked(db.runCommand({
doTxn: [
{op: "u", ns: t.getFullName(), o2: {_id: 5}, o: {$set: {x: 18}}},
{op: "u", ns: t.getFullName(), o2: {_id: 5}, o: {$set: {x: 19}}}
- ]
+ ],
+ txnNumber: NumberLong(txnNumber++)
}));
o.x++;
@@ -170,7 +254,8 @@
{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}}]
+ preCondition: [{ns: t.getFullName(), q: {_id: 5}, res: {x: 19}}],
+ txnNumber: NumberLong(txnNumber++)
}));
o.x++;
@@ -187,7 +272,8 @@
{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}}]
+ preCondition: [{ns: "foo.otherName", q: {_id: 5}, res: {x: 21}}],
+ txnNumber: NumberLong(txnNumber++)
}));
assert.eq(o, t.findOne(), "preCondition didn't match, but ops were still applied");
@@ -198,7 +284,8 @@
{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}}]
+ preCondition: [{ns: t.getFullName(), q: {_id: 5}, res: {x: 19}}],
+ txnNumber: NumberLong(txnNumber++)
}));
assert.eq(o, t.findOne(), "preCondition didn't match, but ops were still applied");
@@ -207,7 +294,8 @@
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}}}
- ]
+ ],
+ txnNumber: NumberLong(txnNumber++)
}));
assert.eq(false, res.results[0], "Op required upsert, which should be disallowed.");
@@ -219,7 +307,8 @@
doTxn: [
{"op": "i", "ns": t.getFullName(), "o": {_id: 6}},
{"op": "u", "ns": t.getFullName(), "o2": {_id: 6}, "o": {$set: {z: 1, a: 2}}}
- ]
+ ],
+ txnNumber: NumberLong(txnNumber++)
}));
assert.eq(t.findOne({_id: 6}), {_id: 6, a: 2, z: 1}); // Note: 'a' and 'z' have been sorted.
@@ -233,7 +322,8 @@
"o2": {_id: 7},
"o": {$v: NumberLong(0), $set: {z: 1, a: 2}}
}
- ]
+ ],
+ txnNumber: NumberLong(txnNumber++),
}));
assert.eq(res.code, 40682);
@@ -248,7 +338,8 @@
"o2": {_id: 8},
"o": {$v: NumberLong(1), $set: {z: 1, a: 2}}
}
- ]
+ ],
+ txnNumber: NumberLong(txnNumber++),
}));
assert.eq(t.findOne({_id: 8}), {_id: 8, a: 2, z: 1}); // Note: 'a' and 'z' have been sorted.
})();
diff --git a/jstests/core/do_txn_oneshot.js b/jstests/core/do_txn_oneshot.js
new file mode 100644
index 00000000000..89bcb65b815
--- /dev/null
+++ b/jstests/core/do_txn_oneshot.js
@@ -0,0 +1,80 @@
+// @tags: [requires_non_retryable_commands]
+
+// Tests that doTxn produces correct oplog entries.
+(function() {
+ 'use strict';
+ // For isMMAPv1.
+ load("jstests/concurrency/fsm_workload_helpers/server_types.js");
+ // For isReplSet
+ load("jstests/libs/fixture_helpers.js");
+ load('jstests/libs/uuid_util.js');
+
+ if (isMMAPv1(db)) {
+ jsTestLog("Skipping test as the storage engine does not support doTxn.");
+ return;
+ }
+ if (!FixtureHelpers.isReplSet(db)) {
+ jsTestLog("Skipping test as doTxn requires a replSet and replication is not enabled.");
+ return;
+ }
+
+ var oplog = db.getSiblingDB('local').oplog.rs;
+ var session = db.getMongo().startSession();
+ var sessionDb = session.getDatabase("test");
+ var t = db.doTxn;
+ t.drop();
+ db.createCollection(t.getName());
+ const tUuid = getUUIDFromListCollections(db, t.getName());
+
+ //
+ // Test insert ops. Insert ops are unmodified except the UUID field is added.
+ //
+ const insertOps = [
+ {op: 'i', ns: t.getFullName(), o: {_id: 100, x: 1, y: 1}},
+ {op: 'i', ns: t.getFullName(), o: {_id: 101, x: 2, y: 1}},
+ ];
+ assert.commandWorked(sessionDb.runCommand({doTxn: insertOps, txnNumber: NumberLong("1")}));
+ let topOfOplog = oplog.find().sort({$natural: -1}).limit(1).next();
+ assert.eq(topOfOplog.txnNumber, NumberLong("1"));
+ assert.docEq(topOfOplog.o.applyOps, insertOps.map(x => Object.assign(x, {ui: tUuid})));
+
+ //
+ // Test update ops. For updates, the "$v" UpdateSemantics field is added and non-idempotent
+ // operations are made idempotent.
+ //
+ const updateOps = [
+ {op: 'u', ns: t.getFullName(), o: {$inc: {x: 10}}, o2: {_id: 100}},
+ {op: 'u', ns: t.getFullName(), o: {$inc: {x: 10}}, o2: {_id: 101}}
+ ];
+ const expectedUpdateOps = [
+ {op: 'u', ns: t.getFullName(), o: {$v: 1, $set: {x: 11}}, o2: {_id: 100}, ui: tUuid},
+ {op: 'u', ns: t.getFullName(), o: {$v: 1, $set: {x: 12}}, o2: {_id: 101}, ui: tUuid}
+ ];
+ assert.commandWorked(sessionDb.runCommand({doTxn: updateOps, txnNumber: NumberLong("2")}));
+ topOfOplog = oplog.find().sort({$natural: -1}).limit(1).next();
+ assert.eq(topOfOplog.txnNumber, NumberLong("2"));
+ assert.docEq(topOfOplog.o.applyOps, expectedUpdateOps);
+
+ //
+ // Test delete ops. Delete ops are unmodified except the UUID field is added.
+ //
+ const deleteOps = [
+ {op: 'd', ns: t.getFullName(), o: {_id: 100}},
+ {op: 'd', ns: t.getFullName(), o: {_id: 101}}
+ ];
+ assert.commandWorked(sessionDb.runCommand({doTxn: deleteOps, txnNumber: NumberLong("3")}));
+ topOfOplog = oplog.find().sort({$natural: -1}).limit(1).next();
+ assert.eq(topOfOplog.txnNumber, NumberLong("3"));
+ assert.docEq(topOfOplog.o.applyOps, deleteOps.map(x => Object.assign(x, {ui: tUuid})));
+
+ //
+ // Make sure the transaction table is not affected by one-shot transactions.
+ //
+ assert.eq(0,
+ db.getSiblingDB('config')
+ .transactions.find({"_id.id": session.getSessionId().id})
+ .toArray(),
+ "No transactions should be written to the transaction table.");
+
+ session.endSession();
+})();
diff --git a/jstests/core/do_txn_atomicity.js b/jstests/core/do_txn_oplog_entry.js
index a1c54f5dd7e..23be1c4d819 100644
--- a/jstests/core/do_txn_atomicity.js
+++ b/jstests/core/do_txn_oplog_entry.js
@@ -6,47 +6,60 @@
// For isMMAPv1.
load("jstests/concurrency/fsm_workload_helpers/server_types.js");
+ // For isReplSet
+ load("jstests/libs/fixture_helpers.js");
if (isMMAPv1(db)) {
jsTestLog("Skipping test as the storage engine does not support doTxn.");
return;
}
+ if (!FixtureHelpers.isReplSet(db)) {
+ jsTestLog("Skipping test as doTxn requires a replSet and replication is not enabled.");
+ return;
+ }
+
+ var session = db.getMongo().startSession();
+ var sessionDb = session.getDatabase("test");
+ var txnNumber = 0;
var t = db.doTxn;
t.drop();
assert.writeOK(t.insert({_id: 1}));
// Operations including commands are not allowed and should be rejected completely.
- assert.commandFailedWithCode(db.adminCommand({
+ assert.commandFailedWithCode(sessionDb.adminCommand({
doTxn: [
{op: 'i', ns: t.getFullName(), o: {_id: ObjectId(), x: 1}},
{op: 'c', ns: "invalid", o: {create: "t"}},
- ]
+ ],
+ txnNumber: NumberLong(txnNumber++)
}),
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.commandFailedWithCode(db.adminCommand({
+ assert.commandFailedWithCode(sessionDb.adminCommand({
doTxn: [
{op: 'i', ns: t.getFullName(), o: {_id: ObjectId(), x: 1}},
{op: 'i', ns: t.getFullName(), o: {_id: tooLong, x: 1}},
- ]
+ ],
+ txnNumber: NumberLong(txnNumber++)
}),
ErrorCodes.KeyTooLong);
assert.eq(t.count({x: 1}), 0);
// Operations on non-existent databases cannot be atomic.
var newDBName = "do_txn_atomicity";
- var newDB = db.getSiblingDB(newDBName);
+ var newDB = sessionDb.getSiblingDB(newDBName);
assert.commandWorked(newDB.dropDatabase());
// Updates on a non-existent database no longer implicitly create collections and will fail with
// a NamespaceNotFound error.
- assert.commandFailedWithCode(
- newDB.runCommand(
- {doTxn: [{op: "u", ns: newDBName + ".foo", o: {_id: 5, x: 17}, o2: {_id: 5, x: 16}}]}),
- ErrorCodes.NamespaceNotFound);
+ assert.commandFailedWithCode(newDB.runCommand({
+ doTxn: [{op: "u", ns: newDBName + ".foo", o: {_id: 5, x: 17}, o2: {_id: 5, x: 16}}],
+ txnNumber: NumberLong(txnNumber++)
+ }),
+ ErrorCodes.NamespaceNotFound);
var sawTooManyLocksError = false;
@@ -66,7 +79,8 @@
multiOps.push({op: 'i', ns: newDBName + "." + multiName, o: {_id: 0, x: [0, 1]}});
}
- let res = [cappedOps, multiOps].map((doTxn) => newDB.runCommand({doTxn}));
+ let res = [cappedOps, multiOps].map(
+ (doTxn) => newDB.runCommand({doTxn: doTxn, txnNumber: NumberLong(txnNumber++)}));
sawTooManyLocksError |= res.some((res) => res.code === ErrorCodes.TooManyLocks);
// Transactions involving just two collections should succeed.
if (n <= 2)
diff --git a/jstests/core/json_schema/misc_validation.js b/jstests/core/json_schema/misc_validation.js
index 23ce4ef72c1..29504be9d69 100644
--- a/jstests/core/json_schema/misc_validation.js
+++ b/jstests/core/json_schema/misc_validation.js
@@ -25,6 +25,8 @@
// For isMMAPv1.
load("jstests/concurrency/fsm_workload_helpers/server_types.js");
+ // For isReplSet
+ load("jstests/libs/fixture_helpers.js");
const testName = "json_schema_misc_validation";
const testDB = db.getSiblingDB(testName);
@@ -319,9 +321,11 @@
coll.drop();
assert.writeOK(coll.insert({_id: 1, a: true}));
- if (!isMMAPv1(db)) {
+ if (FixtureHelpers.isReplSet(db) && !isMongos && !isMMAPv1(db)) {
// Test $jsonSchema in the precondition checking for doTxn.
- res = testDB.adminCommand({
+ const session = db.getMongo().startSession();
+ const sessionDb = session.getDatabase(testDB.getName());
+ res = sessionDb.adminCommand({
doTxn: [
{op: "u", ns: coll.getFullName(), o2: {_id: 1}, o: {$set: {a: false}}},
],
@@ -329,7 +333,8 @@
ns: coll.getFullName(),
q: {$jsonSchema: {properties: {a: {type: "boolean"}}}},
res: {a: true}
- }]
+ }],
+ txnNumber: NumberLong("0")
});
assert.commandWorked(res);
assert.eq(1, res.applied);
diff --git a/jstests/core/views/views_all_commands.js b/jstests/core/views/views_all_commands.js
index 696e72fff80..e5f11d2c1b5 100644
--- a/jstests/core/views/views_all_commands.js
+++ b/jstests/core/views/views_all_commands.js
@@ -190,10 +190,17 @@
delete: {command: {delete: "view", deletes: [{q: {x: 1}, limit: 1}]}, expectFailure: true},
distinct: {command: {distinct: "view", key: "_id"}},
doTxn: {
- command: {doTxn: [{op: "i", o: {_id: 1}, ns: "test.view"}]},
+ command: {
+ doTxn: [{op: "i", o: {_id: 1}, ns: "test.view"}],
+ txnNumber: NumberLong("0"),
+ lsid: {id: UUID()}
+ },
expectFailure: true,
- expectedErrorCode:
- [ErrorCodes.CommandNotSupportedOnView, ErrorCodes.CommandNotSupported],
+ expectedErrorCode: [
+ ErrorCodes.CommandNotSupportedOnView,
+ ErrorCodes.CommandNotSupported,
+ ErrorCodes.IllegalOperation
+ ],
skipSharded: true,
},
driverOIDTest: {skip: isUnrelated},