diff options
42 files changed, 3 insertions, 1957 deletions
diff --git a/buildscripts/resmokeconfig/suites/causally_consistent_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/causally_consistent_jscore_passthrough.yml index 370617c2300..746dc36c311 100644 --- a/buildscripts/resmokeconfig/suites/causally_consistent_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/causally_consistent_jscore_passthrough.yml @@ -30,7 +30,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # doTxn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/causally_consistent_jscore_passthrough_auth.yml b/buildscripts/resmokeconfig/suites/causally_consistent_jscore_passthrough_auth.yml index ed50202405b..620ad2fe5be 100644 --- a/buildscripts/resmokeconfig/suites/causally_consistent_jscore_passthrough_auth.yml +++ b/buildscripts/resmokeconfig/suites/causally_consistent_jscore_passthrough_auth.yml @@ -43,7 +43,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # doTxn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_100ms_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_100ms_refresh_jscore_passthrough.yml index 3a76ef4b0b0..72da949cc48 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_100ms_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_100ms_refresh_jscore_passthrough.yml @@ -23,7 +23,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # doTxn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_10sec_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_10sec_refresh_jscore_passthrough.yml index 36ca87d9587..a806ec5ac57 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_10sec_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_10sec_refresh_jscore_passthrough.yml @@ -23,7 +23,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # doTxn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_1sec_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_1sec_refresh_jscore_passthrough.yml index e383e9771d3..efb7ae3d0a1 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_1sec_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_1sec_refresh_jscore_passthrough.yml @@ -23,7 +23,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # doTxn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_default_refresh_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_default_refresh_jscore_passthrough.yml index b21d11671b3..95177f30486 100644 --- a/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_default_refresh_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/logical_session_cache_sharding_default_refresh_jscore_passthrough.yml @@ -23,7 +23,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # doTxn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml index 8b6001fb74e..e056e160377 100644 --- a/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml @@ -24,7 +24,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # doTxn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml index 693a29f76ba..0e7b56025cb 100644 --- a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml @@ -24,7 +24,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # doTxn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml index d4e3cf62f58..3cfa0050da3 100644 --- a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml @@ -24,7 +24,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # doTxn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml index 68bca7241a3..e8d10941693 100644 --- a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml @@ -24,7 +24,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # doTxn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml b/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml index f950cc7360c..aa97b5220b7 100644 --- a/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml +++ b/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml @@ -24,7 +24,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # doTxn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/secondary_reads_passthrough.yml b/buildscripts/resmokeconfig/suites/secondary_reads_passthrough.yml index d7899ef4d7f..10587dffdc6 100644 --- a/buildscripts/resmokeconfig/suites/secondary_reads_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/secondary_reads_passthrough.yml @@ -54,10 +54,6 @@ selector: - jstests/core/ord.js - jstests/core/tailable_cursor_invalidation.js - jstests/core/tailable_skip_limit.js - # doTxn uses a different session so the operationTime of the default session - # will not be advanced by doTxn. Therefore, operations in the default - # session cannot be guaranteed to get executed after doTxn. - - jstests/core/bypass_doc_validation.js - jstests/core/collation.js # This test runs very slow operations which take a long time to replicate in a 5 node linear # chain. This can lead to awaitReplication timeouts when doing consistency checks. diff --git a/buildscripts/resmokeconfig/suites/sharded_causally_consistent_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/sharded_causally_consistent_jscore_passthrough.yml index 2446c3b4422..369301b0e9e 100644 --- a/buildscripts/resmokeconfig/suites/sharded_causally_consistent_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/sharded_causally_consistent_jscore_passthrough.yml @@ -26,7 +26,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # doTxn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/sharded_causally_consistent_jscore_txns_passthrough.yml b/buildscripts/resmokeconfig/suites/sharded_causally_consistent_jscore_txns_passthrough.yml index d1eb538e77b..0dbfd4f8b9f 100644 --- a/buildscripts/resmokeconfig/suites/sharded_causally_consistent_jscore_txns_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/sharded_causally_consistent_jscore_txns_passthrough.yml @@ -23,10 +23,6 @@ selector: # cluster. - jstests/core/txns/transactions_block_ddl.js - # TODO SERVER-36120: No doTxn command on mongos. - - jstests/core/txns/do_txn_atomicity.js - - jstests/core/txns/do_txn_basic.js - # transactionLifetimeLimitSeconds parameter is not available in mongos. - jstests/core/txns/abort_expired_transaction.js - jstests/core/txns/abort_transaction_thread_does_not_block_on_locks.js diff --git a/buildscripts/resmokeconfig/suites/sharded_collections_causally_consistent_jscore_txns_passthrough.yml b/buildscripts/resmokeconfig/suites/sharded_collections_causally_consistent_jscore_txns_passthrough.yml index 34d30da7c30..9e6a3d0fe42 100644 --- a/buildscripts/resmokeconfig/suites/sharded_collections_causally_consistent_jscore_txns_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/sharded_collections_causally_consistent_jscore_txns_passthrough.yml @@ -19,10 +19,6 @@ selector: # cluster. - jstests/core/txns/transactions_block_ddl.js - # TODO SERVER-36120: No doTxn command on mongos. - - jstests/core/txns/do_txn_atomicity.js - - jstests/core/txns/do_txn_basic.js - # TODO SERVER-36121: Set the transactionLifetimeLimitSeconds parameter, which is not on mongos. - jstests/core/txns/abort_expired_transaction.js - jstests/core/txns/abort_transaction_thread_does_not_block_on_locks.js diff --git a/buildscripts/resmokeconfig/suites/sharded_collections_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/sharded_collections_jscore_passthrough.yml index e4eb9547279..9a3028493b0 100644 --- a/buildscripts/resmokeconfig/suites/sharded_collections_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/sharded_collections_jscore_passthrough.yml @@ -24,7 +24,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # doTxn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/sharded_jscore_txns.yml b/buildscripts/resmokeconfig/suites/sharded_jscore_txns.yml index d1df18601af..5d4482a396b 100644 --- a/buildscripts/resmokeconfig/suites/sharded_jscore_txns.yml +++ b/buildscripts/resmokeconfig/suites/sharded_jscore_txns.yml @@ -23,10 +23,6 @@ selector: # cluster. - jstests/core/txns/transactions_block_ddl.js - # TODO SERVER-36120: No doTxn command on mongos. - - jstests/core/txns/do_txn_atomicity.js - - jstests/core/txns/do_txn_basic.js - # transactionLifetimeLimitSeconds parameter is not available in mongos. - jstests/core/txns/abort_expired_transaction.js - jstests/core/txns/abort_transaction_thread_does_not_block_on_locks.js diff --git a/buildscripts/resmokeconfig/suites/sharded_jscore_txns_sharded_collections.yml b/buildscripts/resmokeconfig/suites/sharded_jscore_txns_sharded_collections.yml index f1cac78a54a..88f46be33fd 100644 --- a/buildscripts/resmokeconfig/suites/sharded_jscore_txns_sharded_collections.yml +++ b/buildscripts/resmokeconfig/suites/sharded_jscore_txns_sharded_collections.yml @@ -19,10 +19,6 @@ selector: # cluster. - jstests/core/txns/transactions_block_ddl.js - # TODO SERVER-36120: No doTxn command on mongos. - - jstests/core/txns/do_txn_atomicity.js - - jstests/core/txns/do_txn_basic.js - # transactionLifetimeLimitSeconds parameter is not available in mongos. - jstests/core/txns/abort_expired_transaction.js - jstests/core/txns/abort_transaction_thread_does_not_block_on_locks.js diff --git a/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml index 40869e6bd0d..228dcdec60d 100644 --- a/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml @@ -21,7 +21,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # do_txn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/sharding_jscore_op_query_passthrough.yml b/buildscripts/resmokeconfig/suites/sharding_jscore_op_query_passthrough.yml index 81960174824..40b2193ec12 100644 --- a/buildscripts/resmokeconfig/suites/sharding_jscore_op_query_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/sharding_jscore_op_query_passthrough.yml @@ -24,7 +24,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # doTxn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/buildscripts/resmokeconfig/suites/sharding_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/sharding_jscore_passthrough.yml index f22560496b3..54393249267 100644 --- a/buildscripts/resmokeconfig/suites/sharding_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/sharding_jscore_passthrough.yml @@ -24,7 +24,6 @@ selector: - jstests/core/dbhash.js # dbhash. - jstests/core/dbhash2.js # dbhash. - jstests/core/diagdata.js # Command not supported in mongos - - jstests/core/do_txn*.js # do_txn - jstests/core/dropdb_race.js # syncdelay. - jstests/core/fsync.js # uses fsync. - jstests/core/geo_haystack*.js # geoSearch. diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js index 71c2fb17b61..dbde9b9f138 100644 --- a/jstests/auth/lib/commands_lib.js +++ b/jstests/auth/lib/commands_lib.js @@ -3248,435 +3248,6 @@ var authCommandsLib = { ] }, { - testname: "doTxn_precondition", - command: { - doTxn: [{ - "op": "i", - "ns": firstDbName + ".x", - "o": {"_id": ObjectId("57dc3d7da4fce4358afa85b8"), "data": 5} - }], - 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({}); - }, - teardown: function(db) { - db.getSisterDB(firstDbName).x.drop(); - }, - testcases: [ - { - runOnDb: adminDbName, - privileges: [ - {resource: {db: firstDbName, collection: "x"}, actions: ["find"]}, - {resource: {db: firstDbName, collection: "x"}, actions: ["insert"]}, - ], - }, - ] - }, - { - testname: "doTxn_insert", - command: { - doTxn: [{ - "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({}); - }, - teardown: function(db) { - db.getSisterDB(firstDbName).x.drop(); - }, - testcases: [ - { - runOnDb: adminDbName, - roles: roles_write, - privileges: [ - {resource: {db: firstDbName, collection: "x"}, actions: ["insert"]}, - ], - }, - ] - }, - { - testname: "doTxn_insert_UUID", - command: function(state) { - return { - doTxn: [{ - "op": "i", - "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"}); - - return { - collName: sibling.x.getFullName(), - uuid: getUUIDFromListCollections(sibling, sibling.x.getName()) - }; - }, - teardown: function(db) { - db.getSisterDB(firstDbName).x.drop(); - }, - testcases: [ - { - runOnDb: adminDbName, - roles: {root: 1, restore: 1, __system: 1}, - privileges: [ - {resource: {db: firstDbName, collection: "x"}, actions: ["insert"]}, - {resource: {cluster: true}, actions: ["useUUID"]} - ], - }, - ] - }, - { - testname: "doTxn_insert_with_nonexistent_UUID", - command: function(state) { - return { - doTxn: [{ - "op": "i", - "ns": state.collName, - // 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"}); - - return { - collName: sibling.x.getFullName(), - uuid: getUUIDFromListCollections(sibling, sibling.x.getName()) - }; - }, - teardown: function(db) { - db.getSisterDB(firstDbName).x.drop(); - }, - testcases: [ - { - // It would be an sanity check failure rather than a auth check - // failure. - expectFail: true, - runOnDb: adminDbName, - roles: {root: 1, restore: 1, __system: 1}, - privileges: [ - {resource: {db: firstDbName, collection: "x"}, actions: ["insert"]}, - {resource: {cluster: true}, actions: ["useUUID"]} - ], - }, - ] - }, - { - testname: "doTxn_insert_UUID_failure", - command: function(state) { - return { - doTxn: [{ - "op": "i", - "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"}); - - return { - collName: sibling.x.getFullName(), - uuid: getUUIDFromListCollections(sibling, sibling.x.getName()) - }; - }, - teardown: function(db) { - db.getSisterDB(firstDbName).x.drop(); - }, - testcases: [ - { - expectAuthzFailure: true, - runOnDb: adminDbName, - privileges: [ - {resource: {db: firstDbName, collection: "x"}, actions: ["insert"]}, - // Don't have useUUID privilege. - ], - }, - ] - }, - { - testname: "doTxn_insert_UUID_with_wrong_ns", - command: function(state) { - return { - doTxn: [{ - "op": "i", - "ns": - 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(); - var sibling = db.getSisterDB(firstDbName); - sibling.runCommand({create: "x"}); - sibling.runCommand({create: "y"}); - return {x_uuid: getUUIDFromListCollections(sibling, sibling.x.getName())}; - }, - teardown: function(db) { - db.getSisterDB(firstDbName).x.drop(); - }, - testcases: [ - { - runOnDb: adminDbName, - privileges: [ - { - resource: {db: firstDbName, collection: "x"}, - actions: ["createCollection", "insert"] - }, - {resource: {db: firstDbName, collection: "y"}, actions: ["createCollection"]}, - {resource: {cluster: true}, actions: ["useUUID", "forceUUID"]} - ], - }, - ] - }, - { - testname: "doTxn_insert_UUID_with_wrong_ns_failure", - command: function(state) { - return { - doTxn: [{ - "op": "i", - "ns": - 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(); - var sibling = db.getSisterDB(firstDbName); - sibling.runCommand({create: "x"}); - sibling.runCommand({create: "y"}); - return {x_uuid: getUUIDFromListCollections(sibling, sibling.x.getName())}; - }, - teardown: function(db) { - db.getSisterDB(firstDbName).x.drop(); - }, - testcases: [ - { - expectAuthzFailure: true, - runOnDb: adminDbName, - privileges: [ - {resource: {db: firstDbName, collection: "x"}, actions: ["createCollection"]}, - { - resource: {db: firstDbName, collection: "y"}, - actions: ["createCollection", "insert"] - }, - {resource: {cluster: true}, actions: ["useUUID", "forceUUID"]} - ], - }, - ] - }, - { - testname: "doTxn_upsert", - command: { - doTxn: [{ - "op": "u", - "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}); - }, - teardown: function(db) { - db.getSisterDB(firstDbName).x.drop(); - }, - testcases: [ - { - runOnDb: adminDbName, - roles: Object.merge(roles_write, {restore: 0}, true), - privileges: [ - {resource: {db: firstDbName, collection: "x"}, actions: ["update", "insert"]}, - ], - }, - ] - }, - { - testname: "doTxn_update", - command: { - doTxn: [{ - "op": "u", - "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}); - }, - teardown: function(db) { - db.getSisterDB(firstDbName).x.drop(); - }, - testcases: [ - { - runOnDb: adminDbName, - roles: Object.merge(roles_write, {restore: 0}, true), - privileges: [ - {resource: {db: firstDbName, collection: "x"}, actions: ["update"]}, - ], - }, - ] - }, - { - testname: "doTxn_update_UUID", - command: function(state) { - return { - doTxn: [{ - "op": "u", - "ns": state.collName, - "ui": state.uuid, - "o2": {"_id": 1}, - "o": {"_id": 1, "data": 8} - }], - 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}); - - return { - collName: sibling.x.getFullName(), - uuid: getUUIDFromListCollections(sibling, sibling.x.getName()) - }; - }, - teardown: function(db) { - db.getSisterDB(firstDbName).x.drop(); - }, - testcases: [ - { - runOnDb: adminDbName, - roles: {root: 1, __system: 1}, - privileges: [ - {resource: {db: firstDbName, collection: "x"}, actions: ["update"]}, - {resource: {cluster: true}, actions: ["useUUID"]} - ], - }, - ] - }, - { - testname: "doTxn_update_UUID_failure", - command: function(state) { - return { - doTxn: [{ - "op": "u", - "ns": state.collName, - "ui": state.uuid, - "o2": {"_id": 1}, - "o": {"_id": 1, "data": 8} - }], - 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}); - - return { - collName: sibling.x.getFullName(), - uuid: getUUIDFromListCollections(sibling, sibling.x.getName()) - }; - }, - teardown: function(db) { - db.getSisterDB(firstDbName).x.drop(); - }, - testcases: [ - { - expectAuthzFailure: true, - runOnDb: adminDbName, - privileges: [ - {resource: {db: firstDbName, collection: "x"}, actions: ["update"]}, - ], - }, - ] - }, - { - testname: "doTxn_delete", - 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}); - }, - teardown: function(db) { - db.getSisterDB(firstDbName).x.drop(); - }, - testcases: [ - { - runOnDb: adminDbName, - roles: Object.merge(roles_write, {restore: 0}, true), - privileges: [ - {resource: {db: firstDbName, collection: "x"}, actions: ["remove"]}, - ], - }, - ] - }, - { testname: "drop", command: {drop: "x"}, setup: function(db) { diff --git a/jstests/core/bypass_doc_validation.js b/jstests/core/bypass_doc_validation.js index 854b96031ba..dcf1a0d28dc 100644 --- a/jstests/core/bypass_doc_validation.js +++ b/jstests/core/bypass_doc_validation.js @@ -11,7 +11,6 @@ * * - aggregation with $out * - applyOps (when not sharded) - * - doTxn (when not sharded) * - findAndModify * - insert * - mapReduce @@ -62,20 +61,6 @@ function runBypassDocumentValidationTest(validator) { assert.eq(1, coll.count({_id: 9})); } - // Test doTxn with a simple insert if a replica set, not on mongos and the storage engine - // is WiredTiger. - if (FixtureHelpers.isReplSet(db) && !isMongos && isWiredTiger(db)) { - const session = db.getMongo().startSession(); - const sessionDb = session.getDatabase(myDb.getName()); - const op = [{op: 'i', ns: coll.getFullName(), o: {_id: 10}}]; - assertFailsValidation(sessionDb.runCommand( - {doTxn: op, bypassDocumentValidation: false, txnNumber: NumberLong("0")})); - assert.eq(0, coll.count({_id: 10})); - assert.commandWorked(sessionDb.runCommand( - {doTxn: op, bypassDocumentValidation: true, txnNumber: NumberLong("1")})); - assert.eq(1, coll.count({_id: 10})); - } - // Test the aggregation command with a $out stage. const outputCollName = 'bypass_output_coll'; const outputColl = myDb[outputCollName]; diff --git a/jstests/core/collation.js b/jstests/core/collation.js index 99623d18b7e..0118ffb3a5a 100644 --- a/jstests/core/collation.js +++ b/jstests/core/collation.js @@ -1821,55 +1821,6 @@ if (!isMongos) { assert.eq(8, coll.findOne({_id: "foo"}).x); } -// doTxn -if (FixtureHelpers.isReplSet(db) && !isMongos && isWiredTiger(db)) { - const session = db.getMongo().startSession(); - const sessionDb = session.getDatabase(db.getName()); - - // Use majority write concern to clear the drop-pending that can cause lock conflicts with - // transactions. - coll.drop({writeConcern: {w: "majority"}}); - - 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(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"}}], - txnNumber: NumberLong("0") - })); - assert.eq(5, coll.findOne({_id: "foo"}).x); - 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"}}], - txnNumber: NumberLong("1") - })); - assert.eq(6, coll.findOne({_id: "foo"}).x); - - // preCondition.res respects collection default collation. - 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"}}], - txnNumber: NumberLong("2") - })); - assert.eq(6, coll.findOne({_id: "foo"}).x); - 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"}}], - txnNumber: NumberLong("3") - })); - assert.eq(7, coll.findOne({_id: "foo"}).x); - - // <operation>.o2 respects collection default collation. - 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); -} - // Test that the collection created with the "cloneCollectionAsCapped" command inherits the // default collation of the corresponding collection. We skip running this command in a sharded // cluster because it isn't supported by mongos. diff --git a/jstests/core/json_schema/misc_validation.js b/jstests/core/json_schema/misc_validation.js index 7ac2fb60fb5..fbd15e7b31a 100644 --- a/jstests/core/json_schema/misc_validation.js +++ b/jstests/core/json_schema/misc_validation.js @@ -10,7 +10,6 @@ * - update * - findAndModify * - applyOps - * - doTxn * - $elemMatch projection * * @tags: [ @@ -333,24 +332,5 @@ if (!isMongos) { // transactions. coll.drop({writeConcern: {w: "majority"}}); assert.writeOK(coll.insert({_id: 1, a: true})); - - if (FixtureHelpers.isReplSet(db) && !isMongos && isWiredTiger(db)) { - // Test $jsonSchema in the precondition checking for doTxn. - 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}}}, - ], - preCondition: [{ - 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/txns/commands_not_allowed_in_txn.js b/jstests/core/txns/commands_not_allowed_in_txn.js index 41260615d91..f0f96bed437 100644 --- a/jstests/core/txns/commands_not_allowed_in_txn.js +++ b/jstests/core/txns/commands_not_allowed_in_txn.js @@ -131,38 +131,6 @@ if (!isMongos) { commands.forEach(testCommand); // -// Test that doTxn is not allowed at positions after the first in transactions. -// - -// There is no doTxn command on mongos. -if (!isMongos) { - assert.commandWorked(sessionDb.runCommand({ - find: collName, - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(++txnNumber), - stmtId: NumberInt(0), - startTransaction: true, - autocommit: false - })); - assert.commandFailedWithCode(sessionDb.runCommand({ - doTxn: [{op: "u", ns: testColl.getFullName(), o2: {_id: 0}, o: {$set: {a: 5}}}], - txnNumber: NumberLong(txnNumber), - stmtId: NumberInt(1), - autocommit: false - }), - ErrorCodes.OperationNotSupportedInTransaction); - - // It is still possible to commit the transaction. The rejected command does not abort the - // transaction. - assert.commandWorked(sessionDb.adminCommand({ - commitTransaction: 1, - txnNumber: NumberLong(txnNumber), - stmtId: NumberInt(2), - autocommit: false - })); -} - -// // Test that a find command with the read-once cursor option is not allowed in a transaction. // assert.commandFailedWithCode(sessionDb.runCommand({ diff --git a/jstests/core/txns/disallow_operations_on_prepared_transaction.js b/jstests/core/txns/disallow_operations_on_prepared_transaction.js index 13d423ab4c1..0e6a8453aa9 100644 --- a/jstests/core/txns/disallow_operations_on_prepared_transaction.js +++ b/jstests/core/txns/disallow_operations_on_prepared_transaction.js @@ -63,17 +63,6 @@ assert.commandFailedWithCode(assert.throws(function() { }), ErrorCodes.PreparedTransactionInProgress); -// This fails with ConflictingOperationInProgress instead of PreparedTransactionInProgress -// because doTxn is always runs with startTransaction = true. -jsTestLog("Test that you can't run doTxn on a prepared transaction."); -assert.commandFailedWithCode(sessionDB.runCommand({ - doTxn: [{op: "u", ns: testColl.getFullName(), o2: {_id: 0}, o: {$set: {a: 5}}}], - txnNumber: NumberLong(session.getTxnNumber_forTesting()), - stmtId: NumberInt(1), - autocommit: false -}), - ErrorCodes.OperationNotSupportedInTransaction); - jsTestLog("Test that you can't run find on a prepared transaction."); assert.commandFailedWithCode(assert.throws(function() { sessionColl.find({}).toArray(); diff --git a/jstests/core/txns/do_txn_atomicity.js b/jstests/core/txns/do_txn_atomicity.js deleted file mode 100644 index 72d2591dbb6..00000000000 --- a/jstests/core/txns/do_txn_atomicity.js +++ /dev/null @@ -1,88 +0,0 @@ -// @tags: [uses_transactions] - -// Tests that doTxn is atomic for CRUD operations -(function() { -'use strict'; - -var session = db.getMongo().startSession(); -var sessionDb = session.getDatabase("test"); -var txnNumber = 0; - -var t = db.doTxn; -t.drop({writeConcern: {w: "majority"}}); -assert.writeOK(t.insert({_id: 1})); - -// Operations including commands are not allowed and should be rejected completely. -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. -assert.commandFailedWithCode(sessionDb.adminCommand({ - doTxn: [ - {op: 'i', ns: t.getFullName(), o: {_id: ObjectId(), x: 1}}, - {op: 'i', ns: "invalid", o: {_id: ObjectId(), x: 1}}, - ], - txnNumber: NumberLong(txnNumber++) -}), - ErrorCodes.InvalidNamespace); -assert.eq(t.count({x: 1}), 0); - -// Operations on non-existent databases cannot be atomic. -var newDBName = "do_txn_atomicity"; -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}}], - txnNumber: NumberLong(txnNumber++) -}), - ErrorCodes.NamespaceNotFound); - -var sawTooManyLocksError = false; - -function applyWithManyLocks(n) { - let cappedOps = []; - let multiOps = []; - - for (let i = 0; i < n; i++) { - // Write to a capped collection, as that may require a lock for serialization. - let cappedName = "capped" + n + "-" + i; - assert.commandWorked(newDB.createCollection(cappedName, {capped: true, size: 100})); - cappedOps.push({op: 'i', ns: newDBName + "." + cappedName, o: {_id: 0}}); - - // Make an index multi-key, as that may require a lock for updating the catalog. - let multiName = "multi" + n + "-" + i; - assert.commandWorked(newDB[multiName].createIndex({x: 1})); - multiOps.push({op: 'i', ns: newDBName + "." + multiName, o: {_id: 0, x: [0, 1]}}); - } - - 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) - res.every((res) => res.ok); - // All transactions should either completely succeed or completely fail. - assert(res.every((res) => res.results.every((result) => result == res.ok))); - assert(res.every((res) => !res.ok || res.applied == n)); -} - -// Try requiring different numbers of collection accesses in a single operation to cover -// all edge cases, so we run out of available locks in different code paths such as during -// oplog application. -applyWithManyLocks(1); -applyWithManyLocks(2); - -for (let i = 9; i < 20; i++) { - applyWithManyLocks(i); -} -assert(!sawTooManyLocksError, "test should not exhaust the max number of locks held at once"); -})(); diff --git a/jstests/core/txns/do_txn_basic.js b/jstests/core/txns/do_txn_basic.js deleted file mode 100644 index 1b0cc100644..00000000000 --- a/jstests/core/txns/do_txn_basic.js +++ /dev/null @@ -1,339 +0,0 @@ -// @tags: [uses_transactions] - -(function() { -"use strict"; - -const t = db.do_txn1; - -var session = db.getMongo().startSession(); -db = session.getDatabase("test"); -var txnNumber = 0; - -// Use majority write concern to clear the drop-pending that can cause lock conflicts with -// transactions. -t.drop({writeConcern: {w: "majority"}}); - -// -// Input validation tests -// - -jsTestLog("Empty array of operations."); -assert.commandFailedWithCode(db.adminCommand({doTxn: [], txnNumber: NumberLong(txnNumber++)}), - ErrorCodes.InvalidOptions, - 'doTxn should fail on empty array of operations'); - -jsTestLog("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'); - -jsTestLog("Missing 'op' field in an operation."); -assert.commandFailedWithCode( - db.adminCommand( - {doTxn: [{ns: t.getFullName(), o: {_id: 0}}], txnNumber: NumberLong(txnNumber++)}), - ErrorCodes.FailedToParse, - 'doTxn should fail on operation without "op" field'); - -jsTestLog("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'); - -jsTestLog("Empty 'op' field value in an operation."); -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'); - -jsTestLog("Missing 'ns' field in an operation."); -assert.commandFailedWithCode( - db.adminCommand({doTxn: [{op: 'u', o: {_id: 0}}], txnNumber: NumberLong(txnNumber++)}), - ErrorCodes.FailedToParse, - 'doTxn should fail on operation without "ns" field'); - -jsTestLog("Missing 'o' field in an operation."); -assert.commandFailedWithCode( - db.adminCommand({doTxn: [{op: 'u', ns: t.getFullName()}], txnNumber: NumberLong(txnNumber++)}), - ErrorCodes.FailedToParse, - 'doTxn should fail on operation without "o" field'); - -jsTestLog("Non-string 'ns' field in an operation."); -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'); - -jsTestLog("Missing dbname in 'ns' field."); -assert.commandFailedWithCode( - 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'); - -jsTestLog("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'); - -jsTestLog("Valid 'ns' field value in unknown operation type 'x'."); -assert.commandFailedWithCode( - db.adminCommand( - {doTxn: [{op: 'x', ns: t.getFullName(), o: {_id: 0}}], txnNumber: NumberLong(txnNumber++)}), - ErrorCodes.FailedToParse, - 'doTxn should fail on unknown operation type "x" with valid "ns" value'); - -jsTestLog("Illegal operation type 'n' (no-op)."); -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.'); - -jsTestLog("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.'); - -jsTestLog("No transaction number in an otherwise valid operation."); -assert.commandFailedWithCode( - db.adminCommand({doTxn: [{"op": "i", "ns": t.getFullName(), "o": {_id: 5, x: 17}}]}), - ErrorCodes.InvalidOptions, - 'doTxn should fail when no transaction number is given.'); - -jsTestLog("Session IDs and transaction numbers on sub-ops are not allowed"); -jsTestLog("doTxn should fail when inner transaction contains session id."); -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.'); - -jsTestLog("doTxn should fail when inner transaction contains transaction number."); -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.'); - -jsTestLog("doTxn should fail when inner transaction contains statement id."); -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.'); - -jsTestLog("Malformed operation with unexpected field 'x'."); -assert.commandFailedWithCode(db.adminCommand({ - doTxn: [{op: 'i', ns: t.getFullName(), o: {_id: 0}, x: 1}], - txnNumber: NumberLong(txnNumber++) -}), - ErrorCodes.FailedToParse, - 'doTxn should fail on malformed operations.'); - -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) - * - * Leave 'expectedErrorCode' undefined if this command is expected to run successfully. - */ -function testCrudOperationOnNonExistentNamespace(optype, o, o2, expectedErrorCode) { - expectedErrorCode = expectedErrorCode || ErrorCodes.OK; - 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], txnNumber: NumberLong(txnNumber++)}; - 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, delete, and update operations on non-existent collections/databases should return -// NamespaceNotFound. -jsTestLog("testCrudOperationOnNonExistentNamespace"); -testCrudOperationOnNonExistentNamespace('i', {_id: 0}, {}, ErrorCodes.NamespaceNotFound); -testCrudOperationOnNonExistentNamespace('d', {_id: 0}, {}, ErrorCodes.NamespaceNotFound); -testCrudOperationOnNonExistentNamespace('u', {x: 0}, {_id: 0}, ErrorCodes.NamespaceNotFound); - -jsTestLog("Valid insert"); -assert.commandWorked(db.createCollection(t.getName())); -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"); - -jsTestLog("Duplicate insert"); -a = assert.commandFailedWithCode(db.adminCommand({ - doTxn: [{"op": "i", "ns": t.getFullName(), "o": {_id: 5, x: 17}}], - txnNumber: NumberLong(txnNumber++) -}), - ErrorCodes.DuplicateKey); -assert.eq( - 1, t.find().count(), "The number of documents changed despite the duplicate insert failing"); -assert.eq(false, a.results[0], "Bad result value for duplicate insert"); - -var o = {_id: 5, x: 17}; -assert.eq(o, t.findOne(), "Mismatching document inserted."); - -jsTestLog("doTxn should fail on insert of object with empty array element"); -// 'o' field is an empty array. -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'); - -jsTestLog("two valid updates"); -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++; -o.x++; - -assert.eq(1, t.find().count(), "Updates increased number of documents"); -assert.eq(o, t.findOne(), "Document doesn't match expected"); -assert.eq(true, res.results[0], "Bad result value for valid update"); -assert.eq(true, res.results[1], "Bad result value for valid update"); - -jsTestLog("preCondition fully matches"); -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}}], - txnNumber: NumberLong(txnNumber++) -})); - -o.x++; -o.x++; - -assert.eq(1, t.find().count(), "Updates increased number of documents"); -assert.eq(o, t.findOne(), "Document doesn't match expected"); -assert.eq(true, res.results[0], "Bad result value for valid update"); -assert.eq(true, res.results[1], "Bad result value for valid update"); - -jsTestLog("preCondition doesn't match ns"); -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}}], - txnNumber: NumberLong(txnNumber++) -})); - -assert.eq(o, t.findOne(), "preCondition didn't match, but ops were still applied"); - -jsTestLog("preCondition doesn't match query"); -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}}], - txnNumber: NumberLong(txnNumber++) -})); - -assert.eq(o, t.findOne(), "preCondition didn't match, but ops were still applied"); - -jsTestLog("upsert disallowed"); -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}}} - ], - txnNumber: NumberLong(txnNumber++) -})); - -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."); - -// When applying a "u" (update) op, we default to 'UpdateNode' update semantics, and $set -// operations add new fields in lexicographic order. -jsTestLog("$set field addition order"); -res = assert.commandWorked(db.adminCommand({ - 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. - -// 'ModifierInterface' semantics are not supported, so an update with {$v: 0} should fail. -jsTestLog("Fail update with {$v:0}"); -res = assert.commandFailed(db.adminCommand({ - doTxn: [ - {"op": "i", "ns": t.getFullName(), "o": {_id: 7}}, - { - "op": "u", - "ns": t.getFullName(), - "o2": {_id: 7}, - "o": {$v: NumberLong(0), $set: {z: 1, a: 2}} - } - ], - txnNumber: NumberLong(txnNumber++), -})); -assert.eq(res.code, 40682); - -// When we explicitly specify {$v: 1}, we should get 'UpdateNode' update semantics, and $set -// operations get performed in lexicographic order. -jsTestLog("update with {$v:1}"); -res = assert.commandWorked(db.adminCommand({ - doTxn: [ - {"op": "i", "ns": t.getFullName(), "o": {_id: 8}}, - { - "op": "u", - "ns": t.getFullName(), - "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/txns/statement_ids_accepted.js b/jstests/core/txns/statement_ids_accepted.js index 05640ead1f8..d93c0e818be 100644 --- a/jstests/core/txns/statement_ids_accepted.js +++ b/jstests/core/txns/statement_ids_accepted.js @@ -90,8 +90,6 @@ assert.commandWorked(sessionDb.runCommand({ autocommit: false })); -// The doTxn command is intentionally left out. - jsTestLog("Check that find and getmore accept a statement ID"); // Put in some data to find so getMore has a cursor to use. assert.writeOK(testColl.insert([{_id: 0}, {_id: 1}], {writeConcern: {w: "majority"}})); diff --git a/jstests/core/views/views_all_commands.js b/jstests/core/views/views_all_commands.js index 30019797aa9..48374ac51ac 100644 --- a/jstests/core/views/views_all_commands.js +++ b/jstests/core/views/views_all_commands.js @@ -200,20 +200,6 @@ let viewsCommandTests = { dbStats: {skip: "TODO(SERVER-25948)"}, 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"}], - txnNumber: NumberLong("0"), - lsid: {id: UUID()} - }, - expectFailure: true, - expectedErrorCode: [ - ErrorCodes.CommandNotSupportedOnView, - ErrorCodes.CommandNotSupported, - ErrorCodes.IllegalOperation - ], - skipSharded: true, - }, driverOIDTest: {skip: isUnrelated}, drop: {command: {drop: "view"}}, dropAllRolesFromDatabase: {skip: isUnrelated}, diff --git a/jstests/libs/override_methods/read_and_write_concern_helpers.js b/jstests/libs/override_methods/read_and_write_concern_helpers.js index 6a04d6dc260..5fa3443ae72 100644 --- a/jstests/libs/override_methods/read_and_write_concern_helpers.js +++ b/jstests/libs/override_methods/read_and_write_concern_helpers.js @@ -51,7 +51,6 @@ var kCommandsSupportingWriteConcern = new Set([ "createUser", "delete", "deleteIndexes", - "doTxn", "drop", "dropAllRolesFromDatabase", "dropAllUsersFromDatabase", @@ -82,4 +81,4 @@ var kCommandsSupportingWriteConcern = new Set([ ]); var kCommandsSupportingWriteConcernInTransaction = - new Set(["doTxn", "abortTransaction", "commitTransaction"]); + new Set(["abortTransaction", "commitTransaction"]); diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp index 8efc03ab32d..17bc2af8fca 100644 --- a/src/mongo/db/commands.cpp +++ b/src/mongo/db/commands.cpp @@ -114,7 +114,6 @@ const StringMap<int> txnCmdWhitelist = {{"abortTransaction", 1}, {"coordinateCommitTransaction", 1}, {"delete", 1}, {"distinct", 1}, - {"doTxn", 1}, {"find", 1}, {"findandmodify", 1}, {"findAndModify", 1}, @@ -129,7 +128,6 @@ const StringMap<int> txnCmdWhitelist = {{"abortTransaction", 1}, const StringMap<int> txnAdminCommands = {{"abortTransaction", 1}, {"commitTransaction", 1}, {"coordinateCommitTransaction", 1}, - {"doTxn", 1}, {"prepareTransaction", 1}}; } // namespace diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 61c39a599cd..d48a75d91b3 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -349,7 +349,6 @@ env.Library( "dbcheck.cpp", "dbcommands_d.cpp", "dbhash.cpp", - "do_txn_cmd.cpp", "driverHelpers.cpp", "haystack.cpp", "mr.cpp", diff --git a/src/mongo/db/commands/do_txn_cmd.cpp b/src/mongo/db/commands/do_txn_cmd.cpp deleted file mode 100644 index fbc542f952a..00000000000 --- a/src/mongo/db/commands/do_txn_cmd.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand - -#include "mongo/platform/basic.h" - -#include <vector> - -#include "mongo/bson/util/bson_check.h" -#include "mongo/bson/util/bson_extract.h" -#include "mongo/db/auth/authorization_session.h" -#include "mongo/db/catalog/collection_catalog.h" -#include "mongo/db/catalog/document_validation.h" -#include "mongo/db/client.h" -#include "mongo/db/commands.h" -#include "mongo/db/commands/oplog_application_checks.h" -#include "mongo/db/commands/test_commands_enabled.h" -#include "mongo/db/concurrency/write_conflict_exception.h" -#include "mongo/db/db_raii.h" -#include "mongo/db/dbdirectclient.h" -#include "mongo/db/jsobj.h" -#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/service_context.h" -#include "mongo/util/log.h" -#include "mongo/util/scopeguard.h" -#include "mongo/util/uuid.h" - -namespace mongo { -namespace { - -/** - * 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& doTxnObj) { - auto parseOp = [](const BSONObj& opObj) { - try { - return repl::ReplOperation::parse(IDLParserErrorContext("doTxn"), opObj); - } catch (...) { - uasserted(ErrorCodes::FailedToParse, - str::stream() << "cannot apply a malformed operation in doTxn: " - << redact(opObj) << ": " << exceptionToStatus().toString()); - } - }; - - OplogApplicationValidity ret = OplogApplicationValidity::kOk; - - 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()); - - // Iterate the ops. - for (BSONElement element : doTxnObj.firstElement().Array()) { - checkBSONType(BSONType::Object, element); - BSONObj opObj = element.Obj(); - auto op = parseOp(opObj); - - // If the op is a command, it's illegal. - uassert(ErrorCodes::InvalidOptions, - "Commands cannot be applied via doTxn.", - op.getOpType() != repl::OpTypeEnum::kCommand); - - // If the op uses any UUIDs at all then the user must possess extra privileges. - if (op.getUuid()) { - ret = OplogApplicationValidity::kNeedsUseUUID; - } - } - return ret; -} - -class DoTxnCmd : public BasicCommand { -public: - DoTxnCmd() : BasicCommand("doTxn") {} - - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } - - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } - - bool supportsReadConcern(const std::string& dbName, - const BSONObj& cmdObj, - repl::ReadConcernLevel level) const { - // Support the read concerns before and after upconversion. - return level == repl::ReadConcernLevel::kLocalReadConcern || - level == repl::ReadConcernLevel::kSnapshotReadConcern; - } - - std::string help() const override { - return "internal (sharding)\n{ doTxn : [ ] , preCondition : [ { ns : ... , q : ... , " - "res : ... } ] }"; - } - - Status checkAuthForOperation(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj) const override { - OplogApplicationValidity validity = validateDoTxnCommand(cmdObj); - return OplogApplicationChecks::checkAuthForCommand(opCtx, dbname, cmdObj, validity); - } - - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - - validateDoTxnCommand(cmdObj); - - boost::optional<DisableDocumentValidation> maybeDisableValidation; - if (shouldBypassDocumentValidationForCommand(cmdObj)) - maybeDisableValidation.emplace(opCtx); - - auto status = OplogApplicationChecks::checkOperationArray(cmdObj.firstElement()); - uassertStatusOK(status); - - // TODO (SERVER-30217): When a write concern is provided to the doTxn command, we - // normally wait on the OpTime of whichever operation successfully completed last. This is - // erroneous, however, if the last operation in the array happens to be a write no-op and - // thus isn’t assigned an OpTime. Let the second to last operation in the doTxn be write - // A, the last operation in doTxn be write B. Let B do a no-op write and let the - // operation that caused B to be a no-op be C. If C has an OpTime after A but before B, - // then we won’t wait for C to be replicated and it could be rolled back, even though B - // 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. - - auto doTxnStatus = CommandHelpers::appendCommandStatusNoThrow( - result, doTxn(opCtx, dbname, cmdObj, &result)); - - return doTxnStatus; - } -}; - -MONGO_REGISTER_TEST_COMMAND(DoTxnCmd); - -} // namespace -} // namespace mongo diff --git a/src/mongo/db/commands/oplog_application_checks.h b/src/mongo/db/commands/oplog_application_checks.h index 4831950b3d4..3537dc716ab 100644 --- a/src/mongo/db/commands/oplog_application_checks.h +++ b/src/mongo/db/commands/oplog_application_checks.h @@ -50,11 +50,9 @@ class OperationContext; // with a specified UUID, so both the forceUUID and useUUID actions must be authorized. // // kOk means no special conditions apply. -// -// Only kOk and kNeedsUseUUID are valid for 'doTxn'. All are valid for 'applyOps'. enum class OplogApplicationValidity { kOk, kNeedsUseUUID, kNeedsForceAndUseUUID, kNeedsSuperuser }; -// OplogApplicationChecks contains helper functions for checking the applyOps and doTxn commands. +// OplogApplicationChecks contains helper functions for checking the applyOps command. class OplogApplicationChecks { public: /** diff --git a/src/mongo/db/initialize_operation_session_info.cpp b/src/mongo/db/initialize_operation_session_info.cpp index 9be38b08946..0257878e145 100644 --- a/src/mongo/db/initialize_operation_session_info.cpp +++ b/src/mongo/db/initialize_operation_session_info.cpp @@ -143,22 +143,6 @@ OperationSessionInfoFromClient initializeOperationSessionInfo(OperationContext* osi.getStartTransaction().value()); } - // Populate the session info for doTxn command. - if (requestBody.firstElementFieldName() == "doTxn"_sd) { - uassert(ErrorCodes::InvalidOptions, - "doTxn can only be run with a transaction number.", - osi.getTxnNumber()); - uassert(ErrorCodes::OperationNotSupportedInTransaction, - "doTxn can not be run in a transaction", - !osi.getAutocommit()); - // 'autocommit' and 'startTransaction' are populated for 'doTxn' to get the oplog - // entry generation behavior used for multi-document transactions. The 'doTxn' - // command still logically behaves as a commit. - osi.setAutocommit(false); - osi.setStartTransaction(true); - } - - return osi; } diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript index eda9c1c6315..871e321363d 100644 --- a/src/mongo/db/repl/SConscript +++ b/src/mongo/db/repl/SConscript @@ -32,7 +32,6 @@ env.Library( target='oplog', source=[ 'apply_ops.cpp', - 'do_txn.cpp', 'oplog.cpp', 'transaction_oplog_application.cpp', env.Idlc('apply_ops.idl')[0], @@ -116,25 +115,6 @@ env.Library( ], ) -env.CppUnitTest( - target='do_txn_test', - source=[ - 'do_txn_test.cpp', - ], - LIBDEPS=[ - 'oplog', - 'oplog_entry', - 'oplog_interface_local', - 'replmocks', - 'storage_interface_impl', - '$BUILD_DIR/mongo/db/auth/authmocks', - '$BUILD_DIR/mongo/db/s/op_observer_sharding_impl', - '$BUILD_DIR/mongo/db/service_context_d_test_fixture', - '$BUILD_DIR/mongo/db/transaction', - '$BUILD_DIR/mongo/rpc/command_status', - ], -) - env.Library( target='rollback_source_impl', source=[ diff --git a/src/mongo/db/repl/do_txn.cpp b/src/mongo/db/repl/do_txn.cpp deleted file mode 100644 index d9d6f7bf2ce..00000000000 --- a/src/mongo/db/repl/do_txn.cpp +++ /dev/null @@ -1,313 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ -#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand - -#include "mongo/platform/basic.h" - -#include "mongo/db/repl/do_txn.h" - -#include "mongo/bson/util/bson_extract.h" -#include "mongo/db/catalog/collection.h" -#include "mongo/db/catalog/collection_catalog.h" -#include "mongo/db/catalog/database.h" -#include "mongo/db/catalog/database_holder.h" -#include "mongo/db/catalog/document_validation.h" -#include "mongo/db/client.h" -#include "mongo/db/concurrency/lock_state.h" -#include "mongo/db/concurrency/write_conflict_exception.h" -#include "mongo/db/curop.h" -#include "mongo/db/db_raii.h" -#include "mongo/db/dbhelpers.h" -#include "mongo/db/index/index_descriptor.h" -#include "mongo/db/matcher/matcher.h" -#include "mongo/db/op_observer.h" -#include "mongo/db/operation_context.h" -#include "mongo/db/query/collation/collation_spec.h" -#include "mongo/db/repl/replication_coordinator.h" -#include "mongo/db/service_context.h" -#include "mongo/db/transaction_participant.h" -#include "mongo/db/views/view_catalog.h" -#include "mongo/rpc/get_status_from_command_result.h" -#include "mongo/util/fail_point_service.h" -#include "mongo/util/log.h" - -namespace mongo { - -constexpr StringData DoTxn::kPreconditionFieldName; - -namespace { - -// If enabled, causes loop in _doTxn() to hang after applying current operation. -MONGO_FAIL_POINT_DEFINE(doTxnPauseBetweenOperations); - -/** - * Return true iff the doTxnCmd can be executed in a single WriteUnitOfWork. - */ -bool _areOpsCrudOnly(const BSONObj& doTxnCmd) { - for (const auto& elem : doTxnCmd.firstElement().Obj()) { - const char* opType = elem.Obj().getField("op").valuestrsafe(); - - // All atomic ops have an opType of length 1. - if (opType[0] == '\0' || opType[1] != '\0') - return false; - - // Only consider CRUD operations. - switch (*opType) { - case 'd': - case 'u': - break; - case 'i': - break; - // Fallthrough. - default: - return false; - } - } - - return true; -} - -Status _doTxn(OperationContext* opCtx, - const std::string& dbName, - const BSONObj& doTxnCmd, - BSONObjBuilder* result, - int* numApplied) { - BSONObj ops = doTxnCmd.firstElement().Obj(); - // apply - *numApplied = 0; - int errors = 0; - - BSONObjIterator i(ops); - BSONArrayBuilder ab; - 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(); - - boost::optional<NamespaceString> nss = NamespaceString(opObj["ns"].String()); - - // Need to check this here, or OldClientContext may fail an invariant. - if (!nss->isValid()) - return {ErrorCodes::InvalidNamespace, "invalid ns: " + nss->ns()}; - - Status status(ErrorCodes::InternalError, ""); - - AutoGetDb autoDb(opCtx, nss->db(), MODE_IX); - auto db = autoDb.getDb(); - if (!db) { - uasserted(ErrorCodes::NamespaceNotFound, - str::stream() << "cannot apply insert, delete, or update operation on a " - "non-existent namespace " - << nss->ns() << ": " << mongo::redact(opObj)); - } - - if (opObj.hasField("ui")) { - auto uuidStatus = UUID::parse(opObj["ui"]); - uassertStatusOK(uuidStatus.getStatus()); - // If "ui" is present, it overrides "nss" for the collection name. - nss = CollectionCatalog::get(opCtx).lookupNSSByUUID(uuidStatus.getValue()); - uassert(ErrorCodes::NamespaceNotFound, - str::stream() << "cannot find collection uuid " << uuidStatus.getValue(), - nss); - } - Lock::CollectionLock collLock(opCtx, *nss, MODE_IX); - auto collection = db->getCollection(opCtx, *nss); - - // 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. - if (!collection && ViewCatalog::get(db)->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)); - } - - // 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, db, opObj, alwaysUpsert, repl::OplogApplication::Mode::kApplyOpsCmd); - if (!status.isOK()) - return status; - - ab.append(status.isOK()); - if (!status.isOK()) { - log() << "doTxn error applying: " << status; - errors++; - } - - (*numApplied)++; - - if (MONGO_FAIL_POINT(doTxnPauseBetweenOperations)) { - MONGO_FAIL_POINT_PAUSE_WHILE_SET(doTxnPauseBetweenOperations); - } - } - - result->append("applied", *numApplied); - result->append("results", ab.arr()); - - if (errors != 0) { - return Status(ErrorCodes::UnknownError, "doTxn had one or more errors applying ops"); - } - - return Status::OK(); -} - -bool _hasPrecondition(const BSONObj& doTxnCmd) { - return doTxnCmd[DoTxn::kPreconditionFieldName].type() == Array; -} - -Status _checkPrecondition(OperationContext* opCtx, - const BSONObj& doTxnCmd, - BSONObjBuilder* result) { - // Precondition check must be done in a write unit of work to make sure it's - // sharing the same snapshot as the writes. - invariant(opCtx->lockState()->inAWriteUnitOfWork()); - - invariant(_hasPrecondition(doTxnCmd)); - - for (auto elem : doTxnCmd[DoTxn::kPreconditionFieldName].Obj()) { - auto preCondition = elem.Obj(); - if (preCondition["ns"].type() != BSONType::String) { - return {ErrorCodes::InvalidNamespace, - str::stream() << "ns in preCondition must be a string, but found type: " - << typeName(preCondition["ns"].type())}; - } - const NamespaceString nss(preCondition["ns"].valueStringData()); - if (!nss.isValid()) { - return {ErrorCodes::InvalidNamespace, "invalid ns: " + nss.ns()}; - } - - // Even if snapshot isolation is provided, database catalog still needs locking. - // Only X and IX mode locks are stashed by the WriteUnitOfWork and IS->IX upgrade - // is not supported, so we can not use IS mode here. - AutoGetCollection autoColl(opCtx, nss, MODE_IX); - - Database* database = autoColl.getDb(); - if (!database) { - return {ErrorCodes::NamespaceNotFound, "database in ns does not exist: " + nss.ns()}; - } - Collection* collection = autoColl.getCollection(); - if (!collection) { - return {ErrorCodes::NamespaceNotFound, "collection in ns does not exist: " + nss.ns()}; - } - - BSONObj realres; - auto qrStatus = QueryRequest::fromLegacyQuery(nss, preCondition["q"].Obj(), {}, 0, 0, 0); - if (!qrStatus.isOK()) { - return qrStatus.getStatus(); - } - auto recordId = Helpers::findOne( - opCtx, autoColl.getCollection(), std::move(qrStatus.getValue()), false); - if (!recordId.isNull()) { - realres = collection->docFor(opCtx, recordId).value(); - } - - - // Get collection default collation. - const CollatorInterface* collator = collection->getDefaultCollator(); - - boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(opCtx, collator)); - Matcher matcher(preCondition["res"].Obj(), std::move(expCtx)); - if (!matcher.matches(realres)) { - result->append("got", realres); - result->append("whatFailed", preCondition); - return {ErrorCodes::BadValue, "preCondition failed"}; - } - } - - return Status::OK(); -} -} // namespace - -Status doTxn(OperationContext* opCtx, - const std::string& dbName, - const BSONObj& doTxnCmd, - BSONObjBuilder* result) { - auto txnParticipant = TransactionParticipant::get(opCtx); - uassert(ErrorCodes::InvalidOptions, "doTxn must be run within a transaction", txnParticipant); - invariant(txnParticipant.inMultiDocumentTransaction()); - invariant(opCtx->getWriteUnitOfWork()); - uassert( - ErrorCodes::InvalidOptions, "doTxn supports only CRUD opts.", _areOpsCrudOnly(doTxnCmd)); - auto hasPrecondition = _hasPrecondition(doTxnCmd); - - - // Acquire global lock in IX mode so that the replication state check will remain valid. - Lock::GlobalLock globalLock(opCtx, MODE_IX); - - auto replCoord = repl::ReplicationCoordinator::get(opCtx); - bool userInitiatedWritesAndNotPrimary = - opCtx->writesAreReplicated() && !replCoord->canAcceptWritesForDatabase(opCtx, dbName); - - if (userInitiatedWritesAndNotPrimary) - return Status(ErrorCodes::NotMaster, - str::stream() << "Not primary while applying ops to database " << dbName); - - int numApplied = 0; - - try { - BSONObjBuilder intermediateResult; - - // The transaction takes place in a global unit of work, so the precondition check - // and the writes will share the same snapshot. - if (hasPrecondition) { - uassertStatusOK(_checkPrecondition(opCtx, doTxnCmd, result)); - } - - numApplied = 0; - uassertStatusOK(_doTxn(opCtx, dbName, doTxnCmd, &intermediateResult, &numApplied)); - txnParticipant.commitUnpreparedTransaction(opCtx); - result->appendElements(intermediateResult.obj()); - } catch (const DBException& ex) { - txnParticipant.abortActiveUnpreparedOrStashPreparedTransaction(opCtx); - BSONArrayBuilder ab; - ++numApplied; - for (int j = 0; j < numApplied; j++) - 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()); - } - - return Status::OK(); -} - -} // namespace mongo diff --git a/src/mongo/db/repl/do_txn.h b/src/mongo/db/repl/do_txn.h deleted file mode 100644 index 7bed2bf3700..00000000000 --- a/src/mongo/db/repl/do_txn.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ -#include "mongo/base/status.h" -#include "mongo/db/repl/oplog.h" - -namespace mongo { -class BSONObj; -class BSONObjBuilder; -class OperationContext; - -class DoTxn { -public: - static constexpr StringData kPreconditionFieldName = "preCondition"_sd; -}; - -/** - * Applies ops contained in 'doTxnCmd' and populates fields in 'result' to be returned to the - * caller. The information contained in 'result' can be returned to the user if called as part - * of the execution of an 'doTxn' command. - * - * The 'oplogApplicationMode' argument determines the semantics of the operations contained within - * the given command object. This function may be called as part of a direct user invocation of the - * 'doTxn' command, or as part of the application of an 'doTxn' oplog operation. In either - * case, the mode can be set to determine how the internal ops are executed. - */ -Status doTxn(OperationContext* opCtx, - const std::string& dbName, - const BSONObj& doTxnCmd, - BSONObjBuilder* result); - -} // namespace mongo diff --git a/src/mongo/db/repl/do_txn_test.cpp b/src/mongo/db/repl/do_txn_test.cpp deleted file mode 100644 index 561579a069c..00000000000 --- a/src/mongo/db/repl/do_txn_test.cpp +++ /dev/null @@ -1,335 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/db/catalog/collection_options.h" -#include "mongo/db/catalog/database_holder.h" -#include "mongo/db/client.h" -#include "mongo/db/op_observer_noop.h" -#include "mongo/db/op_observer_registry.h" -#include "mongo/db/repl/do_txn.h" -#include "mongo/db/repl/oplog_interface_local.h" -#include "mongo/db/repl/repl_client_info.h" -#include "mongo/db/repl/replication_coordinator_mock.h" -#include "mongo/db/repl/storage_interface_impl.h" -#include "mongo/db/s/op_observer_sharding_impl.h" -#include "mongo/db/service_context_d_test_fixture.h" -#include "mongo/db/session_catalog_mongod.h" -#include "mongo/db/transaction_participant.h" -#include "mongo/logger/logger.h" -#include "mongo/rpc/get_status_from_command_result.h" -#include "mongo/stdx/memory.h" - -namespace mongo { -namespace repl { -namespace { - -boost::optional<OplogEntry> onAllTransactionCommit(OperationContext* opCtx) { - OplogInterfaceLocal oplogInterface(opCtx); - auto oplogIter = oplogInterface.makeIterator(); - auto opEntry = unittest::assertGet(oplogIter->next()); - return unittest::assertGet(OplogEntry::parse(opEntry.first)); -} - -/** - * Mock OpObserver that tracks doTxn commit events. - */ -class OpObserverMock : public OpObserverNoop { -public: - /** - * Called by doTxn() when ops are ready to commit. - */ - void onUnpreparedTransactionCommit(OperationContext* opCtx, - const std::vector<repl::ReplOperation>& statements) override; - - - /** - * Called by doTxn() when ops are ready to commit. - */ - void onPreparedTransactionCommit( - OperationContext* opCtx, - OplogSlot commitOplogEntryOpTime, - Timestamp commitTimestamp, - const std::vector<repl::ReplOperation>& statements) noexcept override; - - // If present, holds the applyOps oplog entry written out by the ObObserverImpl - // onPreparedTransactionCommit or onUnpreparedTransactionCommit. - boost::optional<OplogEntry> applyOpsOplogEntry; -}; - -void OpObserverMock::onUnpreparedTransactionCommit( - OperationContext* opCtx, const std::vector<repl::ReplOperation>& statements) { - - applyOpsOplogEntry = onAllTransactionCommit(opCtx); -} - -void OpObserverMock::onPreparedTransactionCommit( - OperationContext* opCtx, - OplogSlot commitOplogEntryOpTime, - Timestamp commitTimestamp, - const std::vector<repl::ReplOperation>& statements) noexcept { - applyOpsOplogEntry = onAllTransactionCommit(opCtx); -} - -/** - * Test fixture for doTxn(). - */ -class DoTxnTest : public ServiceContextMongoDTest { -private: - void setUp() override; - void tearDown() override; - -protected: - OperationContext* opCtx() { - return _opCtx.get(); - } - - void checkTxnTable() { - auto result = _storage->findById( - opCtx(), - NamespaceString::kSessionTransactionsTableNamespace, - BSON(SessionTxnRecord::kSessionIdFieldName << opCtx()->getLogicalSessionId()->toBSON()) - .firstElement()); - if (!_opObserver->applyOpsOplogEntry) { - ASSERT_NOT_OK(result); - return; - } - auto txnRecord = SessionTxnRecord::parse(IDLParserErrorContext("parse txn record for test"), - unittest::assertGet(result)); - - ASSERT(opCtx()->getTxnNumber()); - ASSERT_EQ(*opCtx()->getTxnNumber(), txnRecord.getTxnNum()); - ASSERT_EQ(_opObserver->applyOpsOplogEntry->getOpTime(), txnRecord.getLastWriteOpTime()); - ASSERT(_opObserver->applyOpsOplogEntry->getWallClockTime()); - ASSERT_EQ(*_opObserver->applyOpsOplogEntry->getWallClockTime(), - txnRecord.getLastWriteDate()); - } - - OpObserverMock* _opObserver = nullptr; - std::unique_ptr<StorageInterface> _storage; - ServiceContext::UniqueOperationContext _opCtx; - boost::optional<MongoDOperationContextSession> _ocs; -}; - -void DoTxnTest::setUp() { - // Set up mongod. - ServiceContextMongoDTest::setUp(); - - const auto service = getServiceContext(); - _opCtx = cc().makeOperationContext(); - - // Set up ReplicationCoordinator and create oplog. - ReplicationCoordinator::set(service, stdx::make_unique<ReplicationCoordinatorMock>(service)); - setOplogCollectionName(service); - createOplog(_opCtx.get()); - - // Ensure that we are primary. - auto replCoord = ReplicationCoordinator::get(_opCtx.get()); - ASSERT_OK(replCoord->setFollowerMode(MemberState::RS_PRIMARY)); - - // onStepUp() relies on the storage interface to create the config.transactions table. - repl::StorageInterface::set(service, std::make_unique<StorageInterfaceImpl>()); - - // Set up session catalog - MongoDSessionCatalog::onStepUp(_opCtx.get()); - - // Need the OpObserverImpl in the registry in order for doTxn to work. - OpObserverRegistry* opObserverRegistry = - dynamic_cast<OpObserverRegistry*>(service->getOpObserver()); - opObserverRegistry->addObserver(stdx::make_unique<OpObserverShardingImpl>()); - - // Use OpObserverMock to track applyOps calls generated by doTxn(). - auto opObserver = stdx::make_unique<OpObserverMock>(); - _opObserver = opObserver.get(); - opObserverRegistry->addObserver(std::move(opObserver)); - - // This test uses StorageInterface to create collections and inspect documents inside - // collections. - _storage = stdx::make_unique<StorageInterfaceImpl>(); - - // Set up the transaction and session. - _opCtx->setLogicalSessionId(makeLogicalSessionIdForTest()); - _opCtx->setTxnNumber(0); // TxnNumber can always be 0 because we have a new session. - _ocs.emplace(_opCtx.get()); - - auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant.beginOrContinue(opCtx(), *opCtx()->getTxnNumber(), false, true); - txnParticipant.unstashTransactionResources(opCtx(), "doTxn"); -} - -void DoTxnTest::tearDown() { - _ocs = boost::none; - _opCtx = nullptr; - _storage = {}; - _opObserver = nullptr; - - // Reset default log level in case it was changed. - logger::globalLogDomain()->setMinimumLoggedSeverity(logger::LogComponent::kReplication, - logger::LogSeverity::Debug(0)); - - ServiceContextMongoDTest::tearDown(); -} - -/** - * Fixes up result document returned by doTxn and converts to Status. - */ -Status getStatusFromDoTxnResult(const BSONObj& result) { - if (result["ok"]) { - return getStatusFromCommandResult(result); - } - - BSONObjBuilder builder; - builder.appendElements(result); - auto code = result.getIntField("code"); - builder.appendIntOrLL("ok", code == 0); - auto newResult = builder.obj(); - return getStatusFromCommandResult(newResult); -} - -BSONObj makeInsertOperation(const NamespaceString& nss, - const OptionalCollectionUUID& uuid, - const BSONObj& documentToInsert) { - return uuid ? BSON("op" - << "i" - << "ns" << nss.ns() << "o" << documentToInsert << "ui" << *uuid) - : BSON("op" - << "i" - << "ns" << nss.ns() << "o" << documentToInsert); -} - -/** - * Creates an doTxn command object with a single insert operation. - */ -BSONObj makeDoTxnWithInsertOperation(const NamespaceString& nss, - const OptionalCollectionUUID& uuid, - const BSONObj& documentToInsert) { - auto insertOp = makeInsertOperation(nss, uuid, documentToInsert); - return BSON("doTxn" << BSON_ARRAY(insertOp)); -} - -/** - * Creates an applyOps command object with a single insert operation. - */ -BSONObj makeApplyOpsWithInsertOperation(const NamespaceString& nss, - const OptionalCollectionUUID& uuid, - const BSONObj& documentToInsert) { - auto insertOp = makeInsertOperation(nss, uuid, documentToInsert); - return BSON("applyOps" << BSON_ARRAY(insertOp)); -} - -TEST_F(DoTxnTest, AtomicDoTxnInsertIntoNonexistentCollectionReturnsNamespaceNotFoundInResult) { - NamespaceString nss("test.t"); - auto documentToInsert = BSON("_id" << 0); - auto cmdObj = makeDoTxnWithInsertOperation(nss, boost::none, documentToInsert); - BSONObjBuilder resultBuilder; - ASSERT_EQUALS(ErrorCodes::UnknownError, doTxn(opCtx(), "test", cmdObj, &resultBuilder)); - auto result = resultBuilder.obj(); - auto status = getStatusFromDoTxnResult(result); - ASSERT_EQUALS(ErrorCodes::NamespaceNotFound, status); - checkTxnTable(); -} - -TEST_F(DoTxnTest, AtomicDoTxnInsertWithUuidIntoCollectionWithUuid) { - NamespaceString nss("test.t"); - - auto uuid = UUID::gen(); - - CollectionOptions collectionOptions; - collectionOptions.uuid = uuid; - ASSERT_OK(_storage->createCollection(opCtx(), nss, collectionOptions)); - - auto documentToInsert = BSON("_id" << 0); - auto cmdObj = makeDoTxnWithInsertOperation(nss, uuid, documentToInsert); - auto expectedCmdObj = makeApplyOpsWithInsertOperation(nss, uuid, documentToInsert); - BSONObjBuilder resultBuilder; - ASSERT_OK(doTxn(opCtx(), "test", cmdObj, &resultBuilder)); - ASSERT_EQ(expectedCmdObj.woCompare(_opObserver->applyOpsOplogEntry->getObject(), - BSONObj(), - BSONObj::ComparisonRules::kIgnoreFieldOrder | - BSONObj::ComparisonRules::kConsiderFieldName), - 0) - << "expected: " << expectedCmdObj - << " got: " << _opObserver->applyOpsOplogEntry->getObject(); - checkTxnTable(); -} - -TEST_F(DoTxnTest, AtomicDoTxnInsertWithUuidIntoCollectionWithOtherUuid) { - NamespaceString nss("test.t"); - - auto doTxnUuid = UUID::gen(); - - // Collection has a different UUID. - CollectionOptions collectionOptions; - collectionOptions.uuid = UUID::gen(); - ASSERT_NOT_EQUALS(doTxnUuid, *collectionOptions.uuid); - ASSERT_OK(_storage->createCollection(opCtx(), nss, collectionOptions)); - - // The doTxn returns a NamespaceNotFound error because of the failed UUID lookup - // even though a collection exists with the same namespace as the insert operation. - auto documentToInsert = BSON("_id" << 0); - auto cmdObj = makeDoTxnWithInsertOperation(nss, doTxnUuid, documentToInsert); - BSONObjBuilder resultBuilder; - ASSERT_EQUALS(ErrorCodes::UnknownError, doTxn(opCtx(), "test", cmdObj, &resultBuilder)); - auto result = resultBuilder.obj(); - auto status = getStatusFromDoTxnResult(result); - ASSERT_EQUALS(ErrorCodes::NamespaceNotFound, status); - checkTxnTable(); -} - -TEST_F(DoTxnTest, AtomicDoTxnInsertWithoutUuidIntoCollectionWithUuid) { - NamespaceString nss("test.t"); - - auto uuid = UUID::gen(); - - CollectionOptions collectionOptions; - collectionOptions.uuid = uuid; - ASSERT_OK(_storage->createCollection(opCtx(), nss, collectionOptions)); - - auto documentToInsert = BSON("_id" << 0); - auto cmdObj = makeDoTxnWithInsertOperation(nss, boost::none, documentToInsert); - BSONObjBuilder resultBuilder; - ASSERT_OK(doTxn(opCtx(), "test", cmdObj, &resultBuilder)); - - // Insert operation provided by caller did not contain collection uuid but doTxn() should add - // the uuid to the oplog entry. - auto expectedCmdObj = makeApplyOpsWithInsertOperation(nss, uuid, documentToInsert); - ASSERT_EQ(expectedCmdObj.woCompare(_opObserver->applyOpsOplogEntry->getObject(), - BSONObj(), - BSONObj::ComparisonRules::kIgnoreFieldOrder | - BSONObj::ComparisonRules::kConsiderFieldName), - 0) - << "expected: " << expectedCmdObj - << " got: " << _opObserver->applyOpsOplogEntry->getObject(); - checkTxnTable(); -} - -} // namespace -} // namespace repl -} // namespace mongo diff --git a/src/mongo/db/transaction_validation.cpp b/src/mongo/db/transaction_validation.cpp index d69b935e5bb..a2af2228612 100644 --- a/src/mongo/db/transaction_validation.cpp +++ b/src/mongo/db/transaction_validation.cpp @@ -61,7 +61,7 @@ void validateWriteConcernForTransaction(const WriteConcernOptions& wcResult, Str "writeConcern is not allowed within a multi-statement transaction", wcResult.usedDefault || cmdName == "commitTransaction" || cmdName == "coordinateCommitTransaction" || cmdName == "abortTransaction" || - cmdName == "prepareTransaction" || cmdName == "doTxn"); + cmdName == "prepareTransaction"); } bool shouldCommandSkipSessionCheckout(StringData cmdName) { |