diff --git a/specifications/causal-consistency/tests/causal-consistency-clientBulkWrite.json b/specifications/causal-consistency/tests/causal-consistency-clientBulkWrite.json new file mode 100644 index 00000000000..c2a04422dd5 --- /dev/null +++ b/specifications/causal-consistency/tests/causal-consistency-clientBulkWrite.json @@ -0,0 +1,151 @@ +{ + "description": "causal consistency bulkWrite include afterClusterTime", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "uriOptions": { + "retryWrites": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "causal-consistency-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0", + "sessionOptions": { + "causalConsistency": true + } + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "causal-consistency-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "clientBulkWrite includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "session": "session0", + "models": [ + { + "insertOne": { + "namespace": "causal-consistency-tests.test", + "document": { + "_id": 4 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "command": { + "bulkWrite": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + } + ] +} diff --git a/specifications/causal-consistency/tests/causal-consistency-clientBulkWrite.yml b/specifications/causal-consistency/tests/causal-consistency-clientBulkWrite.yml new file mode 100644 index 00000000000..d1902e188e5 --- /dev/null +++ b/specifications/causal-consistency/tests/causal-consistency-clientBulkWrite.yml @@ -0,0 +1,75 @@ +description: "causal consistency bulkWrite include afterClusterTime" + +schemaVersion: "1.3" + +runOnRequirements: + - minServerVersion: "8.0" + topologies: [replicaset, sharded, load-balanced] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + uriOptions: + retryWrites: false + observeEvents: [commandStartedEvent] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName causal-consistency-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName test + - session: + id: &session0 session0 + client: *client0 + sessionOptions: + causalConsistency: true + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +# In a causally consistent session, once an operationTime has been established by a prior +# operation, subsequent write commands MUST include readConcern.afterClusterTime so the +# server can apply the write causally after the previously-observed data. + +tests: + - description: "clientBulkWrite includes afterClusterTime in causally consistent session" + operations: + - name: find + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectResult: [{ _id: 1, x: 11 }] + - name: clientBulkWrite + object: *client0 + arguments: + session: *session0 + models: + - insertOne: + namespace: causal-consistency-tests.test + document: { _id: 4 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: find + command: + find: *collectionName + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + - commandStartedEvent: + commandName: bulkWrite + command: + bulkWrite: 1 + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } diff --git a/specifications/causal-consistency/tests/causal-consistency-write-commands.json b/specifications/causal-consistency/tests/causal-consistency-write-commands.json new file mode 100644 index 00000000000..a8140c2b3f7 --- /dev/null +++ b/specifications/causal-consistency/tests/causal-consistency-write-commands.json @@ -0,0 +1,1396 @@ +{ + "description": "causal consistency write commands include afterClusterTime", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "uriOptions": { + "retryWrites": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "causal-consistency-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0", + "sessionOptions": { + "causalConsistency": true + } + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "causal-consistency-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "insertOne includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 4 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "insert": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "insertMany includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "insertMany", + "object": "collection0", + "arguments": { + "session": "session0", + "documents": [ + { + "_id": 4 + }, + { + "_id": 5 + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "insert": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "updateOne includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 100 + } + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "update", + "command": { + "update": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "updateMany includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "updateMany", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gt": 0 + } + }, + "update": { + "$set": { + "updated": true + } + } + }, + "expectResult": { + "matchedCount": 3, + "modifiedCount": 3, + "upsertedCount": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "update", + "command": { + "update": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "replaceOne includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "replaceOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "replacement": { + "x": 100 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "update", + "command": { + "update": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "deleteOne includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "deleteOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": { + "deletedCount": 1 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "delete", + "command": { + "delete": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "deleteMany includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "deleteMany", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gt": 0 + } + } + }, + "expectResult": { + "deletedCount": 3 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "delete", + "command": { + "delete": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "findOneAndUpdate includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 100 + } + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify", + "command": { + "findAndModify": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "findOneAndDelete includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "findOneAndDelete", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify", + "command": { + "findAndModify": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "findOneAndReplace includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "findOneAndReplace", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "replacement": { + "x": 100 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify", + "command": { + "findAndModify": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "bulkWrite includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "insertOne": { + "document": { + "_id": 4 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$set": { + "x": 100 + } + } + } + }, + { + "deleteOne": { + "filter": { + "_id": 3 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "insert": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "update", + "command": { + "update": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "delete", + "command": { + "delete": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "create includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "dropCollection", + "object": "database0", + "arguments": { + "session": "session0", + "collection": "causal-consistency-createCollection-test" + } + }, + { + "name": "createCollection", + "object": "database0", + "arguments": { + "session": "session0", + "collection": "causal-consistency-createCollection-test" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "drop", + "command": { + "drop": "causal-consistency-createCollection-test", + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "create", + "command": { + "create": "causal-consistency-createCollection-test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "createIndexes includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "createIndex", + "object": "collection0", + "arguments": { + "session": "session0", + "keys": { + "x": 1 + }, + "name": "x_1" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "createIndexes", + "command": { + "createIndexes": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "drop includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "dropCollection", + "object": "database0", + "arguments": { + "session": "session0", + "collection": "test" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "drop", + "command": { + "drop": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "dropDatabase includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "dropDatabase", + "object": "client0", + "arguments": { + "session": "session0", + "database": "causal-consistency-dropDatabase-test" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "dropDatabase", + "command": { + "dropDatabase": 1, + "$db": "causal-consistency-dropDatabase-test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "dropIndexes includes afterClusterTime in causally consistent session", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + }, + { + "name": "dropIndexes", + "object": "collection0", + "arguments": { + "session": "session0" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "dropIndexes", + "command": { + "dropIndexes": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + }, + "level": { + "$$exists": false + } + } + } + } + } + ] + } + ] + }, + { + "description": "first write command in a causally consistent session does not include afterClusterTime", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 4 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "insert": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + } + } + } + } + ] + } + ] + } + ] +} diff --git a/specifications/causal-consistency/tests/causal-consistency-write-commands.yml b/specifications/causal-consistency/tests/causal-consistency-write-commands.yml new file mode 100644 index 00000000000..a33ca174364 --- /dev/null +++ b/specifications/causal-consistency/tests/causal-consistency-write-commands.yml @@ -0,0 +1,472 @@ +description: "causal consistency write commands include afterClusterTime" + +schemaVersion: "1.3" + +runOnRequirements: + - topologies: [replicaset, sharded, load-balanced] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + uriOptions: + retryWrites: false + observeEvents: [commandStartedEvent] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName causal-consistency-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName test + - session: + id: &session0 session0 + client: *client0 + sessionOptions: + causalConsistency: true + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +# In a causally consistent session, once an operationTime has been established by a prior +# operation, subsequent write commands MUST include readConcern.afterClusterTime so the +# server can apply the write causally after the previously-observed data. + +tests: + - description: "insertOne includes afterClusterTime in causally consistent session" + operations: + - &find + name: find + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectResult: [{ _id: 1, x: 11 }] + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 4 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 4 } } + expectEvents: + - client: *client0 + events: + - &findEvent + commandStartedEvent: + commandName: find + command: + find: *collectionName + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + - commandStartedEvent: + commandName: insert + command: + insert: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "insertMany includes afterClusterTime in causally consistent session" + operations: + - *find + - name: insertMany + object: *collection0 + arguments: + session: *session0 + documents: + - { _id: 4 } + - { _id: 5 } + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: insert + command: + insert: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "updateOne includes afterClusterTime in causally consistent session" + operations: + - *find + - name: updateOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $set: { x: 100 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: update + command: + update: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "updateMany includes afterClusterTime in causally consistent session" + operations: + - *find + - name: updateMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gt: 0 } } + update: { $set: { updated: true } } + expectResult: + matchedCount: 3 + modifiedCount: 3 + upsertedCount: 0 + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: update + command: + update: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "replaceOne includes afterClusterTime in causally consistent session" + operations: + - *find + - name: replaceOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { x: 100 } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: update + command: + update: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "deleteOne includes afterClusterTime in causally consistent session" + operations: + - *find + - name: deleteOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectResult: + deletedCount: 1 + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: delete + command: + delete: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "deleteMany includes afterClusterTime in causally consistent session" + operations: + - *find + - name: deleteMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gt: 0 } } + expectResult: + deletedCount: 3 + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: delete + command: + delete: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "findOneAndUpdate includes afterClusterTime in causally consistent session" + operations: + - *find + - name: findOneAndUpdate + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $set: { x: 100 } } + expectResult: { _id: 1, x: 11 } + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: findAndModify + command: + findAndModify: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "findOneAndDelete includes afterClusterTime in causally consistent session" + operations: + - *find + - name: findOneAndDelete + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectResult: { _id: 1, x: 11 } + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: findAndModify + command: + findAndModify: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "findOneAndReplace includes afterClusterTime in causally consistent session" + operations: + - *find + - name: findOneAndReplace + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { x: 100 } + expectResult: { _id: 1, x: 11 } + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: findAndModify + command: + findAndModify: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "bulkWrite includes afterClusterTime in causally consistent session" + operations: + - *find + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - insertOne: + document: { _id: 4 } + - updateOne: + filter: { _id: 2 } + update: { $set: { x: 100 } } + - deleteOne: + filter: { _id: 3 } + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: insert + command: + insert: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + - commandStartedEvent: + commandName: update + command: + update: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + - commandStartedEvent: + commandName: delete + command: + delete: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "create includes afterClusterTime in causally consistent session" + operations: + - *find + # Drop the collection first to make sure there's no name conflict during + # createCollection. + - name: dropCollection + object: *database0 + arguments: + session: *session0 + collection: &newCollectionName causal-consistency-createCollection-test + - name: createCollection + object: *database0 + arguments: + session: *session0 + collection: *newCollectionName + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: drop + command: + drop: *newCollectionName + lsid: { $$sessionLsid: *session0 } + - commandStartedEvent: + commandName: create + command: + create: *newCollectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "createIndexes includes afterClusterTime in causally consistent session" + operations: + - *find + - name: createIndex + object: *collection0 + arguments: + session: *session0 + keys: { x: 1 } + name: x_1 + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: createIndexes + command: + createIndexes: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "drop includes afterClusterTime in causally consistent session" + operations: + - *find + - name: dropCollection + object: *database0 + arguments: + session: *session0 + collection: *collectionName + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: drop + command: + drop: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "dropDatabase includes afterClusterTime in causally consistent session" + operations: + - *find + - name: dropDatabase + object: *client0 + arguments: + session: *session0 + database: causal-consistency-dropDatabase-test + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: dropDatabase + command: + dropDatabase: 1 + $db: causal-consistency-dropDatabase-test + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "dropIndexes includes afterClusterTime in causally consistent session" + operations: + - *find + - name: dropIndexes + object: *collection0 + arguments: + session: *session0 + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: dropIndexes + command: + dropIndexes: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + # Covers the same condition as Causal Consistency prose test #2, but for a write operation. + - description: "first write command in a causally consistent session does not include afterClusterTime" + operations: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 4 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 4 } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: insert + command: + insert: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } diff --git a/specifications/transactions-convenient-api/tests/unified/callback-aborts.json b/specifications/transactions-convenient-api/tests/unified/callback-aborts.json index 206428715cd..dc8f7fb5a40 100644 --- a/specifications/transactions-convenient-api/tests/unified/callback-aborts.json +++ b/specifications/transactions-convenient-api/tests/unified/callback-aborts.json @@ -308,10 +308,12 @@ "lsid": { "$$sessionLsid": "session0" }, - "autocommit": { - "$$exists": false - }, "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "autocommit": { "$$exists": false }, "startTransaction": { diff --git a/specifications/transactions-convenient-api/tests/unified/callback-aborts.yml b/specifications/transactions-convenient-api/tests/unified/callback-aborts.yml index 9414040eca0..d5a95d77761 100644 --- a/specifications/transactions-convenient-api/tests/unified/callback-aborts.yml +++ b/specifications/transactions-convenient-api/tests/unified/callback-aborts.yml @@ -164,9 +164,10 @@ tests: - { _id: 2 } ordered: true lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } # omitted fields autocommit: { $$exists: false } - readConcern: { $$exists: false } startTransaction: { $$exists: false } writeConcern: { $$exists: false } commandName: insert diff --git a/specifications/transactions-convenient-api/tests/unified/callback-commits.json b/specifications/transactions-convenient-api/tests/unified/callback-commits.json index 06f791e9ae6..edda3864180 100644 --- a/specifications/transactions-convenient-api/tests/unified/callback-commits.json +++ b/specifications/transactions-convenient-api/tests/unified/callback-commits.json @@ -381,10 +381,12 @@ "lsid": { "$$sessionLsid": "session0" }, - "autocommit": { - "$$exists": false - }, "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "autocommit": { "$$exists": false }, "startTransaction": { diff --git a/specifications/transactions-convenient-api/tests/unified/callback-commits.yml b/specifications/transactions-convenient-api/tests/unified/callback-commits.yml index b5cbb041519..7bd3e7feaef 100644 --- a/specifications/transactions-convenient-api/tests/unified/callback-commits.yml +++ b/specifications/transactions-convenient-api/tests/unified/callback-commits.yml @@ -193,9 +193,10 @@ tests: - { _id: 3 } ordered: true lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } # omitted fields autocommit: { $$exists: false } - readConcern: { $$exists: false } startTransaction: { $$exists: false } writeConcern: { $$exists: false } commandName: insert diff --git a/specifications/transactions/tests/unified/commit.json b/specifications/transactions/tests/unified/commit.json index ab778d8df27..f0339069406 100644 --- a/specifications/transactions/tests/unified/commit.json +++ b/specifications/transactions/tests/unified/commit.json @@ -1040,7 +1040,9 @@ ], "ordered": true, "readConcern": { - "$$exists": false + "afterClusterTime": { + "$$exists": true + } }, "lsid": { "$$sessionLsid": "session1" @@ -1196,7 +1198,9 @@ ], "ordered": true, "readConcern": { - "$$exists": false + "afterClusterTime": { + "$$exists": true + } }, "lsid": { "$$sessionLsid": "session1" diff --git a/specifications/transactions/tests/unified/commit.yml b/specifications/transactions/tests/unified/commit.yml index d9af0848944..eaf4ee39ba9 100644 --- a/specifications/transactions/tests/unified/commit.yml +++ b/specifications/transactions/tests/unified/commit.yml @@ -615,7 +615,8 @@ tests: documents: - { _id: 2 } ordered: true - readConcern: { $$exists: false } + readConcern: + afterClusterTime: { $$exists: true } lsid: { $$sessionLsid: *session1 } txnNumber: { $$exists: false } startTransaction: { $$exists: false } @@ -698,7 +699,8 @@ tests: documents: - { _id: 2 } ordered: true - readConcern: { $$exists: false } + readConcern: + afterClusterTime: { $$exists: true } lsid: { $$sessionLsid: *session1 } txnNumber: { $$exists: false } startTransaction: { $$exists: false } diff --git a/specifications/transactions/tests/unified/retryable-writes.json b/specifications/transactions/tests/unified/retryable-writes.json index c196e686227..21ef22b8ab3 100644 --- a/specifications/transactions/tests/unified/retryable-writes.json +++ b/specifications/transactions/tests/unified/retryable-writes.json @@ -217,7 +217,9 @@ ], "ordered": true, "readConcern": { - "$$exists": false + "afterClusterTime": { + "$$exists": true + } }, "lsid": { "$$sessionLsid": "session0" @@ -306,7 +308,9 @@ ], "ordered": true, "readConcern": { - "$$exists": false + "afterClusterTime": { + "$$exists": true + } }, "lsid": { "$$sessionLsid": "session0" diff --git a/specifications/transactions/tests/unified/retryable-writes.yml b/specifications/transactions/tests/unified/retryable-writes.yml index aa9c037d41e..81339c9ad9e 100644 --- a/specifications/transactions/tests/unified/retryable-writes.yml +++ b/specifications/transactions/tests/unified/retryable-writes.yml @@ -132,7 +132,8 @@ tests: documents: - { _id: 2 } ordered: true - readConcern: { $$exists: false } + readConcern: + afterClusterTime: { $$exists: true } lsid: { $$sessionLsid: *session0 } txnNumber: { $numberLong: '2' } startTransaction: { $$exists: false } @@ -175,7 +176,8 @@ tests: - { _id: 4 } - { _id: 5 } ordered: true - readConcern: { $$exists: false } + readConcern: + afterClusterTime: { $$exists: true } lsid: { $$sessionLsid: *session0 } txnNumber: { $numberLong: '4' } startTransaction: { $$exists: false } diff --git a/src/MongoDB.Driver/Core/Operations/ClientBulkWriteOperation.cs b/src/MongoDB.Driver/Core/Operations/ClientBulkWriteOperation.cs index f0a60072608..11ec2380ff6 100644 --- a/src/MongoDB.Driver/Core/Operations/ClientBulkWriteOperation.cs +++ b/src/MongoDB.Driver/Core/Operations/ClientBulkWriteOperation.cs @@ -58,8 +58,9 @@ public ClientBulkWriteOperation( public override string OperationName => "bulkWrite"; - protected override BsonDocument CreateCommand(OperationContext operationContext, ICoreSessionHandle session, long? transactionNumber) + protected override BsonDocument CreateCommand(OperationContext operationContext, ICoreSessionHandle session, ConnectionDescription connectionDescription, long? transactionNumber) { + var readConcern = ReadConcernHelper.GetReadConcernForWriteCommand(session, connectionDescription); var writeConcern = WriteConcernHelper.GetEffectiveWriteConcern(operationContext, session, WriteConcern); return new BsonDocument { @@ -69,6 +70,7 @@ protected override BsonDocument CreateCommand(OperationContext operationContext, { "bypassDocumentValidation", () => _bypassDocumentValidation, _bypassDocumentValidation.HasValue }, { "comment", Comment, Comment != null }, { "let", _let, _let != null }, + { "readConcern", readConcern, readConcern != null }, { "writeConcern", writeConcern, writeConcern != null }, { "txnNumber", () => transactionNumber.Value, transactionNumber.HasValue } }; diff --git a/src/MongoDB.Driver/Core/Operations/CreateCollectionOperation.cs b/src/MongoDB.Driver/Core/Operations/CreateCollectionOperation.cs index 7b666dc369d..552a6414f2d 100644 --- a/src/MongoDB.Driver/Core/Operations/CreateCollectionOperation.cs +++ b/src/MongoDB.Driver/Core/Operations/CreateCollectionOperation.cs @@ -241,6 +241,7 @@ public BsonDocument ClusteredIndex internal BsonDocument CreateCommand(OperationContext operationContext, ICoreSessionHandle session, ConnectionDescription connectionDescription, long? transactionNumber) { var writeConcern = WriteConcernHelper.GetEffectiveWriteConcern(operationContext, session, _writeConcern); + var readConcern = ReadConcernHelper.GetReadConcernForWriteCommand(session, connectionDescription); return new BsonDocument { { "create", _collectionNamespace.CollectionName }, @@ -255,6 +256,7 @@ internal BsonDocument CreateCommand(OperationContext operationContext, ICoreSess { "validationLevel", () => _validationLevel.Value.ToString().ToLowerInvariant(), _validationLevel.HasValue }, { "collation", () => _collation.ToBsonDocument(), _collation != null }, { "comment", _comment, _comment != null }, + { "readConcern", readConcern, readConcern != null }, { "writeConcern", writeConcern, writeConcern != null }, { "expireAfterSeconds", () => _expireAfter.Value.TotalSeconds, _expireAfter.HasValue }, { "timeseries", () => _timeSeriesOptions.ToBsonDocument(), _timeSeriesOptions != null }, diff --git a/src/MongoDB.Driver/Core/Operations/CreateIndexesOperation.cs b/src/MongoDB.Driver/Core/Operations/CreateIndexesOperation.cs index 404a2fbe9a7..f8ed54d16b9 100644 --- a/src/MongoDB.Driver/Core/Operations/CreateIndexesOperation.cs +++ b/src/MongoDB.Driver/Core/Operations/CreateIndexesOperation.cs @@ -173,6 +173,7 @@ internal BsonDocument CreateCommand(OperationContext operationContext, ICoreSess { var maxWireVersion = connectionDescription.MaxWireVersion; var writeConcern = WriteConcernHelper.GetEffectiveWriteConcern(operationContext, session, _writeConcern); + var readConcern = ReadConcernHelper.GetReadConcernForWriteCommand(session, connectionDescription); if (_commitQuorum != null) { Feature.CreateIndexCommitQuorum.ThrowIfNotSupported(maxWireVersion); @@ -183,6 +184,7 @@ internal BsonDocument CreateCommand(OperationContext operationContext, ICoreSess { "createIndexes", _collectionNamespace.CollectionName }, { "indexes", new BsonArray(_requests.Select(request => request.CreateIndexDocument())) }, { "maxTimeMS", () => MaxTimeHelper.ToMaxTimeMS(_maxTime.Value), _maxTime.HasValue && !operationContext.IsRootContextTimeoutConfigured() }, + { "readConcern", readConcern, readConcern != null }, { "writeConcern", writeConcern, writeConcern != null }, { "comment", _comment, _comment != null }, { "commitQuorum", () => _commitQuorum.ToBsonValue(), _commitQuorum != null } diff --git a/src/MongoDB.Driver/Core/Operations/DropCollectionOperation.cs b/src/MongoDB.Driver/Core/Operations/DropCollectionOperation.cs index da9792744c3..52b24b5e994 100644 --- a/src/MongoDB.Driver/Core/Operations/DropCollectionOperation.cs +++ b/src/MongoDB.Driver/Core/Operations/DropCollectionOperation.cs @@ -209,9 +209,11 @@ public async Task ExecuteAttemptAsync(OperationContext operationCo internal BsonDocument CreateCommand(OperationContext operationContext, ICoreSessionHandle session, ConnectionDescription connectionDescription, long? transactionNumber) { var writeConcern = WriteConcernHelper.GetEffectiveWriteConcern(operationContext, session, _writeConcern); + var readConcern = ReadConcernHelper.GetReadConcernForWriteCommand(session, connectionDescription); return new BsonDocument { { "drop", _collectionNamespace.CollectionName }, + { "readConcern", readConcern, readConcern != null }, { "writeConcern", writeConcern, writeConcern != null } }; } diff --git a/src/MongoDB.Driver/Core/Operations/DropDatabaseOperation.cs b/src/MongoDB.Driver/Core/Operations/DropDatabaseOperation.cs index fd91b4438d9..dd70e65e95e 100644 --- a/src/MongoDB.Driver/Core/Operations/DropDatabaseOperation.cs +++ b/src/MongoDB.Driver/Core/Operations/DropDatabaseOperation.cs @@ -13,7 +13,6 @@ * limitations under the License. */ -using System; using System.Threading.Tasks; using MongoDB.Bson; using MongoDB.Bson.Serialization.Serializers; @@ -83,9 +82,11 @@ public bool RetryRequested public BsonDocument CreateCommand(OperationContext operationContext, ICoreSessionHandle session, ConnectionDescription connectionDescription, long? transactionNumber) { var writeConcern = WriteConcernHelper.GetEffectiveWriteConcern(operationContext, session, _writeConcern); + var readConcern = ReadConcernHelper.GetReadConcernForWriteCommand(session, connectionDescription); return new BsonDocument { { "dropDatabase", 1 }, + { "readConcern", readConcern, readConcern != null }, { "writeConcern", writeConcern, writeConcern != null } }; } diff --git a/src/MongoDB.Driver/Core/Operations/DropIndexOperation.cs b/src/MongoDB.Driver/Core/Operations/DropIndexOperation.cs index 07146685c77..69b454e1dc8 100644 --- a/src/MongoDB.Driver/Core/Operations/DropIndexOperation.cs +++ b/src/MongoDB.Driver/Core/Operations/DropIndexOperation.cs @@ -113,11 +113,13 @@ public TimeSpan? MaxTime public BsonDocument CreateCommand(OperationContext operationContext, ICoreSessionHandle session, ConnectionDescription connectionDescription, long? transactionNumber) { var writeConcern = WriteConcernHelper.GetEffectiveWriteConcern(operationContext, session, _writeConcern); + var readConcern = ReadConcernHelper.GetReadConcernForWriteCommand(session, connectionDescription); return new BsonDocument { { "dropIndexes", _collectionNamespace.CollectionName }, { "index", _indexName }, { "maxTimeMS", () => MaxTimeHelper.ToMaxTimeMS(_maxTime.Value), _maxTime.HasValue && !operationContext.IsRootContextTimeoutConfigured() }, + { "readConcern", readConcern, readConcern != null }, { "writeConcern", writeConcern, writeConcern != null }, { "comment", _comment, _comment != null } }; diff --git a/src/MongoDB.Driver/Core/Operations/FindOneAndDeleteOperation.cs b/src/MongoDB.Driver/Core/Operations/FindOneAndDeleteOperation.cs index 0e947ce92a9..6fc6c6c86a2 100644 --- a/src/MongoDB.Driver/Core/Operations/FindOneAndDeleteOperation.cs +++ b/src/MongoDB.Driver/Core/Operations/FindOneAndDeleteOperation.cs @@ -88,6 +88,7 @@ public override BsonDocument CreateCommand(OperationContext operationContext, IC } } + var readConcern = ReadConcernHelper.GetReadConcernForWriteCommand(session, connectionDescription); var writeConcern = WriteConcernHelper.GetEffectiveWriteConcern(operationContext, session, WriteConcern); return new BsonDocument { @@ -97,6 +98,7 @@ public override BsonDocument CreateCommand(OperationContext operationContext, IC { "sort", _sort, _sort != null }, { "fields", _projection, _projection != null }, { "maxTimeMS", () => MaxTimeHelper.ToMaxTimeMS(_maxTime.Value), _maxTime.HasValue && !operationContext.IsRootContextTimeoutConfigured() }, + { "readConcern", readConcern, readConcern != null }, { "writeConcern", writeConcern, writeConcern != null }, { "collation", () => Collation.ToBsonDocument(), Collation != null }, { "comment", Comment, Comment != null }, diff --git a/src/MongoDB.Driver/Core/Operations/FindOneAndReplaceOperation.cs b/src/MongoDB.Driver/Core/Operations/FindOneAndReplaceOperation.cs index 17e1afa52e0..42d930e7a9c 100644 --- a/src/MongoDB.Driver/Core/Operations/FindOneAndReplaceOperation.cs +++ b/src/MongoDB.Driver/Core/Operations/FindOneAndReplaceOperation.cs @@ -117,6 +117,7 @@ public override BsonDocument CreateCommand(OperationContext operationContext, IC } } + var readConcern = ReadConcernHelper.GetReadConcernForWriteCommand(session, connectionDescription); var writeConcern = WriteConcernHelper.GetEffectiveWriteConcern(operationContext, session, WriteConcern); return new BsonDocument { @@ -127,7 +128,8 @@ public override BsonDocument CreateCommand(OperationContext operationContext, IC { "sort", _sort, _sort != null }, { "fields", _projection, _projection != null }, { "upsert", true, _isUpsert }, - { "maxTimeMS", () => MaxTimeHelper.ToMaxTimeMS(_maxTime.Value), _maxTime.HasValue && !operationContext.IsRootContextTimeoutConfigured() }, + { "maxTimeMS", () => MaxTimeHelper.ToMaxTimeMS(_maxTime.Value), _maxTime.HasValue && !operationContext.IsRootContextTimeoutConfigured() }, + { "readConcern", readConcern, readConcern != null }, { "writeConcern", writeConcern, writeConcern != null }, { "bypassDocumentValidation", () => _bypassDocumentValidation.Value, _bypassDocumentValidation.HasValue }, { "collation", () => Collation.ToBsonDocument(), Collation != null }, diff --git a/src/MongoDB.Driver/Core/Operations/FindOneAndUpdateOperation.cs b/src/MongoDB.Driver/Core/Operations/FindOneAndUpdateOperation.cs index 97d4a12b278..9b4c3c37e4a 100644 --- a/src/MongoDB.Driver/Core/Operations/FindOneAndUpdateOperation.cs +++ b/src/MongoDB.Driver/Core/Operations/FindOneAndUpdateOperation.cs @@ -126,6 +126,7 @@ public override BsonDocument CreateCommand(OperationContext operationContext, IC } } + var readConcern = ReadConcernHelper.GetReadConcernForWriteCommand(session, connectionDescription); var writeConcern = WriteConcernHelper.GetEffectiveWriteConcern(operationContext, session, WriteConcern); return new BsonDocument { @@ -137,6 +138,7 @@ public override BsonDocument CreateCommand(OperationContext operationContext, IC { "fields", _projection, _projection != null }, { "upsert", true, _isUpsert }, { "maxTimeMS", () => MaxTimeHelper.ToMaxTimeMS(_maxTime.Value), _maxTime.HasValue && !operationContext.IsRootContextTimeoutConfigured() }, + { "readConcern", readConcern, readConcern != null }, { "writeConcern", writeConcern, writeConcern != null }, { "bypassDocumentValidation", () => _bypassDocumentValidation.Value, _bypassDocumentValidation.HasValue }, { "collation", () => Collation.ToBsonDocument(), Collation != null }, diff --git a/src/MongoDB.Driver/Core/Operations/ReadConcernHelper.cs b/src/MongoDB.Driver/Core/Operations/ReadConcernHelper.cs index e6589242033..f5ea27513e2 100644 --- a/src/MongoDB.Driver/Core/Operations/ReadConcernHelper.cs +++ b/src/MongoDB.Driver/Core/Operations/ReadConcernHelper.cs @@ -27,6 +27,22 @@ public static BsonDocument GetReadConcernForCommand(ICoreSession session, Connec return (session.IsInTransaction || session.IsSnapshot) ? null : ToBsonDocument(session, connectionDescription, readConcern); } + // Write commands do not support readConcern.level; only afterClusterTime is allowed for causal consistency. + public static BsonDocument GetReadConcernForWriteCommand(ICoreSession session, ConnectionDescription connectionDescription) + { + if (session.IsInTransaction) + { + return null; + } + + if (AreSessionsSupported(connectionDescription) && session.IsCausallyConsistent && session.OperationTime != null) + { + return new BsonDocument("afterClusterTime", session.OperationTime); + } + + return null; + } + public static BsonDocument GetReadConcernForFirstCommandInTransaction(ICoreSession session, ConnectionDescription connectionDescription) { var readConcern = session.CurrentTransaction.TransactionOptions.ReadConcern; diff --git a/src/MongoDB.Driver/Core/Operations/RetryableDeleteCommandOperation.cs b/src/MongoDB.Driver/Core/Operations/RetryableDeleteCommandOperation.cs index 2ee8aa9fb4d..4259df28bb5 100644 --- a/src/MongoDB.Driver/Core/Operations/RetryableDeleteCommandOperation.cs +++ b/src/MongoDB.Driver/Core/Operations/RetryableDeleteCommandOperation.cs @@ -21,6 +21,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Core.Bindings; +using MongoDB.Driver.Core.Connections; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Core.WireProtocol.Messages; using MongoDB.Driver.Core.WireProtocol.Messages.Encoders; @@ -62,7 +63,7 @@ public BatchableSource Deletes get { return _deletes; } } - protected override BsonDocument CreateCommand(OperationContext operationContext, ICoreSessionHandle session, long? transactionNumber) + protected override BsonDocument CreateCommand(OperationContext operationContext, ICoreSessionHandle session, ConnectionDescription connectionDescription, long? transactionNumber) { if (WriteConcern != null && !WriteConcern.IsAcknowledged) { @@ -73,11 +74,13 @@ protected override BsonDocument CreateCommand(OperationContext operationContext, } var writeConcern = WriteConcernHelper.GetEffectiveWriteConcern(operationContext, session, WriteConcern); + var readConcern = ReadConcernHelper.GetReadConcernForWriteCommand(session, connectionDescription); return new BsonDocument { { "delete", _collectionNamespace.CollectionName }, { "ordered", IsOrdered }, { "comment", Comment, Comment != null }, + { "readConcern", readConcern, readConcern != null }, { "writeConcern", writeConcern, writeConcern != null }, { "txnNumber", () => transactionNumber.Value, transactionNumber.HasValue }, { "let", _let, _let != null } diff --git a/src/MongoDB.Driver/Core/Operations/RetryableInsertCommandOperation.cs b/src/MongoDB.Driver/Core/Operations/RetryableInsertCommandOperation.cs index 15973a027c5..203dc65fa42 100644 --- a/src/MongoDB.Driver/Core/Operations/RetryableInsertCommandOperation.cs +++ b/src/MongoDB.Driver/Core/Operations/RetryableInsertCommandOperation.cs @@ -18,8 +18,8 @@ using MongoDB.Bson; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; -using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Core.Bindings; +using MongoDB.Driver.Core.Connections; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Core.WireProtocol.Messages; using MongoDB.Driver.Core.WireProtocol.Messages.Encoders; @@ -68,15 +68,17 @@ public IBsonSerializer DocumentSerializer get { return _documentSerializer; } } - protected override BsonDocument CreateCommand(OperationContext operationContext, ICoreSessionHandle session, long? transactionNumber) + protected override BsonDocument CreateCommand(OperationContext operationContext, ICoreSessionHandle session, ConnectionDescription connectionDescription, long? transactionNumber) { var writeConcern = WriteConcernHelper.GetEffectiveWriteConcern(operationContext, session, WriteConcern); + var readConcern = ReadConcernHelper.GetReadConcernForWriteCommand(session, connectionDescription); return new BsonDocument { { "insert", _collectionNamespace.CollectionName }, { "ordered", IsOrdered }, { "bypassDocumentValidation", () => _bypassDocumentValidation, _bypassDocumentValidation.HasValue }, { "comment", Comment, Comment != null }, + { "readConcern", readConcern, readConcern != null }, { "writeConcern", writeConcern, writeConcern != null }, { "txnNumber", () => transactionNumber.Value, transactionNumber.HasValue } }; @@ -100,54 +102,5 @@ protected override IEnumerable CreateCommandPayl var payload = new Type1CommandMessageSection("documents", documents, _documentSerializer, elementNameValidator, maxBatchCount, maxDocumentSize); return new Type1CommandMessageSection[] { payload }; } - - // nested types - private class InsertSerializer : SerializerBase - { - // private fields - private IBsonSerializer _cachedSerializer; - private readonly IBsonSerializer _documentSerializer; - - // constructors - public InsertSerializer(IBsonSerializer documentSerializer) - { - _documentSerializer = documentSerializer; - _cachedSerializer = documentSerializer; - } - - // public methods - public override bool Equals(object obj) - { - if (object.ReferenceEquals(obj, null)) { return false; } - if (object.ReferenceEquals(this, obj)) { return true; } - return - base.Equals(obj) && - obj is InsertSerializer other && - object.Equals(_documentSerializer, other._documentSerializer); - } - - public override int GetHashCode() => 0; - - public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TDocument value) - { - IBsonSerializer serializer; - - var actualType = value.GetType(); - if (actualType == typeof(TDocument)) - { - serializer = _documentSerializer; - } - else - { - if (_cachedSerializer.ValueType != actualType) - { - _cachedSerializer = BsonSerializer.LookupSerializer(actualType); - } - serializer = _cachedSerializer; - } - - serializer.Serialize(context, value); - } - } } } diff --git a/src/MongoDB.Driver/Core/Operations/RetryableUpdateCommandOperation.cs b/src/MongoDB.Driver/Core/Operations/RetryableUpdateCommandOperation.cs index 40f25de3b03..691a06de162 100644 --- a/src/MongoDB.Driver/Core/Operations/RetryableUpdateCommandOperation.cs +++ b/src/MongoDB.Driver/Core/Operations/RetryableUpdateCommandOperation.cs @@ -21,6 +21,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Core.Bindings; +using MongoDB.Driver.Core.Connections; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Core.Operations.ElementNameValidators; using MongoDB.Driver.Core.WireProtocol.Messages; @@ -69,7 +70,7 @@ public BatchableSource Updates get { return _updates; } } - protected override BsonDocument CreateCommand(OperationContext operationContext, ICoreSessionHandle session, long? transactionNumber) + protected override BsonDocument CreateCommand(OperationContext operationContext, ICoreSessionHandle session, ConnectionDescription connectionDescription, long? transactionNumber) { if (WriteConcern != null && !WriteConcern.IsAcknowledged) { @@ -80,12 +81,14 @@ protected override BsonDocument CreateCommand(OperationContext operationContext, } var writeConcern = WriteConcernHelper.GetEffectiveWriteConcern(operationContext, session, WriteConcern); + var readConcern = ReadConcernHelper.GetReadConcernForWriteCommand(session, connectionDescription); return new BsonDocument { { "update", _collectionNamespace.CollectionName }, { "ordered", IsOrdered }, { "bypassDocumentValidation", () => _bypassDocumentValidation.Value, _bypassDocumentValidation.HasValue }, { "comment", Comment, Comment != null }, + { "readConcern", readConcern, readConcern != null }, { "writeConcern", writeConcern, writeConcern != null }, { "txnNumber", () => transactionNumber.Value, transactionNumber.HasValue }, { "let", _let, _let != null } diff --git a/src/MongoDB.Driver/Core/Operations/RetryableWriteCommandOperationBase.cs b/src/MongoDB.Driver/Core/Operations/RetryableWriteCommandOperationBase.cs index c44b7af7cb5..1145c3c0575 100644 --- a/src/MongoDB.Driver/Core/Operations/RetryableWriteCommandOperationBase.cs +++ b/src/MongoDB.Driver/Core/Operations/RetryableWriteCommandOperationBase.cs @@ -21,6 +21,7 @@ using MongoDB.Bson.IO; using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Core.Bindings; +using MongoDB.Driver.Core.Connections; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Core.WireProtocol; using MongoDB.Driver.Core.WireProtocol.Messages; @@ -171,7 +172,7 @@ public Task ExecuteAttemptAsync(OperationContext operationContext, args.MessageEncoderSettings); } - protected abstract BsonDocument CreateCommand(OperationContext operationContext, ICoreSessionHandle session, long? transactionNumber); + protected abstract BsonDocument CreateCommand(OperationContext operationContext, ICoreSessionHandle session, ConnectionDescription connectionDescription, long? transactionNumber); protected abstract IEnumerable CreateCommandPayloads(IChannelHandle channel, int attempt); @@ -187,7 +188,7 @@ private MessageEncoderSettings CreateMessageEncoderSettings(IChannelHandle chann private CommandArgs GetCommandArgs(OperationContext operationContext, RetryableWriteContext context, int attempt, long? transactionNumber) { var args = new CommandArgs(); - args.Command = CreateCommand(operationContext, context.Binding.Session, transactionNumber); + args.Command = CreateCommand(operationContext, context.Binding.Session, context.Channel.ConnectionDescription, transactionNumber); args.CommandPayloads = CreateCommandPayloads(context.Channel, attempt).ToList(); args.PostWriteAction = GetPostWriteAction(args.CommandPayloads); args.ResponseHandling = GetResponseHandling(); diff --git a/tests/MongoDB.Driver.Tests/Core/Operations/ReadConcernHelperTests.cs b/tests/MongoDB.Driver.Tests/Core/Operations/ReadConcernHelperTests.cs index 1ba64bdbdfb..c5d83774021 100644 --- a/tests/MongoDB.Driver.Tests/Core/Operations/ReadConcernHelperTests.cs +++ b/tests/MongoDB.Driver.Tests/Core/Operations/ReadConcernHelperTests.cs @@ -139,6 +139,32 @@ public void GetReadConcernForSnapshotSession_should_return_expected_result( result.Should().Be(expectedResult); } + [Theory] + [InlineData(false, false, null, null)] + [InlineData(false, false, 1234, null)] + [InlineData(false, true, null, null)] + [InlineData(false, true, 1234, "{ afterClusterTime : Timestamp(0, 1234) }")] + [InlineData(true, false, null, null)] + [InlineData(true, false, 1234, null)] + [InlineData(true, true, null, null)] + [InlineData(true, true, 1234, null)] + public void GetReadConcernForWriteCommand_should_return_expected_result( + bool isInTransaction, + bool isCausallyConsistent, + int? operationTime, + string expectedResult) + { + var session = CreateSession( + isInTransaction: isInTransaction, + isCausallyConsistent: isCausallyConsistent, + operationTime: operationTime.HasValue ? new BsonTimestamp(operationTime.Value) : null); + var connectionDescription = CreateConnectionDescription(logicalSessionTimeoutMinutes: true); + + var result = ReadConcernHelper.GetReadConcernForWriteCommand(session, connectionDescription); + + result.Should().Be(expectedResult); + } + // private methods private ConnectionDescription CreateConnectionDescription( bool logicalSessionTimeoutMinutes = false, diff --git a/tests/MongoDB.Driver.Tests/Specifications/UnifiedTestSpecRunner.cs b/tests/MongoDB.Driver.Tests/Specifications/UnifiedTestSpecRunner.cs index df93f63c2b8..9b28ae3555b 100644 --- a/tests/MongoDB.Driver.Tests/Specifications/UnifiedTestSpecRunner.cs +++ b/tests/MongoDB.Driver.Tests/Specifications/UnifiedTestSpecRunner.cs @@ -44,6 +44,9 @@ public UnifiedTestSpecRunner(ITestOutputHelper testOutputHelper) [UnifiedTestsTheory("auth.tests.unified")] public void Auth(JsonDrivenTestCase testCase) => Run(testCase); + [UnifiedTestsTheory("causal_consistency.tests")] + public void CausalConsistency(JsonDrivenTestCase testCase) => Run(testCase); + [Category("SupportLoadBalancing")] [UnifiedTestsTheory("change_streams.tests.unified")] public void ChangeStreams(JsonDrivenTestCase testCase) => Run(testCase); diff --git a/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedDropCollectionOperation.cs b/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedDropCollectionOperation.cs index 9993c171d57..72d5694b652 100644 --- a/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedDropCollectionOperation.cs +++ b/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedDropCollectionOperation.cs @@ -24,11 +24,14 @@ public class UnifiedDropCollectionOperation : IUnifiedEntityTestOperation { private readonly string _collectionName; private readonly IMongoDatabase _database; + private readonly IClientSessionHandle _session; public UnifiedDropCollectionOperation( + IClientSessionHandle session, IMongoDatabase database, string collectionName) { + _session = session; _database = database; _collectionName = collectionName; } @@ -37,7 +40,14 @@ public OperationResult Execute(CancellationToken cancellationToken) { try { - _database.DropCollection(_collectionName); + if (_session == null) + { + _database.DropCollection(_collectionName); + } + else + { + _database.DropCollection(_session, _collectionName); + } return OperationResult.FromResult(null); } @@ -51,7 +61,14 @@ public async Task ExecuteAsync(CancellationToken cancellationTo { try { - await _database.DropCollectionAsync(_collectionName); + if (_session == null) + { + await _database.DropCollectionAsync(_collectionName, cancellationToken); + } + else + { + await _database.DropCollectionAsync(_session, _collectionName, cancellationToken); + } return OperationResult.FromResult(null); } @@ -76,6 +93,7 @@ public UnifiedDropCollectionOperation Build(string targetDatabaseId, BsonDocumen var database = _entityMap.Databases[targetDatabaseId]; string collectionName = null; + IClientSessionHandle session = null; foreach (var argument in arguments) { @@ -84,12 +102,15 @@ public UnifiedDropCollectionOperation Build(string targetDatabaseId, BsonDocumen case "collection": collectionName = argument.Value.AsString; break; + case "session": + session = _entityMap.Sessions[argument.Value.AsString]; + break; default: throw new FormatException($"Invalid DropCollectionOperation argument name: '{argument.Name}'."); } } - return new UnifiedDropCollectionOperation(database, collectionName); + return new UnifiedDropCollectionOperation(session, database, collectionName); } } } diff --git a/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedDropDatabaseOperation.cs b/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedDropDatabaseOperation.cs new file mode 100644 index 00000000000..c8d5a17423a --- /dev/null +++ b/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedDropDatabaseOperation.cs @@ -0,0 +1,115 @@ +/* Copyright 2010-present MongoDB Inc. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Bson; + +namespace MongoDB.Driver.Tests.UnifiedTestOperations; + +public class UnifiedDropDatabaseOperation : IUnifiedEntityTestOperation +{ + private readonly IMongoClient _client; + private readonly string _databaseName; + private readonly IClientSessionHandle _session; + + public UnifiedDropDatabaseOperation( + IMongoClient client, + IClientSessionHandle session, + string databaseName) + { + _client = client; + _session = session; + _databaseName = databaseName; + } + + public OperationResult Execute(CancellationToken cancellationToken) + { + try + { + if (_session == null) + { + _client.DropDatabase(_databaseName, cancellationToken); + } + else + { + _client.DropDatabase(_session, _databaseName, cancellationToken); + } + + return OperationResult.FromResult(null); + } + catch (Exception exception) + { + return OperationResult.FromException(exception); + } + } + + public async Task ExecuteAsync(CancellationToken cancellationToken) + { + try + { + if (_session == null) + { + await _client.DropDatabaseAsync(_databaseName, cancellationToken); + } + else + { + await _client.DropDatabaseAsync(_session, _databaseName, cancellationToken); + } + + return OperationResult.FromResult(null); + } + catch (Exception exception) + { + return OperationResult.FromException(exception); + } + } +} + +public class UnifiedDropDatabaseOperationBuilder +{ + private readonly UnifiedEntityMap _entityMap; + + public UnifiedDropDatabaseOperationBuilder(UnifiedEntityMap entityMap) + { + _entityMap = entityMap; + } + + public UnifiedDropDatabaseOperation Build(string targetClientId, BsonDocument arguments) + { + var client = _entityMap.Clients[targetClientId]; + + string databaseName = null; + IClientSessionHandle session = null; + + foreach (var argument in arguments) + { + switch (argument.Name) + { + case "database": + databaseName = argument.Value.AsString; + break; + case "session": + session = _entityMap.Sessions[argument.Value.AsString]; + break; + default: + throw new FormatException($"Invalid DropCollectionOperation argument name: '{argument.Name}'."); + } + } + + return new UnifiedDropDatabaseOperation(client, session, databaseName); + } +} diff --git a/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedTestOperationFactory.cs b/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedTestOperationFactory.cs index 3040bd4f0f6..9a943f88dbb 100644 --- a/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedTestOperationFactory.cs +++ b/tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedTestOperationFactory.cs @@ -80,6 +80,7 @@ public IUnifiedTestOperation CreateOperation(string operationName, string target "clientBulkWrite" => new UnifiedClientBulkWriteOperationBuilder(_entityMap).Build(targetEntityId, operationArguments), "close" => new UnifiedCloseClientOperationBuilder(_entityMap).Build(targetEntityId, operationArguments), "createChangeStream" => new UnifiedCreateChangeStreamOnClientOperationBuilder(_entityMap).Build(targetEntityId, operationArguments), + "dropDatabase" => new UnifiedDropDatabaseOperationBuilder(_entityMap).Build(targetEntityId, operationArguments), "listDatabases" => new UnifiedListDatabasesOperationBuilder(_entityMap).Build(targetEntityId, operationArguments), "listDatabaseNames" => new UnifiedListDatabaseNamesOperationBuilder(_entityMap).Build(targetEntityId, operationArguments), _ => throw new FormatException($"Invalid method name: '{operationName}'."),