From 2d56e0e7d21d32007b5915bd65a79562def61126 Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Wed, 11 Sep 2024 18:04:10 -0400 Subject: [PATCH 01/14] GODRIVER-2388 Improved Bulk Write API. --- .../unacknowledgedBulkWrite.json | 171 +- .../unacknowledgedBulkWrite.yml | 95 +- .../client-bulkWrite-delete-options.json | 268 + .../client-bulkWrite-delete-options.yml | 137 + .../client-bulkWrite-errorResponse.json | 69 + .../client-bulkWrite-errorResponse.yml | 38 + .../crud/unified/client-bulkWrite-errors.json | 455 ++ .../crud/unified/client-bulkWrite-errors.yml | 241 + .../client-bulkWrite-mixed-namespaces.json | 315 + .../client-bulkWrite-mixed-namespaces.yml | 147 + .../unified/client-bulkWrite-options.json | 716 +++ .../crud/unified/client-bulkWrite-options.yml | 351 ++ .../unified/client-bulkWrite-ordered.json | 291 + .../crud/unified/client-bulkWrite-ordered.yml | 153 + .../unified/client-bulkWrite-results.json | 833 +++ .../crud/unified/client-bulkWrite-results.yml | 312 + .../client-bulkWrite-update-options.json | 949 +++ .../client-bulkWrite-update-options.yml | 338 + .../client-bulkWrite-update-pipeline.json | 258 + .../client-bulkWrite-update-pipeline.yml | 133 + .../client-bulkWrite-update-validation.json | 216 + .../client-bulkWrite-update-validation.yml | 79 + .../client-bulkWrite-clientErrors.json | 351 ++ .../unified/client-bulkWrite-clientErrors.yml | 173 + .../client-bulkWrite-serverErrors.json | 873 +++ .../unified/client-bulkWrite-serverErrors.yml | 413 ++ .../unified/handshakeError.json | 2015 ++++++ .../unified/handshakeError.yml | 877 +++ .../logging/operation-id.json | 189 + .../server-selection/logging/operation-id.yml | 101 +- .../unified/client-bulkWrite.json | 593 ++ .../transactions/unified/client-bulkWrite.yml | 263 + .../transactions/unified/mongos-pin-auto.json | 5474 +++++++++++++++++ .../transactions/unified/mongos-pin-auto.yml | 1705 +++++ 34 files changed, 19541 insertions(+), 51 deletions(-) create mode 100644 testdata/crud/unified/client-bulkWrite-delete-options.json create mode 100644 testdata/crud/unified/client-bulkWrite-delete-options.yml create mode 100644 testdata/crud/unified/client-bulkWrite-errorResponse.json create mode 100644 testdata/crud/unified/client-bulkWrite-errorResponse.yml create mode 100644 testdata/crud/unified/client-bulkWrite-errors.json create mode 100644 testdata/crud/unified/client-bulkWrite-errors.yml create mode 100644 testdata/crud/unified/client-bulkWrite-mixed-namespaces.json create mode 100644 testdata/crud/unified/client-bulkWrite-mixed-namespaces.yml create mode 100644 testdata/crud/unified/client-bulkWrite-options.json create mode 100644 testdata/crud/unified/client-bulkWrite-options.yml create mode 100644 testdata/crud/unified/client-bulkWrite-ordered.json create mode 100644 testdata/crud/unified/client-bulkWrite-ordered.yml create mode 100644 testdata/crud/unified/client-bulkWrite-results.json create mode 100644 testdata/crud/unified/client-bulkWrite-results.yml create mode 100644 testdata/crud/unified/client-bulkWrite-update-options.json create mode 100644 testdata/crud/unified/client-bulkWrite-update-options.yml create mode 100644 testdata/crud/unified/client-bulkWrite-update-pipeline.json create mode 100644 testdata/crud/unified/client-bulkWrite-update-pipeline.yml create mode 100644 testdata/crud/unified/client-bulkWrite-update-validation.json create mode 100644 testdata/crud/unified/client-bulkWrite-update-validation.yml create mode 100644 testdata/retryable-writes/unified/client-bulkWrite-clientErrors.json create mode 100644 testdata/retryable-writes/unified/client-bulkWrite-clientErrors.yml create mode 100644 testdata/retryable-writes/unified/client-bulkWrite-serverErrors.json create mode 100644 testdata/retryable-writes/unified/client-bulkWrite-serverErrors.yml create mode 100644 testdata/retryable-writes/unified/handshakeError.json create mode 100644 testdata/retryable-writes/unified/handshakeError.yml create mode 100644 testdata/transactions/unified/client-bulkWrite.json create mode 100644 testdata/transactions/unified/client-bulkWrite.yml create mode 100644 testdata/transactions/unified/mongos-pin-auto.json create mode 100644 testdata/transactions/unified/mongos-pin-auto.yml diff --git a/testdata/command-monitoring/unacknowledgedBulkWrite.json b/testdata/command-monitoring/unacknowledgedBulkWrite.json index 4c16d6df11..b30e1540f4 100644 --- a/testdata/command-monitoring/unacknowledgedBulkWrite.json +++ b/testdata/command-monitoring/unacknowledgedBulkWrite.json @@ -1,15 +1,25 @@ { - "description": "unacknowledgedBulkWrite", - "schemaVersion": "1.0", + "description": "unacknowledged-client-bulkWrite", + "schemaVersion": "1.7", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], "createEntities": [ { "client": { "id": "client", + "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent", "commandSucceededEvent", "commandFailedEvent" - ] + ], + "uriOptions": { + "w": 0 + } } }, { @@ -23,12 +33,7 @@ "collection": { "id": "collection", "database": "database", - "collectionName": "test", - "collectionOptions": { - "writeConcern": { - "w": 0 - } - } + "collectionName": "test" } } ], @@ -40,64 +45,170 @@ { "_id": 1, "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 } ] } ], + "_yamlAnchors": { + "namespace": "command-monitoring-tests.test" + }, "tests": [ { - "description": "A successful unordered bulk write with an unacknowledged write concern", + "description": "A successful mixed client bulkWrite", "operations": [ { - "name": "bulkWrite", - "object": "collection", + "object": "client", + "name": "clientBulkWrite", "arguments": { - "requests": [ + "models": [ { "insertOne": { + "namespace": "command-monitoring-tests.test", "document": { - "_id": "unorderedBulkWriteInsertW0", + "_id": 4, "x": 44 } } + }, + { + "updateOne": { + "namespace": "command-monitoring-tests.test", + "filter": { + "_id": 3 + }, + "update": { + "$set": { + "x": 333 + } + } + } } - ], - "ordered": false + ] + }, + "expectResult": { + "insertedCount": { + "$$unsetOrMatches": 0 + }, + "upsertedCount": { + "$$unsetOrMatches": 0 + }, + "matchedCount": { + "$$unsetOrMatches": 0 + }, + "modifiedCount": { + "$$unsetOrMatches": 0 + }, + "deletedCount": { + "$$unsetOrMatches": 0 + }, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } } + }, + { + "object": "collection", + "name": "find", + "arguments": { + "filter": {} + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 333 + }, + { + "_id": 4, + "x": 44 + } + ] } ], "expectEvents": [ { "client": "client", + "ignoreExtraEvents": true, "events": [ { "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", "command": { - "insert": "test", - "documents": [ + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ { - "_id": "unorderedBulkWriteInsertW0", - "x": 44 + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + }, + { + "update": 0, + "filter": { + "_id": 3 + }, + "updateMods": { + "$set": { + "x": 333 + } + }, + "multi": false } ], - "ordered": false, - "writeConcern": { - "w": 0 - } - }, - "commandName": "insert", - "databaseName": "command-monitoring-tests" + "nsInfo": [ + { + "ns": "command-monitoring-tests.test" + } + ] + } } }, { "commandSucceededEvent": { + "commandName": "bulkWrite", "reply": { "ok": 1, - "n": { + "nInserted": { + "$$exists": false + }, + "nMatched": { + "$$exists": false + }, + "nModified": { + "$$exists": false + }, + "nUpserted": { + "$$exists": false + }, + "nDeleted": { "$$exists": false } - }, - "commandName": "insert" + } } } ] diff --git a/testdata/command-monitoring/unacknowledgedBulkWrite.yml b/testdata/command-monitoring/unacknowledgedBulkWrite.yml index d7c8ce0d0c..35b8d556fb 100644 --- a/testdata/command-monitoring/unacknowledgedBulkWrite.yml +++ b/testdata/command-monitoring/unacknowledgedBulkWrite.yml @@ -1,14 +1,21 @@ -description: "unacknowledgedBulkWrite" +description: "unacknowledged-client-bulkWrite" -schemaVersion: "1.0" +schemaVersion: "1.7" + +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid createEntities: - client: id: &client client + useMultipleMongoses: false observeEvents: - commandStartedEvent - commandSucceededEvent - commandFailedEvent + uriOptions: + w: 0 - database: id: &database database client: *client @@ -17,39 +24,87 @@ createEntities: id: &collection collection database: *database collectionName: &collectionName test - collectionOptions: - writeConcern: { w: 0 } initialData: - collectionName: *collectionName databaseName: *databaseName documents: - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "command-monitoring-tests.test" tests: - - description: "A successful unordered bulk write with an unacknowledged write concern" + - description: 'A successful mixed client bulkWrite' operations: - - name: bulkWrite - object: *collection + - object: *client + name: clientBulkWrite arguments: - requests: + models: - insertOne: - document: { _id: "unorderedBulkWriteInsertW0", x: 44 } - ordered: false + namespace: *namespace + document: { _id: 4, x: 44 } + - updateOne: + namespace: *namespace + filter: { _id: 3 } + update: { $set: { x: 333 } } + expectResult: + insertedCount: + $$unsetOrMatches: 0 + upsertedCount: + $$unsetOrMatches: 0 + matchedCount: + $$unsetOrMatches: 0 + modifiedCount: + $$unsetOrMatches: 0 + deletedCount: + $$unsetOrMatches: 0 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} + # Force completion of the w:0 write by executing a find on the same connection + - object: *collection + name: find + arguments: + filter: {} + expectResult: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 333 } + - { _id: 4, x: 44 } + expectEvents: - - client: *client + - + client: *client + ignoreExtraEvents: true events: - commandStartedEvent: + commandName: bulkWrite + databaseName: admin command: - insert: *collectionName - documents: - - { _id: "unorderedBulkWriteInsertW0", x: 44 } - ordered: false - writeConcern: { w: 0 } - commandName: insert - databaseName: *databaseName + bulkWrite: 1 + errorsOnly: true + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + - update: 0 + filter: { _id: 3 } + updateMods: { $set: { x: 333 } } + multi: false + nsInfo: + - ns: *namespace - commandSucceededEvent: + commandName: bulkWrite reply: ok: 1 - n: { $$exists: false } - commandName: insert + nInserted: { $$exists: false } + nMatched: { $$exists: false } + nModified: { $$exists: false } + nUpserted: { $$exists: false } + nDeleted: { $$exists: false } diff --git a/testdata/crud/unified/client-bulkWrite-delete-options.json b/testdata/crud/unified/client-bulkWrite-delete-options.json new file mode 100644 index 0000000000..d9987897dc --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-delete-options.json @@ -0,0 +1,268 @@ +{ + "description": "client bulkWrite delete options", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0", + "collation": { + "locale": "simple" + }, + "hint": "_id_" + }, + "tests": [ + { + "description": "client bulk write delete with collation", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "collation": { + "locale": "simple" + } + } + }, + { + "deleteMany": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": { + "$gt": 1 + } + }, + "collation": { + "locale": "simple" + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 3, + "insertResults": {}, + "updateResults": {}, + "deleteResults": { + "0": { + "deletedCount": 1 + }, + "1": { + "deletedCount": 2 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "delete": 0, + "filter": { + "_id": 1 + }, + "collation": { + "locale": "simple" + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": { + "$gt": 1 + } + }, + "collation": { + "locale": "simple" + }, + "multi": true + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [] + } + ] + }, + { + "description": "client bulk write delete with hint", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "hint": "_id_" + } + }, + { + "deleteMany": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": { + "$gt": 1 + } + }, + "hint": "_id_" + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 3, + "insertResults": {}, + "updateResults": {}, + "deleteResults": { + "0": { + "deletedCount": 1 + }, + "1": { + "deletedCount": 2 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "delete": 0, + "filter": { + "_id": 1 + }, + "hint": "_id_", + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": { + "$gt": 1 + } + }, + "hint": "_id_", + "multi": true + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [] + } + ] + } + ] +} diff --git a/testdata/crud/unified/client-bulkWrite-delete-options.yml b/testdata/crud/unified/client-bulkWrite-delete-options.yml new file mode 100644 index 0000000000..9297838535 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-delete-options.yml @@ -0,0 +1,137 @@ +description: "client bulkWrite delete options" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + collation: &collation { "locale": "simple" } + hint: &hint _id_ + +tests: + - description: "client bulk write delete with collation" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - deleteOne: + namespace: *namespace + filter: { _id: 1 } + collation: *collation + - deleteMany: + namespace: *namespace + filter: { _id: { $gt: 1 } } + collation: *collation + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 3 + insertResults: {} + updateResults: {} + deleteResults: + 0: + deletedCount: 1 + 1: + deletedCount: 2 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - delete: 0 + filter: { _id: 1 } + collation: *collation + multi: false + - delete: 0 + filter: { _id: { $gt: 1 } } + collation: *collation + multi: true + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: [] + - description: "client bulk write delete with hint" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - deleteOne: + namespace: *namespace + filter: { _id: 1 } + hint: *hint + - deleteMany: + namespace: *namespace + filter: { _id: { $gt: 1 } } + hint: *hint + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 3 + insertResults: {} + updateResults: {} + deleteResults: + 0: + deletedCount: 1 + 1: + deletedCount: 2 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - delete: 0 + filter: { _id: 1 } + hint: *hint + multi: false + - delete: 0 + filter: { _id: { $gt: 1 } } + hint: *hint + multi: true + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: [] diff --git a/testdata/crud/unified/client-bulkWrite-errorResponse.json b/testdata/crud/unified/client-bulkWrite-errorResponse.json new file mode 100644 index 0000000000..b828aad3b9 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-errorResponse.json @@ -0,0 +1,69 @@ +{ + "description": "client bulkWrite errorResponse", + "schemaVersion": "1.12", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite operations support errorResponse assertions", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 8 + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorCode": 8, + "errorResponse": { + "code": 8 + } + } + } + ] + } + ] +} diff --git a/testdata/crud/unified/client-bulkWrite-errorResponse.yml b/testdata/crud/unified/client-bulkWrite-errorResponse.yml new file mode 100644 index 0000000000..d63010afc7 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-errorResponse.yml @@ -0,0 +1,38 @@ +description: "client bulkWrite errorResponse" +schemaVersion: "1.12" +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false # Avoid setting fail points with multiple mongoses + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + +tests: + - description: "client bulkWrite operations support errorResponse assertions" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ bulkWrite ] + errorCode: &errorCode 8 # UnknownError + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 1 } + expectError: + errorCode: *errorCode + errorResponse: + code: *errorCode diff --git a/testdata/crud/unified/client-bulkWrite-errors.json b/testdata/crud/unified/client-bulkWrite-errors.json new file mode 100644 index 0000000000..8cc45bb5f2 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-errors.json @@ -0,0 +1,455 @@ +{ + "description": "client bulkWrite errors", + "schemaVersion": "1.21", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ], + "uriOptions": { + "retryWrites": false + }, + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0", + "writeConcernErrorCode": 91, + "writeConcernErrorMessage": "Replication is being shut down", + "undefinedVarCode": 17276 + }, + "tests": [ + { + "description": "an individual operation fails during an ordered bulkWrite", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + } + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id2" + ] + } + } + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 3 + } + } + } + ], + "verboseResults": true + }, + "expectError": { + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 1, + "insertResults": {}, + "updateResults": {}, + "deleteResults": { + "0": { + "deletedCount": 1 + } + } + }, + "writeErrors": { + "1": { + "code": 17276 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "an individual operation fails during an unordered bulkWrite", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + } + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id2" + ] + } + } + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 3 + } + } + } + ], + "verboseResults": true, + "ordered": false + }, + "expectError": { + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 2, + "insertResults": {}, + "updateResults": {}, + "deleteResults": { + "0": { + "deletedCount": 1 + }, + "2": { + "deletedCount": 1 + } + } + }, + "writeErrors": { + "1": { + "code": 17276 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "detailed results are omitted from error when verboseResults is false", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + } + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id2" + ] + } + } + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 3 + } + } + } + ], + "verboseResults": false + }, + "expectError": { + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 1, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + }, + "writeErrors": { + "1": { + "code": 17276 + } + } + } + } + ] + }, + { + "description": "a top-level failure occurs during a bulkWrite", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 8 + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "x": 1 + } + } + } + ], + "verboseResults": true + }, + "expectError": { + "errorCode": 8 + } + } + ] + }, + { + "description": "a bulk write with only errors does not report a partial result", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id2" + ] + } + } + } + } + ], + "verboseResults": true + }, + "expectError": { + "expectResult": { + "$$unsetOrMatches": {} + }, + "writeErrors": { + "0": { + "code": 17276 + } + } + } + } + ] + }, + { + "description": "a write concern error occurs during a bulkWrite", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 10 + } + } + } + ], + "verboseResults": true + }, + "expectError": { + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 10 + } + }, + "updateResults": {}, + "deleteResults": {} + }, + "writeConcernErrors": [ + { + "code": 91, + "message": "Replication is being shut down" + } + ] + } + } + ] + }, + { + "description": "an empty list of write models is a client-side error", + "operations": [ + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "models": [], + "verboseResults": true + }, + "expectError": { + "isClientError": true + } + } + ] + } + ] +} diff --git a/testdata/crud/unified/client-bulkWrite-errors.yml b/testdata/crud/unified/client-bulkWrite-errors.yml new file mode 100644 index 0000000000..6c513006ce --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-errors.yml @@ -0,0 +1,241 @@ +description: "client bulkWrite errors" +schemaVersion: "1.21" +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + uriOptions: + retryWrites: false + useMultipleMongoses: false # Target a single mongos with failpoint + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + writeConcernErrorCode: &writeConcernErrorCode 91 + writeConcernErrorMessage: &writeConcernErrorMessage "Replication is being shut down" + undefinedVarCode: &undefinedVarCode 17276 # Use of an undefined variable + +tests: + - description: "an individual operation fails during an ordered bulkWrite" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - deleteOne: + namespace: *namespace + filter: { _id: 1 } + - deleteOne: + namespace: *namespace + filter: + $expr: + $eq: [ "$_id", "$$id2" ] # Attempt to access a nonexistent let var + - deleteOne: + namespace: *namespace + filter: { _id: 3 } + verboseResults: true + expectError: + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 1 + insertResults: {} + updateResults: {} + deleteResults: + 0: + deletedCount: 1 + writeErrors: + 1: + code: *undefinedVarCode + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - description: "an individual operation fails during an unordered bulkWrite" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - deleteOne: + namespace: *namespace + filter: { _id: 1 } + - deleteOne: + namespace: *namespace + filter: + $expr: + $eq: [ "$_id", "$$id2" ] # Attempt to access a nonexistent let var + - deleteOne: + namespace: *namespace + filter: { _id: 3 } + verboseResults: true + ordered: false + expectError: + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 2 + insertResults: {} + updateResults: {} + deleteResults: + 0: + deletedCount: 1 + 2: + deletedCount: 1 + writeErrors: + 1: + code: *undefinedVarCode + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 2, x: 22 } + - description: "detailed results are omitted from error when verboseResults is false" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - deleteOne: + namespace: *namespace + filter: { _id: 1 } + - deleteOne: + namespace: *namespace + filter: + $expr: + $eq: [ "$_id", "$$id2" ] # Attempt to access a nonexistent let var + - deleteOne: + namespace: *namespace + filter: { _id: 3 } + verboseResults: false + expectError: + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 1 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} + writeErrors: + 1: + code: *undefinedVarCode + - description: "a top-level failure occurs during a bulkWrite" + operations: + - object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - bulkWrite + errorCode: 8 # UnknownError + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { x: 1 } + verboseResults: true + expectError: + errorCode: 8 + - description: "a bulk write with only errors does not report a partial result" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - deleteOne: + namespace: *namespace + filter: + $expr: + $eq: [ "$_id", "$$id2" ] # Attempt to access a nonexistent let var + verboseResults: true + expectError: + expectResult: + $$unsetOrMatches: {} # Empty or nonexistent result when no successful writes occurred + writeErrors: + 0: + code: *undefinedVarCode + - description: "a write concern error occurs during a bulkWrite" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - bulkWrite + writeConcernError: + code: *writeConcernErrorCode + errmsg: *writeConcernErrorMessage + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 10 } + verboseResults: true + expectError: + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 10 + updateResults: {} + deleteResults: {} + writeConcernErrors: + - code: *writeConcernErrorCode + message: *writeConcernErrorMessage + - description: "an empty list of write models is a client-side error" + operations: + - name: clientBulkWrite + object: *client0 + arguments: + models: [] + verboseResults: true + expectError: + isClientError: true diff --git a/testdata/crud/unified/client-bulkWrite-mixed-namespaces.json b/testdata/crud/unified/client-bulkWrite-mixed-namespaces.json new file mode 100644 index 0000000000..55f0618923 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-mixed-namespaces.json @@ -0,0 +1,315 @@ +{ + "description": "client bulkWrite with mixed namespaces", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "db0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + }, + { + "collection": { + "id": "collection1", + "database": "database0", + "collectionName": "coll1" + } + }, + { + "database": { + "id": "database1", + "client": "client0", + "databaseName": "db1" + } + }, + { + "collection": { + "id": "collection2", + "database": "database1", + "collectionName": "coll2" + } + } + ], + "initialData": [ + { + "databaseName": "db0", + "collectionName": "coll0", + "documents": [] + }, + { + "databaseName": "db0", + "collectionName": "coll1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + }, + { + "databaseName": "db1", + "collectionName": "coll2", + "documents": [ + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "_yamlAnchors": { + "db0Coll0Namespace": "db0.coll0", + "db0Coll1Namespace": "db0.coll1", + "db1Coll2Namespace": "db1.coll2" + }, + "tests": [ + { + "description": "client bulkWrite with mixed namespaces", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "db0.coll0", + "document": { + "_id": 1 + } + } + }, + { + "insertOne": { + "namespace": "db0.coll0", + "document": { + "_id": 2 + } + } + }, + { + "updateOne": { + "namespace": "db0.coll1", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteOne": { + "namespace": "db1.coll2", + "filter": { + "_id": 3 + } + } + }, + { + "deleteOne": { + "namespace": "db0.coll1", + "filter": { + "_id": 2 + } + } + }, + { + "replaceOne": { + "namespace": "db1.coll2", + "filter": { + "_id": 4 + }, + "replacement": { + "x": 45 + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 2, + "upsertedCount": 0, + "matchedCount": 2, + "modifiedCount": 2, + "deletedCount": 2, + "insertResults": { + "0": { + "insertedId": 1 + }, + "1": { + "insertedId": 2 + } + }, + "updateResults": { + "2": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "5": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": { + "3": { + "deletedCount": 1 + }, + "4": { + "deletedCount": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "bulkWrite": 1, + "ops": [ + { + "insert": 0, + "document": { + "_id": 1 + } + }, + { + "insert": 0, + "document": { + "_id": 2 + } + }, + { + "update": 1, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "delete": 2, + "filter": { + "_id": 3 + }, + "multi": false + }, + { + "delete": 1, + "filter": { + "_id": 2 + }, + "multi": false + }, + { + "update": 2, + "filter": { + "_id": 4 + }, + "updateMods": { + "x": 45 + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "db0.coll0" + }, + { + "ns": "db0.coll1" + }, + { + "ns": "db1.coll2" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "db0", + "collectionName": "coll0", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + }, + { + "databaseName": "db0", + "collectionName": "coll1", + "documents": [ + { + "_id": 1, + "x": 12 + } + ] + }, + { + "databaseName": "db1", + "collectionName": "coll2", + "documents": [ + { + "_id": 4, + "x": 45 + } + ] + } + ] + } + ] +} diff --git a/testdata/crud/unified/client-bulkWrite-mixed-namespaces.yml b/testdata/crud/unified/client-bulkWrite-mixed-namespaces.yml new file mode 100644 index 0000000000..9788bce8c5 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-mixed-namespaces.yml @@ -0,0 +1,147 @@ +description: "client bulkWrite with mixed namespaces" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name db0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + - collection: + id: &collection1 collection1 + database: *database0 + collectionName: &collection1Name coll1 + - database: + id: &database1 database1 + client: *client0 + databaseName: &database1Name db1 + - collection: + id: &collection2 collection2 + database: *database1 + collectionName: &collection2Name coll2 + +initialData: + - databaseName: *database0Name + collectionName: *collection0Name + documents: [] + - databaseName: *database0Name + collectionName: *collection1Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - databaseName: *database1Name + collectionName: *collection2Name + documents: + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + +_yamlAnchors: + db0Coll0Namespace: &db0Coll0Namespace "db0.coll0" + db0Coll1Namespace: &db0Coll1Namespace "db0.coll1" + db1Coll2Namespace: &db1Coll2Namespace "db1.coll2" + +tests: + - description: "client bulkWrite with mixed namespaces" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *db0Coll0Namespace + document: { _id: 1 } + - insertOne: + namespace: *db0Coll0Namespace + document: { _id: 2 } + - updateOne: + namespace: *db0Coll1Namespace + filter: { _id: 1 } + update: { $inc: { x: 1 } } + - deleteOne: + namespace: *db1Coll2Namespace + filter: { _id: 3 } + - deleteOne: + namespace: *db0Coll1Namespace + filter: { _id: 2 } + - replaceOne: + namespace: *db1Coll2Namespace + filter: { _id: 4 } + replacement: { x: 45 } + verboseResults: true + expectResult: + insertedCount: 2 + upsertedCount: 0 + matchedCount: 2 + modifiedCount: 2 + deletedCount: 2 + insertResults: + 0: + insertedId: 1 + 1: + insertedId: 2 + updateResults: + 2: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 5: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + deleteResults: + 3: + deletedCount: 1 + 4: + deletedCount: 1 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + bulkWrite: 1 + ops: + - insert: 0 + document: { _id: 1 } + - insert: 0 + document: { _id: 2 } + - update: 1 + filter: { _id: 1 } + updateMods: { $inc: { x: 1 } } + multi: false + - delete: 2 + filter: { _id: 3 } + multi: false + - delete: 1 + filter: { _id: 2 } + multi: false + - update: 2 + filter: { _id: 4 } + updateMods: { x: 45 } + multi: false + nsInfo: + - ns: *db0Coll0Namespace + - ns: *db0Coll1Namespace + - ns: *db1Coll2Namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { _id: 1 } + - { _id: 2 } + - databaseName: *database0Name + collectionName: *collection1Name + documents: + - { _id: 1, x: 12 } + - databaseName: *database1Name + collectionName: *collection2Name + documents: + - { _id: 4, x: 45 } diff --git a/testdata/crud/unified/client-bulkWrite-options.json b/testdata/crud/unified/client-bulkWrite-options.json new file mode 100644 index 0000000000..708fe4e85b --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-options.json @@ -0,0 +1,716 @@ +{ + "description": "client bulkWrite top-level options", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "client": { + "id": "writeConcernClient", + "uriOptions": { + "w": 1 + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0", + "comment": { + "bulk": "write" + }, + "let": { + "id1": 1, + "id2": 2 + }, + "writeConcern": { + "w": "majority" + } + }, + "tests": [ + { + "description": "client bulkWrite comment", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 3, + "x": 33 + } + } + } + ], + "comment": { + "bulk": "write" + }, + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 3 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "comment": { + "bulk": "write" + }, + "ops": [ + { + "insert": 0, + "document": { + "_id": 3, + "x": 33 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "client bulkWrite bypassDocumentValidation", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 3, + "x": 33 + } + } + } + ], + "bypassDocumentValidation": true, + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 3 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "bypassDocumentValidation": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 3, + "x": 33 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "client bulkWrite let", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id1" + ] + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id2" + ] + } + } + } + } + ], + "let": { + "id1": 1, + "id2": 2 + }, + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 1, + "modifiedCount": 1, + "deletedCount": 1, + "insertResults": {}, + "updateResults": { + "0": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": { + "1": { + "deletedCount": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "let": { + "id1": 1, + "id2": 2 + }, + "ops": [ + { + "update": 0, + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id1" + ] + } + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id2" + ] + } + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "x": 12 + } + ] + } + ] + }, + { + "description": "client bulkWrite bypassDocumentValidation: false is sent", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 3, + "x": 33 + } + } + } + ], + "bypassDocumentValidation": false, + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 3 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "bypassDocumentValidation": false, + "ops": [ + { + "insert": 0, + "document": { + "_id": 3, + "x": 33 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "client bulkWrite writeConcern", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 3, + "x": 33 + } + } + } + ], + "writeConcern": { + "w": "majority" + }, + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 3 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "writeConcern": { + "w": "majority" + }, + "ops": [ + { + "insert": 0, + "document": { + "_id": 3, + "x": 33 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ] + }, + { + "description": "client bulkWrite inherits writeConcern from client", + "operations": [ + { + "object": "writeConcernClient", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 3, + "x": 33 + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 3 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "writeConcernClient", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "writeConcern": { + "w": 1 + }, + "ops": [ + { + "insert": 0, + "document": { + "_id": 3, + "x": 33 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ] + }, + { + "description": "client bulkWrite writeConcern option overrides client writeConcern", + "operations": [ + { + "object": "writeConcernClient", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 3, + "x": 33 + } + } + } + ], + "writeConcern": { + "w": "majority" + }, + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 3 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "writeConcernClient", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "writeConcern": { + "w": "majority" + }, + "ops": [ + { + "insert": 0, + "document": { + "_id": 3, + "x": 33 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/testdata/crud/unified/client-bulkWrite-options.yml b/testdata/crud/unified/client-bulkWrite-options.yml new file mode 100644 index 0000000000..e0cbe747b3 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-options.yml @@ -0,0 +1,351 @@ +description: "client bulkWrite top-level options" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - client: + id: &writeConcernClient writeConcernClient + uriOptions: + &clientWriteConcern { w: 1 } + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + comment: &comment { bulk: "write" } + let: &let { id1: 1, id2: 2 } + writeConcern: &majorityWriteConcern { w: "majority" } + +tests: + - description: "client bulkWrite comment" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 3, x: 33 } + comment: *comment + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 3 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + comment: *comment + ops: + - insert: 0 + document: { _id: 3, x: 33 } + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - description: "client bulkWrite bypassDocumentValidation" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 3, x: 33 } + bypassDocumentValidation: true + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 3 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + bypassDocumentValidation: true + ops: + - insert: 0 + document: { _id: 3, x: 33 } + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - description: "client bulkWrite let" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateOne: + namespace: *namespace + filter: + $expr: + $eq: [ "$_id", "$$id1" ] + update: + $inc: { x: 1 } + - deleteOne: + namespace: *namespace + filter: + $expr: + $eq: [ "$_id", "$$id2" ] + let: *let + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 1 + modifiedCount: 1 + deletedCount: 1 + insertResults: {} + updateResults: + 0: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + deleteResults: + 1: + deletedCount: 1 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + let: *let + ops: + - update: 0 + filter: + $expr: + $eq: [ "$_id", "$$id1" ] + updateMods: { $inc: { x: 1 } } + multi: false + - delete: 0 + filter: + $expr: + $eq: [ "$_id", "$$id2" ] + multi: false + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { _id: 1, x: 12 } + - description: "client bulkWrite bypassDocumentValidation: false is sent" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 3, x: 33 } + bypassDocumentValidation: false + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 3 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + bypassDocumentValidation: false + ops: + - insert: 0 + document: { _id: 3, x: 33 } + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - description: "client bulkWrite writeConcern" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 3, x: 33 } + writeConcern: *majorityWriteConcern + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 3 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + writeConcern: *majorityWriteConcern + ops: + - insert: 0 + document: { _id: 3, x: 33 } + nsInfo: + - ns: *namespace + - description: "client bulkWrite inherits writeConcern from client" + operations: + - object: *writeConcernClient + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 3, x: 33 } + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 3 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *writeConcernClient + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + writeConcern: { w: 1 } + ops: + - insert: 0 + document: { _id: 3, x: 33 } + nsInfo: + - ns: *namespace + - description: "client bulkWrite writeConcern option overrides client writeConcern" + operations: + - object: *writeConcernClient + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 3, x: 33 } + writeConcern: *majorityWriteConcern + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 3 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *writeConcernClient + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + writeConcern: *majorityWriteConcern + ops: + - insert: 0 + document: { _id: 3, x: 33 } + nsInfo: + - ns: *namespace diff --git a/testdata/crud/unified/client-bulkWrite-ordered.json b/testdata/crud/unified/client-bulkWrite-ordered.json new file mode 100644 index 0000000000..6fb10d992f --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-ordered.json @@ -0,0 +1,291 @@ +{ + "description": "client bulkWrite with ordered option", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite with ordered: false", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "verboseResults": true, + "ordered": false + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 1 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": false, + "ops": [ + { + "insert": 0, + "document": { + "_id": 1, + "x": 11 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "client bulkWrite with ordered: true", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "verboseResults": true, + "ordered": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 1 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 1, + "x": 11 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "client bulkWrite defaults to ordered: true", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 1 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 1, + "x": 11 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + } + ] +} diff --git a/testdata/crud/unified/client-bulkWrite-ordered.yml b/testdata/crud/unified/client-bulkWrite-ordered.yml new file mode 100644 index 0000000000..48aa8ad40a --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-ordered.yml @@ -0,0 +1,153 @@ +description: "client bulkWrite with ordered option" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: [] + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + +tests: + - description: "client bulkWrite with ordered: false" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 1, x: 11 } + verboseResults: true + ordered: false + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 1 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: false + ops: + - insert: 0 + document: { _id: 1, x: 11 } + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - description: "client bulkWrite with ordered: true" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 1, x: 11 } + verboseResults: true + ordered: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 1 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 1, x: 11 } + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - description: "client bulkWrite defaults to ordered: true" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 1, x: 11 } + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 1 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 1, x: 11 } + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } diff --git a/testdata/crud/unified/client-bulkWrite-results.json b/testdata/crud/unified/client-bulkWrite-results.json new file mode 100644 index 0000000000..accf5a9cbf --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-results.json @@ -0,0 +1,833 @@ +{ + "description": "client bulkWrite results", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 5, + "x": 55 + }, + { + "_id": 6, + "x": 66 + }, + { + "_id": 7, + "x": 77 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite with verboseResults: true returns detailed results", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 8, + "x": 88 + } + } + }, + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$inc": { + "x": 2 + } + } + } + }, + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "upsert": true + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 5 + } + } + }, + { + "deleteMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 1, + "matchedCount": 3, + "modifiedCount": 3, + "deletedCount": 3, + "insertResults": { + "0": { + "insertedId": 8 + } + }, + "updateResults": { + "1": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "2": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedId": { + "$$exists": false + } + }, + "3": { + "matchedCount": 1, + "modifiedCount": 0, + "upsertedId": 4 + } + }, + "deleteResults": { + "4": { + "deletedCount": 1 + }, + "5": { + "deletedCount": 2 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 8, + "x": 88 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$inc": { + "x": 2 + } + }, + "multi": true + }, + { + "update": 0, + "filter": { + "_id": 4 + }, + "updateMods": { + "x": 44 + }, + "upsert": true, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 5 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + }, + "multi": true + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 24 + }, + { + "_id": 3, + "x": 35 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 8, + "x": 88 + } + ] + } + ] + }, + { + "description": "client bulkWrite with verboseResults: false omits detailed results", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 8, + "x": 88 + } + } + }, + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$inc": { + "x": 2 + } + } + } + }, + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "upsert": true + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 5 + } + } + }, + { + "deleteMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + } + } + } + ], + "verboseResults": false + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 1, + "matchedCount": 3, + "modifiedCount": 3, + "deletedCount": 3, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 8, + "x": 88 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$inc": { + "x": 2 + } + }, + "multi": true + }, + { + "update": 0, + "filter": { + "_id": 4 + }, + "updateMods": { + "x": 44 + }, + "upsert": true, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 5 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + }, + "multi": true + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 24 + }, + { + "_id": 3, + "x": 35 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 8, + "x": 88 + } + ] + } + ] + }, + { + "description": "client bulkWrite defaults to verboseResults: false", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 8, + "x": 88 + } + } + }, + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$inc": { + "x": 2 + } + } + } + }, + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "upsert": true + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 5 + } + } + }, + { + "deleteMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + } + } + } + ] + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 1, + "matchedCount": 3, + "modifiedCount": 3, + "deletedCount": 3, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 8, + "x": 88 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$inc": { + "x": 2 + } + }, + "multi": true + }, + { + "update": 0, + "filter": { + "_id": 4 + }, + "updateMods": { + "x": 44 + }, + "upsert": true, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 5 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + }, + "multi": true + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 24 + }, + { + "_id": 3, + "x": 35 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 8, + "x": 88 + } + ] + } + ] + } + ] +} diff --git a/testdata/crud/unified/client-bulkWrite-results.yml b/testdata/crud/unified/client-bulkWrite-results.yml new file mode 100644 index 0000000000..86cb5346ae --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-results.yml @@ -0,0 +1,312 @@ +description: "client bulkWrite results" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 5, x: 55 } + - { _id: 6, x: 66 } + - { _id: 7, x: 77 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + +tests: + - description: "client bulkWrite with verboseResults: true returns detailed results" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 8, x: 88 } + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: { $inc: { x: 1 } } + - updateMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: { $inc: { x: 2 } } + - replaceOne: + namespace: *namespace + filter: { _id: 4 } + replacement: { x: 44 } + upsert: true + - deleteOne: + namespace: *namespace + filter: { _id: 5 } + - deleteMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 1 + matchedCount: 3 + modifiedCount: 3 + deletedCount: 3 + insertResults: + 0: + insertedId: 8 + updateResults: + 1: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 2: + matchedCount: 2 + modifiedCount: 2 + upsertedId: { $$exists: false } + 3: + matchedCount: 1 + modifiedCount: 0 + upsertedId: 4 + deleteResults: + 4: + deletedCount: 1 + 5: + deletedCount: 2 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 8, x: 88 } + - update: 0 + filter: { _id: 1 } + updateMods: { $inc: { x: 1 } } + multi: false + - update: 0 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + updateMods: { $inc: { x: 2 } } + multi: true + - update: 0 + filter: { _id: 4 } + updateMods: { x: 44 } + upsert: true + multi: false + - delete: 0 + filter: { _id: 5 } + multi: false + - delete: 0 + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + multi: true + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 24 } + - { _id: 3, x: 35 } + - { _id: 4, x: 44 } + - { _id: 8, x: 88 } + - description: "client bulkWrite with verboseResults: false omits detailed results" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 8, x: 88 } + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: { $inc: { x: 1 } } + - updateMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: { $inc: { x: 2 } } + - replaceOne: + namespace: *namespace + filter: { _id: 4 } + replacement: { x: 44 } + upsert: true + - deleteOne: + namespace: *namespace + filter: { _id: 5 } + - deleteMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + verboseResults: false + expectResult: + insertedCount: 1 + upsertedCount: 1 + matchedCount: 3 + modifiedCount: 3 + deletedCount: 3 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: true + ordered: true + ops: + - insert: 0 + document: { _id: 8, x: 88 } + - update: 0 + filter: { _id: 1 } + updateMods: { $inc: { x: 1 } } + multi: false + - update: 0 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + updateMods: { $inc: { x: 2 } } + multi: true + - update: 0 + filter: { _id: 4 } + updateMods: { x: 44 } + upsert: true + multi: false + - delete: 0 + filter: { _id: 5 } + multi: false + - delete: 0 + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + multi: true + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 24 } + - { _id: 3, x: 35 } + - { _id: 4, x: 44 } + - { _id: 8, x: 88 } + - description: "client bulkWrite defaults to verboseResults: false" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 8, x: 88 } + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: { $inc: { x: 1 } } + - updateMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: { $inc: { x: 2 } } + - replaceOne: + namespace: *namespace + filter: { _id: 4 } + replacement: { x: 44 } + upsert: true + - deleteOne: + namespace: *namespace + filter: { _id: 5 } + - deleteMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + expectResult: + insertedCount: 1 + upsertedCount: 1 + matchedCount: 3 + modifiedCount: 3 + deletedCount: 3 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: true + ordered: true + ops: + - insert: 0 + document: { _id: 8, x: 88 } + - update: 0 + filter: { _id: 1 } + updateMods: { $inc: { x: 1 } } + multi: false + - update: 0 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + updateMods: { $inc: { x: 2 } } + multi: true + - update: 0 + filter: { _id: 4 } + updateMods: { x: 44 } + upsert: true + multi: false + - delete: 0 + filter: { _id: 5 } + multi: false + - delete: 0 + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + multi: true + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 24 } + - { _id: 3, x: 35 } + - { _id: 4, x: 44 } + - { _id: 8, x: 88 } diff --git a/testdata/crud/unified/client-bulkWrite-update-options.json b/testdata/crud/unified/client-bulkWrite-update-options.json new file mode 100644 index 0000000000..ce6241c681 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-update-options.json @@ -0,0 +1,949 @@ +{ + "description": "client bulkWrite update options", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "array": [ + 1, + 2, + 3 + ] + }, + { + "_id": 2, + "array": [ + 1, + 2, + 3 + ] + }, + { + "_id": 3, + "array": [ + 1, + 2, + 3 + ] + }, + { + "_id": 4, + "array": [ + 1, + 2, + 3 + ] + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0", + "collation": { + "locale": "simple" + }, + "hint": "_id_" + }, + "tests": [ + { + "description": "client bulkWrite update with arrayFilters", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "array.$[i]": 4 + } + }, + "arrayFilters": [ + { + "i": { + "$gte": 2 + } + } + ] + } + }, + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$set": { + "array.$[i]": 5 + } + }, + "arrayFilters": [ + { + "i": { + "$gte": 2 + } + } + ] + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 3, + "modifiedCount": 3, + "deletedCount": 0, + "insertResults": {}, + "updateResults": { + "0": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "1": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$set": { + "array.$[i]": 4 + } + }, + "arrayFilters": [ + { + "i": { + "$gte": 2 + } + } + ], + "multi": false + }, + { + "update": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$set": { + "array.$[i]": 5 + } + }, + "arrayFilters": [ + { + "i": { + "$gte": 2 + } + } + ], + "multi": true + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "array": [ + 1, + 4, + 4 + ] + }, + { + "_id": 2, + "array": [ + 1, + 5, + 5 + ] + }, + { + "_id": 3, + "array": [ + 1, + 5, + 5 + ] + }, + { + "_id": 4, + "array": [ + 1, + 2, + 3 + ] + } + ] + } + ] + }, + { + "description": "client bulkWrite update with collation", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "array": [ + 1, + 2, + 4 + ] + } + }, + "collation": { + "locale": "simple" + } + } + }, + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$set": { + "array": [ + 1, + 2, + 5 + ] + } + }, + "collation": { + "locale": "simple" + } + } + }, + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 4 + }, + "replacement": { + "array": [ + 1, + 2, + 6 + ] + }, + "collation": { + "locale": "simple" + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 4, + "modifiedCount": 4, + "deletedCount": 0, + "insertResults": {}, + "updateResults": { + "0": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "1": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedId": { + "$$exists": false + } + }, + "2": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$set": { + "array": [ + 1, + 2, + 4 + ] + } + }, + "collation": { + "locale": "simple" + }, + "multi": false + }, + { + "update": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$set": { + "array": [ + 1, + 2, + 5 + ] + } + }, + "collation": { + "locale": "simple" + }, + "multi": true + }, + { + "update": 0, + "filter": { + "_id": 4 + }, + "updateMods": { + "array": [ + 1, + 2, + 6 + ] + }, + "collation": { + "locale": "simple" + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "array": [ + 1, + 2, + 4 + ] + }, + { + "_id": 2, + "array": [ + 1, + 2, + 5 + ] + }, + { + "_id": 3, + "array": [ + 1, + 2, + 5 + ] + }, + { + "_id": 4, + "array": [ + 1, + 2, + 6 + ] + } + ] + } + ] + }, + { + "description": "client bulkWrite update with hint", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "array": [ + 1, + 2, + 4 + ] + } + }, + "hint": "_id_" + } + }, + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$set": { + "array": [ + 1, + 2, + 5 + ] + } + }, + "hint": "_id_" + } + }, + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 4 + }, + "replacement": { + "array": [ + 1, + 2, + 6 + ] + }, + "hint": "_id_" + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 4, + "modifiedCount": 4, + "deletedCount": 0, + "insertResults": {}, + "updateResults": { + "0": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "1": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedId": { + "$$exists": false + } + }, + "2": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$set": { + "array": [ + 1, + 2, + 4 + ] + } + }, + "hint": "_id_", + "multi": false + }, + { + "update": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$set": { + "array": [ + 1, + 2, + 5 + ] + } + }, + "hint": "_id_", + "multi": true + }, + { + "update": 0, + "filter": { + "_id": 4 + }, + "updateMods": { + "array": [ + 1, + 2, + 6 + ] + }, + "hint": "_id_", + "multi": false + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "array": [ + 1, + 2, + 4 + ] + }, + { + "_id": 2, + "array": [ + 1, + 2, + 5 + ] + }, + { + "_id": 3, + "array": [ + 1, + 2, + 5 + ] + }, + { + "_id": 4, + "array": [ + 1, + 2, + 6 + ] + } + ] + } + ] + }, + { + "description": "client bulkWrite update with upsert", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 5 + }, + "update": { + "$set": { + "array": [ + 1, + 2, + 4 + ] + } + }, + "upsert": true + } + }, + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 6 + }, + "replacement": { + "array": [ + 1, + 2, + 6 + ] + }, + "upsert": true + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 2, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": {}, + "updateResults": { + "0": { + "matchedCount": 1, + "modifiedCount": 0, + "upsertedId": 5 + }, + "1": { + "matchedCount": 1, + "modifiedCount": 0, + "upsertedId": 6 + } + }, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": { + "_id": 5 + }, + "updateMods": { + "$set": { + "array": [ + 1, + 2, + 4 + ] + } + }, + "upsert": true, + "multi": false + }, + { + "update": 0, + "filter": { + "_id": 6 + }, + "updateMods": { + "array": [ + 1, + 2, + 6 + ] + }, + "upsert": true, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "array": [ + 1, + 2, + 3 + ] + }, + { + "_id": 2, + "array": [ + 1, + 2, + 3 + ] + }, + { + "_id": 3, + "array": [ + 1, + 2, + 3 + ] + }, + { + "_id": 4, + "array": [ + 1, + 2, + 3 + ] + }, + { + "_id": 5, + "array": [ + 1, + 2, + 4 + ] + }, + { + "_id": 6, + "array": [ + 1, + 2, + 6 + ] + } + ] + } + ] + } + ] +} diff --git a/testdata/crud/unified/client-bulkWrite-update-options.yml b/testdata/crud/unified/client-bulkWrite-update-options.yml new file mode 100644 index 0000000000..c5cc20d480 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-update-options.yml @@ -0,0 +1,338 @@ +description: "client bulkWrite update options" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, array: [ 1, 2, 3 ] } + - { _id: 2, array: [ 1, 2, 3 ] } + - { _id: 3, array: [ 1, 2, 3 ] } + - { _id: 4, array: [ 1, 2, 3 ] } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + collation: &collation { "locale": "simple" } + hint: &hint _id_ + +tests: + - description: "client bulkWrite update with arrayFilters" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: + $set: + array.$[i]: 4 + arrayFilters: [ i: { $gte: 2 } ] + - updateMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: + $set: + array.$[i]: 5 + arrayFilters: [ i: { $gte: 2 } ] + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 3 + modifiedCount: 3 + deletedCount: 0 + insertResults: {} + updateResults: + 0: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 1: + matchedCount: 2 + modifiedCount: 2 + upsertedId: { $$exists: false } + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - update: 0 + filter: { _id: 1 } + updateMods: + $set: + array.$[i]: 4 + arrayFilters: [ i: { $gte: 2 } ] + multi: false + - update: 0 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + updateMods: + $set: + array.$[i]: 5 + arrayFilters: [ i: { $gte: 2 } ] + multi: true + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { _id: 1, array: [ 1, 4, 4 ] } + - { _id: 2, array: [ 1, 5, 5 ] } + - { _id: 3, array: [ 1, 5, 5 ] } + - { _id: 4, array: [ 1, 2, 3 ] } + - description: "client bulkWrite update with collation" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: { $set: { array: [ 1, 2, 4 ] } } + collation: *collation + - updateMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: { $set: { array: [ 1, 2, 5 ] } } + collation: *collation + - replaceOne: + namespace: *namespace + filter: { _id: 4 } + replacement: { array: [ 1, 2, 6 ] } + collation: *collation + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 4 + modifiedCount: 4 + deletedCount: 0 + insertResults: {} + updateResults: + 0: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 1: + matchedCount: 2 + modifiedCount: 2 + upsertedId: { $$exists: false } + 2: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - update: 0 + filter: { _id: 1 } + updateMods: { $set: { array: [ 1, 2, 4 ] } } + collation: *collation + multi: false + - update: 0 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + updateMods: { $set: { array: [ 1, 2, 5 ] } } + collation: *collation + multi: true + - update: 0 + filter: { _id: 4 } + updateMods: { array: [ 1, 2, 6 ] } + collation: *collation + multi: false + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { _id: 1, array: [ 1, 2, 4 ] } + - { _id: 2, array: [ 1, 2, 5 ] } + - { _id: 3, array: [ 1, 2, 5 ] } + - { _id: 4, array: [ 1, 2, 6 ] } + - description: "client bulkWrite update with hint" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: { $set: { array: [ 1, 2, 4 ] } } + hint: *hint + - updateMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: { $set: { array: [ 1, 2, 5 ] } } + hint: *hint + - replaceOne: + namespace: *namespace + filter: { _id: 4 } + replacement: { array: [ 1, 2, 6 ] } + hint: *hint + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 4 + modifiedCount: 4 + deletedCount: 0 + insertResults: {} + updateResults: + 0: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 1: + matchedCount: 2 + modifiedCount: 2 + upsertedId: { $$exists: false } + 2: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - update: 0 + filter: { _id: 1 } + updateMods: { $set: { array: [ 1, 2, 4 ] } } + hint: *hint + multi: false + - update: 0 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + updateMods: { $set: { array: [ 1, 2, 5 ] } } + hint: *hint + multi: true + - update: 0 + filter: { _id: 4 } + updateMods: { array: [ 1, 2, 6 ] } + hint: *hint + multi: false + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { _id: 1, array: [ 1, 2, 4 ] } + - { _id: 2, array: [ 1, 2, 5 ] } + - { _id: 3, array: [ 1, 2, 5 ] } + - { _id: 4, array: [ 1, 2, 6 ] } + - description: "client bulkWrite update with upsert" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateOne: + namespace: *namespace + filter: { _id: 5 } + update: { $set: { array: [ 1, 2, 4 ] } } + upsert: true + - replaceOne: + namespace: *namespace + filter: { _id: 6 } + replacement: { array: [ 1, 2, 6 ] } + upsert: true + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 2 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: {} + updateResults: + 0: + matchedCount: 1 + modifiedCount: 0 + upsertedId: 5 + 1: + matchedCount: 1 + modifiedCount: 0 + upsertedId: 6 + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - update: 0 + filter: { _id: 5 } + updateMods: { $set: { array: [ 1, 2, 4 ] } } + upsert: true + multi: false + - update: 0 + filter: { _id: 6 } + updateMods: { array: [ 1, 2, 6 ] } + upsert: true + multi: false + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { _id: 1, array: [ 1, 2, 3 ] } + - { _id: 2, array: [ 1, 2, 3 ] } + - { _id: 3, array: [ 1, 2, 3 ] } + - { _id: 4, array: [ 1, 2, 3 ] } + - { _id: 5, array: [ 1, 2, 4 ] } + - { _id: 6, array: [ 1, 2, 6 ] } diff --git a/testdata/crud/unified/client-bulkWrite-update-pipeline.json b/testdata/crud/unified/client-bulkWrite-update-pipeline.json new file mode 100644 index 0000000000..9dba5ee6c5 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-update-pipeline.json @@ -0,0 +1,258 @@ +{ + "description": "client bulkWrite update pipeline", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 1 + }, + { + "_id": 2, + "x": 2 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite updateOne with pipeline", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": [ + { + "$addFields": { + "foo": 1 + } + } + ] + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 1, + "modifiedCount": 1, + "deletedCount": 0, + "insertResults": {}, + "updateResults": { + "0": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": [ + { + "$addFields": { + "foo": 1 + } + } + ], + "multi": false + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "x": 1, + "foo": 1 + }, + { + "_id": 2, + "x": 2 + } + ] + } + ] + }, + { + "description": "client bulkWrite updateMany with pipeline", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": {}, + "update": [ + { + "$addFields": { + "foo": 1 + } + } + ] + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 2, + "modifiedCount": 2, + "deletedCount": 0, + "insertResults": {}, + "updateResults": { + "0": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": {}, + "updateMods": [ + { + "$addFields": { + "foo": 1 + } + } + ], + "multi": true + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "x": 1, + "foo": 1 + }, + { + "_id": 2, + "x": 2, + "foo": 1 + } + ] + } + ] + } + ] +} diff --git a/testdata/crud/unified/client-bulkWrite-update-pipeline.yml b/testdata/crud/unified/client-bulkWrite-update-pipeline.yml new file mode 100644 index 0000000000..c90e93b47c --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-update-pipeline.yml @@ -0,0 +1,133 @@ +description: "client bulkWrite update pipeline" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - {_id: 1, x: 1} + - {_id: 2, x: 2} + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + +tests: + - description: "client bulkWrite updateOne with pipeline" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: + - $addFields: + foo: 1 + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 1 + modifiedCount: 1 + deletedCount: 0 + insertResults: {} + updateResults: + 0: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { "$$exists": false } + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - update: 0 + filter: { _id: 1 } + updateMods: + - $addFields: + foo: 1 + multi: false + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - {_id: 1, x: 1, foo: 1} + - {_id: 2, x: 2 } + + - description: "client bulkWrite updateMany with pipeline" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateMany: + namespace: *namespace + filter: {} + update: + - $addFields: + foo: 1 + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 2 + modifiedCount: 2 + deletedCount: 0 + insertResults: {} + updateResults: + 0: + matchedCount: 2 + modifiedCount: 2 + upsertedId: { "$$exists": false } + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - update: 0 + filter: { } + updateMods: + - $addFields: + foo: 1 + multi: true + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - {_id: 1, x: 1, foo: 1} + - {_id: 2, x: 2, foo: 1} diff --git a/testdata/crud/unified/client-bulkWrite-update-validation.json b/testdata/crud/unified/client-bulkWrite-update-validation.json new file mode 100644 index 0000000000..617e711338 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-update-validation.json @@ -0,0 +1,216 @@ +{ + "description": "client-bulkWrite-update-validation", + "schemaVersion": "1.1", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite replaceOne prohibits atomic modifiers", + "operations": [ + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "models": [ + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "replacement": { + "$set": { + "x": 22 + } + } + } + } + ] + }, + "expectError": { + "isClientError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "client bulkWrite updateOne requires atomic modifiers", + "operations": [ + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "x": 22 + } + } + } + ] + }, + "expectError": { + "isClientError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "client bulkWrite updateMany requires atomic modifiers", + "operations": [ + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "models": [ + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "x": 44 + } + } + } + ] + }, + "expectError": { + "isClientError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/testdata/crud/unified/client-bulkWrite-update-validation.yml b/testdata/crud/unified/client-bulkWrite-update-validation.yml new file mode 100644 index 0000000000..478554c322 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-update-validation.yml @@ -0,0 +1,79 @@ +description: "client-bulkWrite-update-validation" + +schemaVersion: "1.1" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: &initialData + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + +tests: + - description: "client bulkWrite replaceOne prohibits atomic modifiers" + operations: + - name: clientBulkWrite + object: *client0 + arguments: + models: + - replaceOne: + namespace: *namespace + filter: { _id: 1 } + replacement: { $set: { x: 22 } } + expectError: + isClientError: true + expectEvents: + - client: *client0 + events: [] + outcome: *initialData + + - description: "client bulkWrite updateOne requires atomic modifiers" + operations: + - name: clientBulkWrite + object: *client0 + arguments: + models: + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: { x: 22 } + expectError: + isClientError: true + expectEvents: + - client: *client0 + events: [] + outcome: *initialData + + - description: "client bulkWrite updateMany requires atomic modifiers" + operations: + - name: clientBulkWrite + object: *client0 + arguments: + models: + - updateMany: + namespace: *namespace + filter: { _id: { $gt: 1 } } + update: { x: 44 } + expectError: + isClientError: true + expectEvents: + - client: *client0 + events: [] + outcome: *initialData diff --git a/testdata/retryable-writes/unified/client-bulkWrite-clientErrors.json b/testdata/retryable-writes/unified/client-bulkWrite-clientErrors.json new file mode 100644 index 0000000000..d16e0c9c8d --- /dev/null +++ b/testdata/retryable-writes/unified/client-bulkWrite-clientErrors.json @@ -0,0 +1,351 @@ +{ + "description": "client bulkWrite retryable writes with client errors", + "schemaVersion": "1.21", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ], + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ], + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "retryable-writes-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite with one network error succeeds after retry", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-tests.coll0", + "document": { + "_id": 4, + "x": 44 + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 4 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ] + }, + { + "description": "client bulkWrite with two network errors fails after retry", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-tests.coll0", + "document": { + "_id": 4, + "x": 44 + } + } + } + ], + "verboseResults": true + }, + "expectError": { + "isClientError": true, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/testdata/retryable-writes/unified/client-bulkWrite-clientErrors.yml b/testdata/retryable-writes/unified/client-bulkWrite-clientErrors.yml new file mode 100644 index 0000000000..e5214b90f8 --- /dev/null +++ b/testdata/retryable-writes/unified/client-bulkWrite-clientErrors.yml @@ -0,0 +1,173 @@ +description: "client bulkWrite retryable writes with client errors" +schemaVersion: "1.21" +runOnRequirements: + - minServerVersion: "8.0" + topologies: + - replicaset + - sharded + - load-balanced + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name retryable-writes-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "retryable-writes-tests.coll0" + +tests: + - description: "client bulkWrite with one network error succeeds after retry" + operations: + - object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: [ bulkWrite ] + closeConnection: true + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 4, x: 44 } + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 4 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + nsInfo: + - ns: *namespace + # An implicit session is included with the transaction number: + lsid: { "$$exists": true } + txnNumber: { "$$exists": true } + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + nsInfo: + - ns: *namespace + # An implicit session is included with the transaction number: + lsid: { "$$exists": true } + txnNumber: { "$$exists": true } + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - description: "client bulkWrite with two network errors fails after retry" + operations: + - object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: [ bulkWrite ] + closeConnection: true + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 4, x: 44 } + verboseResults: true + expectError: + isClientError: true + errorLabelsContain: ["RetryableWriteError"] # Error label added by driver. + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + nsInfo: + - ns: *namespace + # An implicit session is included with the transaction number: + lsid: { "$$exists": true } + txnNumber: { "$$exists": true } + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + nsInfo: + - ns: *namespace + # An implicit session is included with the transaction number: + lsid: { "$$exists": true } + txnNumber: { "$$exists": true } + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } diff --git a/testdata/retryable-writes/unified/client-bulkWrite-serverErrors.json b/testdata/retryable-writes/unified/client-bulkWrite-serverErrors.json new file mode 100644 index 0000000000..f58c82bcc7 --- /dev/null +++ b/testdata/retryable-writes/unified/client-bulkWrite-serverErrors.json @@ -0,0 +1,873 @@ +{ + "description": "client bulkWrite retryable writes", + "schemaVersion": "1.21", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ], + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ], + "useMultipleMongoses": false + } + }, + { + "client": { + "id": "clientRetryWritesFalse", + "uriOptions": { + "retryWrites": false + }, + "observeEvents": [ + "commandStartedEvent" + ], + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "retryable-writes-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite with no multi: true operations succeeds after retryable top-level error", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-tests.coll0", + "document": { + "_id": 4, + "x": 44 + } + } + }, + { + "updateOne": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "replaceOne": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 2 + }, + "replacement": { + "x": 222 + } + } + }, + { + "deleteOne": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 3 + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 2, + "modifiedCount": 2, + "deletedCount": 1, + "insertResults": { + "0": { + "insertedId": 4 + } + }, + "updateResults": { + "1": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "2": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": { + "3": { + "deletedCount": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "_id": 2 + }, + "updateMods": { + "x": 222 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 3 + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "_id": 2 + }, + "updateMods": { + "x": 222 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 3 + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 222 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ] + }, + { + "description": "client bulkWrite with multi: true operations fails after retryable top-level error", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateMany": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteMany": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 3 + } + } + } + ] + }, + "expectError": { + "errorCode": 189, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": true + }, + { + "delete": 0, + "filter": { + "_id": 3 + }, + "multi": true + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ] + } + } + } + ] + } + ] + }, + { + "description": "client bulkWrite with no multi: true operations succeeds after retryable writeConcernError", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-tests.coll0", + "document": { + "_id": 4, + "x": 44 + } + } + }, + { + "updateOne": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "replaceOne": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 2 + }, + "replacement": { + "x": 222 + } + } + }, + { + "deleteOne": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 3 + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 2, + "modifiedCount": 2, + "deletedCount": 1, + "insertResults": { + "0": { + "insertedId": 4 + } + }, + "updateResults": { + "1": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "2": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": { + "3": { + "deletedCount": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "_id": 2 + }, + "updateMods": { + "x": 222 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 3 + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "_id": 2 + }, + "updateMods": { + "x": 222 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 3 + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "client bulkWrite with multi: true operations fails after retryable writeConcernError", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateMany": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteMany": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 3 + } + } + } + ] + }, + "expectError": { + "writeConcernErrors": [ + { + "code": 91, + "message": "Replication is being shut down" + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": true + }, + { + "delete": 0, + "filter": { + "_id": 3 + }, + "multi": true + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ] + } + } + } + ] + } + ] + }, + { + "description": "client bulkWrite with retryWrites: false does not retry", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "clientRetryWritesFalse", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "clientRetryWritesFalse", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-tests.coll0", + "document": { + "_id": 4, + "x": 44 + } + } + } + ] + }, + "expectError": { + "errorCode": 189, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "expectEvents": [ + { + "client": "clientRetryWritesFalse", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/testdata/retryable-writes/unified/client-bulkWrite-serverErrors.yml b/testdata/retryable-writes/unified/client-bulkWrite-serverErrors.yml new file mode 100644 index 0000000000..722e5cc8e0 --- /dev/null +++ b/testdata/retryable-writes/unified/client-bulkWrite-serverErrors.yml @@ -0,0 +1,413 @@ +description: "client bulkWrite retryable writes" +schemaVersion: "1.21" +runOnRequirements: + - minServerVersion: "8.0" + topologies: + - replicaset + - sharded + - load-balanced + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + useMultipleMongoses: false + - client: + id: &clientRetryWritesFalse clientRetryWritesFalse + uriOptions: + retryWrites: false + observeEvents: [ commandStartedEvent ] + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name retryable-writes-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "retryable-writes-tests.coll0" + +tests: + - description: "client bulkWrite with no multi: true operations succeeds after retryable top-level error" + operations: + - object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: [ bulkWrite ] + errorCode: 189 # PrimarySteppedDown + errorLabels: [ RetryableWriteError ] + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 4, x: 44 } + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: + $inc: { x: 1 } + - replaceOne: + namespace: *namespace + filter: { _id: 2 } + replacement: { x: 222 } + - deleteOne: + namespace: *namespace + filter: { _id: 3 } + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 2 + modifiedCount: 2 + deletedCount: 1 + insertResults: + 0: + insertedId: 4 + updateResults: + 1: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 2: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + deleteResults: + 3: + deletedCount: 1 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + - update: 0 + filter: { _id: 1 } + updateMods: + $inc: { x: 1 } + multi: false + - update: 0 + filter: { _id: 2 } + updateMods: { x: 222 } + multi: false + - delete: 0 + filter: { _id: 3 } + multi: false + nsInfo: + - ns: *namespace + lsid: { $$exists: true } + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + - update: 0 + filter: { _id: 1 } + updateMods: + $inc: { x: 1 } + multi: false + - update: 0 + filter: { _id: 2 } + updateMods: { x: 222 } + multi: false + - delete: 0 + filter: { _id: 3 } + multi: false + nsInfo: + - ns: *namespace + lsid: { $$exists: true } + txnNumber: { $$exists: true } + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 222 } + - { _id: 4, x: 44 } + - description: "client bulkWrite with multi: true operations fails after retryable top-level error" + operations: + - object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: [ bulkWrite ] + errorCode: 189 # PrimarySteppedDown + errorLabels: [ RetryableWriteError ] + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateMany: + namespace: *namespace + filter: { _id: 1 } + update: + $inc: { x: 1 } + - deleteMany: + namespace: *namespace + filter: { _id: 3 } + expectError: + errorCode: 189 + errorLabelsContain: [ RetryableWriteError ] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: true + ordered: true + ops: + - update: 0 + filter: { _id: 1 } + updateMods: + $inc: { x: 1 } + multi: true + - delete: 0 + filter: { _id: 3 } + multi: true + nsInfo: + - ns: *namespace + - description: "client bulkWrite with no multi: true operations succeeds after retryable writeConcernError" + operations: + - object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: [ bulkWrite ] + errorLabels: [ RetryableWriteError ] + writeConcernError: + code: 91 + errmsg: "Replication is being shut down" + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 4, x: 44 } + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: + $inc: { x: 1 } + - replaceOne: + namespace: *namespace + filter: { _id: 2 } + replacement: { x: 222 } + - deleteOne: + namespace: *namespace + filter: { _id: 3 } + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 2 + modifiedCount: 2 + deletedCount: 1 + insertResults: + 0: + insertedId: 4 + updateResults: + 1: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 2: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + deleteResults: + 3: + deletedCount: 1 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + - update: 0 + filter: { _id: 1 } + updateMods: + $inc: { x: 1 } + multi: false + - update: 0 + filter: { _id: 2 } + updateMods: { x: 222 } + multi: false + - delete: 0 + filter: { _id: 3 } + multi: false + nsInfo: + - ns: *namespace + lsid: { $$exists: true } + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + - update: 0 + filter: { _id: 1 } + updateMods: + $inc: { x: 1 } + multi: false + - update: 0 + filter: { _id: 2 } + updateMods: { x: 222 } + multi: false + - delete: 0 + filter: { _id: 3 } + multi: false + nsInfo: + - ns: *namespace + lsid: { $$exists: true } + txnNumber: { $$exists: true } + - description: "client bulkWrite with multi: true operations fails after retryable writeConcernError" + operations: + - object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: [ bulkWrite ] + errorLabels: [ RetryableWriteError ] + writeConcernError: + code: 91 + errmsg: "Replication is being shut down" + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateMany: + namespace: *namespace + filter: { _id: 1 } + update: + $inc: { x: 1 } + - deleteMany: + namespace: *namespace + filter: { _id: 3 } + expectError: + writeConcernErrors: + - code: 91 + message: "Replication is being shut down" + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: true + ordered: true + ops: + - update: 0 + filter: { _id: 1 } + updateMods: + $inc: { x: 1 } + multi: true + - delete: 0 + filter: { _id: 3 } + multi: true + nsInfo: + - ns: *namespace + - description: "client bulkWrite with retryWrites: false does not retry" + operations: + - object: testRunner + name: failPoint + arguments: + client: *clientRetryWritesFalse + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: [ bulkWrite ] + errorCode: 189 # PrimarySteppedDown + errorLabels: [ RetryableWriteError ] + - object: *clientRetryWritesFalse + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 4, x: 44 } + expectError: + errorCode: 189 + errorLabelsContain: [ RetryableWriteError ] + expectEvents: + - client: *clientRetryWritesFalse + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: true + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + nsInfo: + - ns: *namespace diff --git a/testdata/retryable-writes/unified/handshakeError.json b/testdata/retryable-writes/unified/handshakeError.json new file mode 100644 index 0000000000..93cb2e849e --- /dev/null +++ b/testdata/retryable-writes/unified/handshakeError.json @@ -0,0 +1,2015 @@ +{ + "description": "retryable writes handshake failures", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.2", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ], + "auth": true + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "connectionCheckOutStartedEvent", + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-handshake-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "client.clientBulkWrite succeeds after retryable handshake network error", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "clientBulkWrite", + "object": "client", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-handshake-tests.coll", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandSucceededEvent": { + "commandName": "bulkWrite" + } + } + ] + } + ] + }, + { + "description": "client.clientBulkWrite succeeds after retryable handshake server error (ShutdownInProgress)", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "clientBulkWrite", + "object": "client", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-handshake-tests.coll", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandSucceededEvent": { + "commandName": "bulkWrite" + } + } + ] + } + ] + }, + { + "description": "collection.insertOne succeeds after retryable handshake network error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "collection.insertOne succeeds after retryable handshake server error (ShutdownInProgress)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "collection.insertMany succeeds after retryable handshake network error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "collection.insertMany succeeds after retryable handshake server error (ShutdownInProgress)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "collection.deleteOne succeeds after retryable handshake network error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "deleteOne", + "object": "collection", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandSucceededEvent": { + "commandName": "delete" + } + } + ] + } + ] + }, + { + "description": "collection.deleteOne succeeds after retryable handshake server error (ShutdownInProgress)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "deleteOne", + "object": "collection", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandSucceededEvent": { + "commandName": "delete" + } + } + ] + } + ] + }, + { + "description": "collection.replaceOne succeeds after retryable handshake network error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "replaceOne", + "object": "collection", + "arguments": { + "filter": {}, + "replacement": { + "x": 22 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandSucceededEvent": { + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "collection.replaceOne succeeds after retryable handshake server error (ShutdownInProgress)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "replaceOne", + "object": "collection", + "arguments": { + "filter": {}, + "replacement": { + "x": 22 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandSucceededEvent": { + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "collection.updateOne succeeds after retryable handshake network error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "updateOne", + "object": "collection", + "arguments": { + "filter": {}, + "update": { + "$set": { + "x": 22 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandSucceededEvent": { + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "collection.updateOne succeeds after retryable handshake server error (ShutdownInProgress)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "updateOne", + "object": "collection", + "arguments": { + "filter": {}, + "update": { + "$set": { + "x": 22 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandSucceededEvent": { + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndDelete succeeds after retryable handshake network error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "findOneAndDelete", + "object": "collection", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandSucceededEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndDelete succeeds after retryable handshake server error (ShutdownInProgress)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "findOneAndDelete", + "object": "collection", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandSucceededEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndReplace succeeds after retryable handshake network error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "findOneAndReplace", + "object": "collection", + "arguments": { + "filter": {}, + "replacement": { + "x": 22 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandSucceededEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndReplace succeeds after retryable handshake server error (ShutdownInProgress)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "findOneAndReplace", + "object": "collection", + "arguments": { + "filter": {}, + "replacement": { + "x": 22 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandSucceededEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndUpdate succeeds after retryable handshake network error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "findOneAndUpdate", + "object": "collection", + "arguments": { + "filter": {}, + "update": { + "$set": { + "x": 22 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandSucceededEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndUpdate succeeds after retryable handshake server error (ShutdownInProgress)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "findOneAndUpdate", + "object": "collection", + "arguments": { + "filter": {}, + "update": { + "$set": { + "x": 22 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandSucceededEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.bulkWrite succeeds after retryable handshake network error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "bulkWrite", + "object": "collection", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "collection.bulkWrite succeeds after retryable handshake server error (ShutdownInProgress)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "bulkWrite", + "object": "collection", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + } + ] + } + ] + } + ] +} diff --git a/testdata/retryable-writes/unified/handshakeError.yml b/testdata/retryable-writes/unified/handshakeError.yml new file mode 100644 index 0000000000..1743463370 --- /dev/null +++ b/testdata/retryable-writes/unified/handshakeError.yml @@ -0,0 +1,877 @@ +# Tests in this file are generated from handshakeError.yml.template. + +description: "retryable writes handshake failures" + +schemaVersion: "1.4" # For `serverless: forbid` + +runOnRequirements: + - minServerVersion: "4.2" + topologies: [replicaset, sharded, load-balanced] + auth: true + +createEntities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - connectionCheckOutStartedEvent + - commandStartedEvent + - commandSucceededEvent + - commandFailedEvent + - database: + id: &database database + client: *client + databaseName: &databaseName retryable-writes-handshake-tests + - collection: + id: &collection collection + database: *database + collectionName: &collectionName coll + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + +tests: + # Because setting a failPoint creates a connection in the connection pool, run + # a ping operation that fails immediately after the failPoint operation in + # order to discard the connection before running the actual operation to be + # tested. The saslContinue command is used to avoid SDAM errors. + # + # Description of events: + # - Failpoint operation. + # - Creates a connection in the connection pool that must be closed. + # - Ping operation. + # - Triggers failpoint (first time). + # - Closes the connection made by the fail point operation. + # - Test operation. + # - New connection is created. + # - Triggers failpoint (second time). + # - Tests whether operation successfully retries the handshake and succeeds. + + - description: "client.clientBulkWrite succeeds after retryable handshake network error" + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0 + serverless: forbid + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: clientBulkWrite + object: *client + arguments: + models: + - insertOne: + namespace: retryable-writes-handshake-tests.coll + document: { _id: 8, x: 88 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: bulkWrite + - commandSucceededEvent: + commandName: bulkWrite + + - description: "client.clientBulkWrite succeeds after retryable handshake server error (ShutdownInProgress)" + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0 + serverless: forbid + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: clientBulkWrite + object: *client + arguments: + models: + - insertOne: + namespace: retryable-writes-handshake-tests.coll + document: { _id: 8, x: 88 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: bulkWrite + - commandSucceededEvent: + commandName: bulkWrite + + - description: "collection.insertOne succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: insertOne + object: *collection + arguments: + document: { _id: 2, x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert + + - description: "collection.insertOne succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: insertOne + object: *collection + arguments: + document: { _id: 2, x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert + + - description: "collection.insertMany succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: insertMany + object: *collection + arguments: + documents: + - { _id: 2, x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert + + - description: "collection.insertMany succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: insertMany + object: *collection + arguments: + documents: + - { _id: 2, x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert + + - description: "collection.deleteOne succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: deleteOne + object: *collection + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: delete + - commandSucceededEvent: + commandName: delete + + - description: "collection.deleteOne succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: deleteOne + object: *collection + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: delete + - commandSucceededEvent: + commandName: delete + + - description: "collection.replaceOne succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: replaceOne + object: *collection + arguments: + filter: {} + replacement: { x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: update + - commandSucceededEvent: + commandName: update + + - description: "collection.replaceOne succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: replaceOne + object: *collection + arguments: + filter: {} + replacement: { x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: update + - commandSucceededEvent: + commandName: update + + - description: "collection.updateOne succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: updateOne + object: *collection + arguments: + filter: {} + update: { $set: { x: 22 } } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: update + - commandSucceededEvent: + commandName: update + + - description: "collection.updateOne succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: updateOne + object: *collection + arguments: + filter: {} + update: { $set: { x: 22 } } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: update + - commandSucceededEvent: + commandName: update + + - description: "collection.findOneAndDelete succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: findOneAndDelete + object: *collection + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: findAndModify + - commandSucceededEvent: + commandName: findAndModify + + - description: "collection.findOneAndDelete succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: findOneAndDelete + object: *collection + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: findAndModify + - commandSucceededEvent: + commandName: findAndModify + + - description: "collection.findOneAndReplace succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: findOneAndReplace + object: *collection + arguments: + filter: {} + replacement: { x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: findAndModify + - commandSucceededEvent: + commandName: findAndModify + + - description: "collection.findOneAndReplace succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: findOneAndReplace + object: *collection + arguments: + filter: {} + replacement: { x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: findAndModify + - commandSucceededEvent: + commandName: findAndModify + + - description: "collection.findOneAndUpdate succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: findOneAndUpdate + object: *collection + arguments: + filter: {} + update: { $set: { x: 22 } } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: findAndModify + - commandSucceededEvent: + commandName: findAndModify + + - description: "collection.findOneAndUpdate succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: findOneAndUpdate + object: *collection + arguments: + filter: {} + update: { $set: { x: 22 } } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: findAndModify + - commandSucceededEvent: + commandName: findAndModify + + - description: "collection.bulkWrite succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: bulkWrite + object: *collection + arguments: + requests: + - insertOne: + document: { _id: 2, x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert + + - description: "collection.bulkWrite succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: bulkWrite + object: *collection + arguments: + requests: + - insertOne: + document: { _id: 2, x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert diff --git a/testdata/server-selection/logging/operation-id.json b/testdata/server-selection/logging/operation-id.json index 276e4b8d6d..c1024184ff 100644 --- a/testdata/server-selection/logging/operation-id.json +++ b/testdata/server-selection/logging/operation-id.json @@ -47,6 +47,9 @@ } } ], + "_yamlAnchors": { + "namespace": "logging-tests.server-selection" + }, "tests": [ { "description": "Successful bulkWrite operation: log messages have operationIds", @@ -224,6 +227,192 @@ ] } ] + }, + { + "description": "Successful client bulkWrite operation: log messages have operationIds", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "operations": [ + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "topologyDescriptionChangedEvent": {} + }, + "count": 2 + } + }, + { + "name": "clientBulkWrite", + "object": "client", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "logging-tests.server-selection", + "document": { + "x": 1 + } + } + } + ] + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "serverSelection", + "data": { + "message": "Server selection started", + "operationId": { + "$$type": [ + "int", + "long" + ] + }, + "operation": "bulkWrite" + } + }, + { + "level": "debug", + "component": "serverSelection", + "data": { + "message": "Server selection succeeded", + "operationId": { + "$$type": [ + "int", + "long" + ] + }, + "operation": "bulkWrite" + } + } + ] + } + ] + }, + { + "description": "Failed client bulkWrite operation: log messages have operationIds", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "hello", + "ismaster" + ], + "appName": "loggingClient", + "closeConnection": true + } + } + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverDescriptionChangedEvent": { + "newDescription": { + "type": "Unknown" + } + } + }, + "count": 1 + } + }, + { + "name": "bulkWrite", + "object": "client", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "logging-tests.server-selection", + "document": { + "x": 1 + } + } + } + ] + }, + "expectError": { + "isClientError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "serverSelection", + "data": { + "message": "Server selection started", + "operationId": { + "$$type": [ + "int", + "long" + ] + }, + "operation": "bulkWrite" + } + }, + { + "level": "info", + "component": "serverSelection", + "data": { + "message": "Waiting for suitable server to become available", + "operationId": { + "$$type": [ + "int", + "long" + ] + }, + "operation": "bulkWrite" + } + }, + { + "level": "debug", + "component": "serverSelection", + "data": { + "message": "Server selection failed", + "operationId": { + "$$type": [ + "int", + "long" + ] + }, + "operation": "bulkWrite" + } + } + ] + } + ] } ] } diff --git a/testdata/server-selection/logging/operation-id.yml b/testdata/server-selection/logging/operation-id.yml index 21be7b7d14..1687fc392d 100644 --- a/testdata/server-selection/logging/operation-id.yml +++ b/testdata/server-selection/logging/operation-id.yml @@ -1,4 +1,3 @@ - description: "operation-id" schemaVersion: "1.14" @@ -31,6 +30,9 @@ createEntities: - client: id: &failPointClient failPointClient +_yamlAnchors: + namespace: &namespace "logging-tests.server-selection" + tests: - description: "Successful bulkWrite operation: log messages have operationIds" operations: @@ -122,3 +124,100 @@ tests: message: "Server selection failed" operationId: { $$type: [int, long] } operation: insert + + - description: "Successful client bulkWrite operation: log messages have operationIds" + runOnRequirements: + - minServerVersion: "8.0" # required for bulkWrite command + serverless: forbid + operations: + # ensure we've discovered the server so it is immediately available + # and no extra "waiting for suitable server" messages are emitted. + # expected topology events reflect initial server discovery and server connect event. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + topologyDescriptionChangedEvent: {} + count: 2 + - name: clientBulkWrite + object: *client + arguments: + models: + - insertOne: + namespace: *namespace + document: { x: 1 } + expectLogMessages: + - client: *client + messages: + - level: debug + component: serverSelection + data: + message: "Server selection started" + operationId: { $$type: [int, long] } + operation: bulkWrite + - level: debug + component: serverSelection + data: + message: "Server selection succeeded" + operationId: { $$type: [int, long] } + operation: bulkWrite + + - description: "Failed client bulkWrite operation: log messages have operationIds" + runOnRequirements: + - minServerVersion: "8.0" # required for bulkWrite command + serverless: forbid + operations: + # fail all hello/legacy hello commands for the main client. + - name: failPoint + object: testRunner + arguments: + client: *failPointClient + failPoint: + configureFailPoint: failCommand + mode: alwaysOn + data: + failCommands: ["hello", "ismaster"] + appName: *appName + closeConnection: true + # wait until we've marked the server unknown due + # to a failed heartbeat. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: bulkWrite + object: *client + arguments: + models: + - insertOne: + namespace: *namespace + document: { x: 1 } + expectError: + isClientError: true # server selection timeout + expectLogMessages: + - client: *client + messages: + - level: debug + component: serverSelection + data: + message: "Server selection started" + operationId: { $$type: [int, long] } + operation: bulkWrite + - level: info + component: serverSelection + data: + message: "Waiting for suitable server to become available" + operationId: { $$type: [int, long] } + operation: bulkWrite + - level: debug + component: serverSelection + data: + message: "Server selection failed" + operationId: { $$type: [int, long] } + operation: bulkWrite diff --git a/testdata/transactions/unified/client-bulkWrite.json b/testdata/transactions/unified/client-bulkWrite.json new file mode 100644 index 0000000000..4a8d013f8d --- /dev/null +++ b/testdata/transactions/unified/client-bulkWrite.json @@ -0,0 +1,593 @@ +{ + "description": "client bulkWrite transactions", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ], + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + }, + { + "client": { + "id": "client_with_wmajority", + "uriOptions": { + "w": "majority" + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "session": { + "id": "session_with_wmajority", + "client": "client_with_wmajority" + } + } + ], + "_yamlAnchors": { + "namespace": "transaction-tests.coll0" + }, + "initialData": [ + { + "databaseName": "transaction-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 5, + "x": 55 + }, + { + "_id": 6, + "x": 66 + }, + { + "_id": 7, + "x": 77 + } + ] + } + ], + "tests": [ + { + "description": "client bulkWrite in a transaction", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "session": "session0", + "models": [ + { + "insertOne": { + "namespace": "transaction-tests.coll0", + "document": { + "_id": 8, + "x": 88 + } + } + }, + { + "updateOne": { + "namespace": "transaction-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateMany": { + "namespace": "transaction-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$inc": { + "x": 2 + } + } + } + }, + { + "replaceOne": { + "namespace": "transaction-tests.coll0", + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "upsert": true + } + }, + { + "deleteOne": { + "namespace": "transaction-tests.coll0", + "filter": { + "_id": 5 + } + } + }, + { + "deleteMany": { + "namespace": "transaction-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 1, + "matchedCount": 3, + "modifiedCount": 3, + "deletedCount": 3, + "insertResults": { + "0": { + "insertedId": 8 + } + }, + "updateResults": { + "1": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "2": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedId": { + "$$exists": false + } + }, + "3": { + "matchedCount": 1, + "modifiedCount": 0, + "upsertedId": 4 + } + }, + "deleteResults": { + "4": { + "deletedCount": 1 + }, + "5": { + "deletedCount": 2 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": 1, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 8, + "x": 88 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$inc": { + "x": 2 + } + }, + "multi": true + }, + { + "update": 0, + "filter": { + "_id": 4 + }, + "updateMods": { + "x": 44 + }, + "upsert": true, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 5 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + }, + "multi": true + } + ], + "nsInfo": [ + { + "ns": "transaction-tests.coll0" + } + ] + } + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction", + "databaseName": "admin", + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": 1, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 24 + }, + { + "_id": 3, + "x": 35 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 8, + "x": 88 + } + ] + } + ] + }, + { + "description": "client writeConcern ignored for client bulkWrite in transaction", + "operations": [ + { + "object": "session_with_wmajority", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": 1 + } + } + }, + { + "object": "client_with_wmajority", + "name": "clientBulkWrite", + "arguments": { + "session": "session_with_wmajority", + "models": [ + { + "insertOne": { + "namespace": "transaction-tests.coll0", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + } + }, + { + "object": "session_with_wmajority", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client_with_wmajority", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "lsid": { + "$$sessionLsid": "session_with_wmajority" + }, + "txnNumber": 1, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 8, + "x": 88 + } + } + ], + "nsInfo": [ + { + "ns": "transaction-tests.coll0" + } + ] + } + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session_with_wmajority" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": 1 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 5, + "x": 55 + }, + { + "_id": 6, + "x": 66 + }, + { + "_id": 7, + "x": 77 + }, + { + "_id": 8, + "x": 88 + } + ] + } + ] + }, + { + "description": "client bulkWrite with writeConcern in a transaction causes a transaction error", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "session": "session0", + "writeConcern": { + "w": 1 + }, + "models": [ + { + "insertOne": { + "namespace": "transaction-tests.coll0", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + }, + "expectError": { + "isClientError": true, + "errorContains": "Cannot set write concern after starting a transaction" + } + } + ] + } + ] +} diff --git a/testdata/transactions/unified/client-bulkWrite.yml b/testdata/transactions/unified/client-bulkWrite.yml new file mode 100644 index 0000000000..d80e618728 --- /dev/null +++ b/testdata/transactions/unified/client-bulkWrite.yml @@ -0,0 +1,263 @@ +description: "client bulkWrite transactions" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + topologies: + - replicaset + - sharded + - load-balanced + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name transaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + - session: + id: &session0 session0 + client: *client0 + - client: + id: &client_with_wmajority client_with_wmajority + uriOptions: + w: majority + observeEvents: + - commandStartedEvent + - session: + id: &session_with_wmajority session_with_wmajority + client: *client_with_wmajority + +_yamlAnchors: + namespace: &namespace "transaction-tests.coll0" + +initialData: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 5, x: 55 } + - { _id: 6, x: 66 } + - { _id: 7, x: 77 } + +tests: + - description: "client bulkWrite in a transaction" + operations: + - object: *session0 + name: startTransaction + - object: *client0 + name: clientBulkWrite + arguments: + session: *session0 + models: + - insertOne: + namespace: *namespace + document: { _id: 8, x: 88 } + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: { $inc: { x: 1 } } + - updateMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: { $inc: { x: 2 } } + - replaceOne: + namespace: *namespace + filter: { _id: 4 } + replacement: { x: 44 } + upsert: true + - deleteOne: + namespace: *namespace + filter: { _id: 5 } + - deleteMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 1 + matchedCount: 3 + modifiedCount: 3 + deletedCount: 3 + insertResults: + 0: + insertedId: 8 + updateResults: + 1: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 2: + matchedCount: 2 + modifiedCount: 2 + upsertedId: { $$exists: false } + 3: + matchedCount: 1 + modifiedCount: 0 + upsertedId: 4 + deleteResults: + 4: + deletedCount: 1 + 5: + deletedCount: 2 + - object: *session0 + name: commitTransaction + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + lsid: { $$sessionLsid: *session0 } + txnNumber: 1 + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 8, x: 88 } + - update: 0 + filter: { _id: 1 } + updateMods: { $inc: { x: 1 } } + multi: false + - update: 0 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + updateMods: { $inc: { x: 2 } } + multi: true + - update: 0 + filter: { _id: 4 } + updateMods: { x: 44 } + upsert: true + multi: false + - delete: 0 + filter: { _id: 5 } + multi: false + - delete: 0 + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + multi: true + nsInfo: + - ns: *namespace + - commandStartedEvent: + commandName: commitTransaction + databaseName: admin + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: 1 + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 24 } + - { _id: 3, x: 35 } + - { _id: 4, x: 44 } + - { _id: 8, x: 88 } + - description: 'client writeConcern ignored for client bulkWrite in transaction' + operations: + - object: *session_with_wmajority + name: startTransaction + arguments: + writeConcern: + w: 1 + - object: *client_with_wmajority + name: clientBulkWrite + arguments: + session: *session_with_wmajority + models: + - insertOne: + namespace: *namespace + document: { _id: 8, x: 88 } + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} + - object: *session_with_wmajority + name: commitTransaction + expectEvents: + - + client: *client_with_wmajority + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + lsid: { $$sessionLsid: *session_with_wmajority } + txnNumber: 1 + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + bulkWrite: 1 + errorsOnly: true + ordered: true + ops: + - insert: 0 + document: { _id: 8, x: 88 } + nsInfo: + - ns: *namespace + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session_with_wmajority } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: 1 + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 5, x: 55 } + - { _id: 6, x: 66 } + - { _id: 7, x: 77 } + - { _id: 8, x: 88 } + - description: "client bulkWrite with writeConcern in a transaction causes a transaction error" + operations: + - object: *session0 + name: startTransaction + - object: *client0 + name: clientBulkWrite + arguments: + session: *session0 + writeConcern: + w: 1 + models: + - insertOne: + namespace: *namespace + document: { _id: 8, x: 88 } + expectError: + isClientError: true + errorContains: "Cannot set write concern after starting a transaction" diff --git a/testdata/transactions/unified/mongos-pin-auto.json b/testdata/transactions/unified/mongos-pin-auto.json new file mode 100644 index 0000000000..27db520401 --- /dev/null +++ b/testdata/transactions/unified/mongos-pin-auto.json @@ -0,0 +1,5474 @@ +{ + "description": "mongos-pin-auto", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded" + ], + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "tests": [ + { + "description": "remain pinned after non-transient Interrupted error on insertOne", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "object": "testRunner", + "name": "targetedFailPoint", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11601 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError", + "UnknownTransactionCommitResult" + ], + "errorCodeName": "Interrupted" + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 3 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 4 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ] + }, + { + "description": "unpin after transient error within a transaction", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "object": "testRunner", + "name": "targetedFailPoint", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 3 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 4 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on insertOne insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on insertMany insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "insertMany", + "object": "collection0", + "arguments": { + "session": "session0", + "documents": [ + { + "_id": 4 + }, + { + "_id": 5 + } + ] + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on updateOne update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on replaceOne update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "replaceOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "replacement": { + "y": 1 + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on updateMany update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "updateMany", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gte": 1 + } + }, + "update": { + "$set": { + "z": 1 + } + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on deleteOne delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "deleteOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on deleteMany delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "deleteMany", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gte": 1 + } + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on findOneAndDelete findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "findOneAndDelete", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on findOneAndUpdate findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on findOneAndReplace findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "findOneAndReplace", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "replacement": { + "y": 1 + }, + "returnDocument": "Before" + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on bulkWrite insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "insertOne": { + "document": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on bulkWrite update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "updateOne": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 1 + } + } + } + } + ] + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on bulkWrite delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on find find", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on countDocuments aggregate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "countDocuments", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": {} + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on aggregate aggregate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "session": "session0", + "pipeline": [] + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on distinct distinct", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "distinct", + "object": "collection0", + "arguments": { + "session": "session0", + "fieldName": "_id", + "filter": {} + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on runCommand insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "runCommand", + "object": "database0", + "arguments": { + "session": "session0", + "commandName": "insert", + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ] + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on clientBulkWrite bulkWrite", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "session": "session0", + "models": [ + { + "insertOne": { + "namespace": "database0.collection0", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ] + }, + { + "description": "unpin after transient connection error on insertOne insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on insertOne insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on insertMany insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "name": "insertMany", + "object": "collection0", + "arguments": { + "session": "session0", + "documents": [ + { + "_id": 4 + }, + { + "_id": 5 + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on insertMany insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "insertMany", + "object": "collection0", + "arguments": { + "session": "session0", + "documents": [ + { + "_id": 4 + }, + { + "_id": 5 + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on updateOne update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "closeConnection": true + } + } + } + }, + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on updateOne update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on replaceOne update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "closeConnection": true + } + } + } + }, + { + "name": "replaceOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "replacement": { + "y": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on replaceOne update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "replaceOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "replacement": { + "y": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on updateMany update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "closeConnection": true + } + } + } + }, + { + "name": "updateMany", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gte": 1 + } + }, + "update": { + "$set": { + "z": 1 + } + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on updateMany update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "updateMany", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gte": 1 + } + }, + "update": { + "$set": { + "z": 1 + } + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on deleteOne delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "closeConnection": true + } + } + } + }, + { + "name": "deleteOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on deleteOne delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "deleteOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on deleteMany delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "closeConnection": true + } + } + } + }, + { + "name": "deleteMany", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gte": 1 + } + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on deleteMany delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "deleteMany", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gte": 1 + } + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on findOneAndDelete findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "closeConnection": true + } + } + } + }, + { + "name": "findOneAndDelete", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on findOneAndDelete findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "findOneAndDelete", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on findOneAndUpdate findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "closeConnection": true + } + } + } + }, + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on findOneAndUpdate findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on findOneAndReplace findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "closeConnection": true + } + } + } + }, + { + "name": "findOneAndReplace", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "replacement": { + "y": 1 + }, + "returnDocument": "Before" + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on findOneAndReplace findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "findOneAndReplace", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "replacement": { + "y": 1 + }, + "returnDocument": "Before" + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on bulkWrite insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "insertOne": { + "document": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on bulkWrite insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "insertOne": { + "document": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on bulkWrite update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "closeConnection": true + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "updateOne": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 1 + } + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on bulkWrite update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "updateOne": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 1 + } + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on bulkWrite delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "closeConnection": true + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on bulkWrite delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on find find", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on find find", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on countDocuments aggregate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "name": "countDocuments", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": {} + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on countDocuments aggregate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "countDocuments", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": {} + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on aggregate aggregate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "session": "session0", + "pipeline": [] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on aggregate aggregate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "session": "session0", + "pipeline": [] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on distinct distinct", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "closeConnection": true + } + } + } + }, + { + "name": "distinct", + "object": "collection0", + "arguments": { + "session": "session0", + "fieldName": "_id", + "filter": {} + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on distinct distinct", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "distinct", + "object": "collection0", + "arguments": { + "session": "session0", + "fieldName": "_id", + "filter": {} + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on runCommand insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database0", + "arguments": { + "session": "session0", + "commandName": "insert", + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ] + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on runCommand insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "runCommand", + "object": "database0", + "arguments": { + "session": "session0", + "commandName": "insert", + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ] + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on clientBulkWrite bulkWrite", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "closeConnection": true + } + } + } + }, + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "session": "session0", + "models": [ + { + "insertOne": { + "namespace": "database0.collection0", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on clientBulkWrite bulkWrite", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "session": "session0", + "models": [ + { + "insertOne": { + "namespace": "database0.collection0", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ] + } + ] +} diff --git a/testdata/transactions/unified/mongos-pin-auto.yml b/testdata/transactions/unified/mongos-pin-auto.yml new file mode 100644 index 0000000000..db620c371b --- /dev/null +++ b/testdata/transactions/unified/mongos-pin-auto.yml @@ -0,0 +1,1705 @@ +# Autogenerated tests that transient errors in a transaction unpin the session. +# See mongos-pin-auto-tests.py + +description: mongos-pin-auto + +schemaVersion: '1.4' + +runOnRequirements: + - minServerVersion: "4.1.8" + # Note: tests utilize targetedFailPoint, which is incompatible with + # load-balanced and useMultipleMongoses:true + topologies: [ sharded ] + # serverless proxy doesn't append error labels to errors in transactions + # caused by failpoints (CLOUDP-88216) + serverless: "forbid" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collection_name + databaseName: *database_name + documents: &data + - { _id: 1 } + - { _id: 2 } + +tests: + - description: remain pinned after non-transient Interrupted error on insertOne + operations: + - &startTransaction + object: session0 + name: startTransaction + - &initialCommand + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 3 } + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } } + - object: testRunner + name: targetedFailPoint + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ "insert" ] + errorCode: 11601 + - object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] + errorCodeName: Interrupted + - &assertSessionPinned + object: testRunner + name: assertSessionPinned + arguments: + session: *session0 + - &commitTransaction + object: *session0 + name: commitTransaction + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 3 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + + - description: 'unpin after transient error within a transaction' + operations: + - *startTransaction + - *initialCommand + - object: testRunner + name: targetedFailPoint + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ "insert" ] + closeConnection: true + - object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + errorLabelsOmit: ["UnknownTransactionCommitResult"] + - &assertSessionUnpinned + object: testRunner + name: assertSessionUnpinned + arguments: + session: *session0 + - &abortTransaction + object: *session0 + name: abortTransaction + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 3 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: abortTransaction + databaseName: admin + outcome: &outcome + - collectionName: *collection_name + databaseName: *database_name + documents: *data + + # The rest of the tests in this file test every operation type against + # multiple types of transient errors (connection and error code). + + - description: remain pinned after non-transient Interrupted error on insertOne insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 11601 + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on insertMany insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 11601 + - name: insertMany + object: *collection0 + arguments: + session: *session0 + documents: [ { _id: 4 }, { _id: 5 } ] + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on updateOne update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 11601 + - name: updateOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on replaceOne update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 11601 + - name: replaceOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on updateMany update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 11601 + - name: updateMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gte: 1 } } + update: {$set: { z: 1 } } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on deleteOne delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + errorCode: 11601 + - name: deleteOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on deleteMany delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + errorCode: 11601 + - name: deleteMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gte: 1 } } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on findOneAndDelete findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + errorCode: 11601 + - name: findOneAndDelete + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on findOneAndUpdate findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + errorCode: 11601 + - name: findOneAndUpdate + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on findOneAndReplace findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + errorCode: 11601 + - name: findOneAndReplace + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + returnDocument: Before + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on bulkWrite insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 11601 + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - insertOne: + document: { _id: 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on bulkWrite update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 11601 + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - updateOne: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on bulkWrite delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + errorCode: 11601 + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - deleteOne: + filter: { _id: 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on find find + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["find"] + errorCode: 11601 + - name: find + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on countDocuments aggregate + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["aggregate"] + errorCode: 11601 + - name: countDocuments + object: *collection0 + arguments: + session: *session0 + filter: {} + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on aggregate aggregate + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["aggregate"] + errorCode: 11601 + - name: aggregate + object: *collection0 + arguments: + session: *session0 + pipeline: [] + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on distinct distinct + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["distinct"] + errorCode: 11601 + - name: distinct + object: *collection0 + arguments: + session: *session0 + fieldName: _id + filter: {} + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on runCommand insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 11601 + - name: runCommand + object: *database0 + arguments: + session: *session0 + commandName: insert + command: + insert: *collection_name + documents: + - { _id : 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on clientBulkWrite bulkWrite + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["bulkWrite"] + errorCode: 11601 + - name: clientBulkWrite + object: *client0 + arguments: + session: *session0 + models: + - insertOne: + namespace: database0.collection0 + document: { _id: 8, x: 88 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0" + + - description: unpin after transient connection error on insertOne insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + closeConnection: true + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on insertOne insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 91 + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on insertMany insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + closeConnection: true + - name: insertMany + object: *collection0 + arguments: + session: *session0 + documents: [ { _id: 4 }, { _id: 5 } ] + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on insertMany insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 91 + - name: insertMany + object: *collection0 + arguments: + session: *session0 + documents: [ { _id: 4 }, { _id: 5 } ] + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on updateOne update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + closeConnection: true + - name: updateOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on updateOne update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 91 + - name: updateOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on replaceOne update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + closeConnection: true + - name: replaceOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on replaceOne update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 91 + - name: replaceOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on updateMany update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + closeConnection: true + - name: updateMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gte: 1 } } + update: {$set: { z: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on updateMany update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 91 + - name: updateMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gte: 1 } } + update: {$set: { z: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on deleteOne delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + closeConnection: true + - name: deleteOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on deleteOne delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + errorCode: 91 + - name: deleteOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on deleteMany delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + closeConnection: true + - name: deleteMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gte: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on deleteMany delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + errorCode: 91 + - name: deleteMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gte: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on findOneAndDelete findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + closeConnection: true + - name: findOneAndDelete + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on findOneAndDelete findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + errorCode: 91 + - name: findOneAndDelete + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on findOneAndUpdate findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + closeConnection: true + - name: findOneAndUpdate + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on findOneAndUpdate findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + errorCode: 91 + - name: findOneAndUpdate + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on findOneAndReplace findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + closeConnection: true + - name: findOneAndReplace + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + returnDocument: Before + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on findOneAndReplace findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + errorCode: 91 + - name: findOneAndReplace + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + returnDocument: Before + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on bulkWrite insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + closeConnection: true + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - insertOne: + document: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on bulkWrite insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 91 + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - insertOne: + document: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on bulkWrite update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + closeConnection: true + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - updateOne: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on bulkWrite update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 91 + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - updateOne: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on bulkWrite delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + closeConnection: true + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - deleteOne: + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on bulkWrite delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + errorCode: 91 + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - deleteOne: + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on find find + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["find"] + closeConnection: true + - name: find + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on find find + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["find"] + errorCode: 91 + - name: find + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on countDocuments aggregate + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["aggregate"] + closeConnection: true + - name: countDocuments + object: *collection0 + arguments: + session: *session0 + filter: {} + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on countDocuments aggregate + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["aggregate"] + errorCode: 91 + - name: countDocuments + object: *collection0 + arguments: + session: *session0 + filter: {} + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on aggregate aggregate + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["aggregate"] + closeConnection: true + - name: aggregate + object: *collection0 + arguments: + session: *session0 + pipeline: [] + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on aggregate aggregate + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["aggregate"] + errorCode: 91 + - name: aggregate + object: *collection0 + arguments: + session: *session0 + pipeline: [] + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on distinct distinct + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["distinct"] + closeConnection: true + - name: distinct + object: *collection0 + arguments: + session: *session0 + fieldName: _id + filter: {} + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on distinct distinct + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["distinct"] + errorCode: 91 + - name: distinct + object: *collection0 + arguments: + session: *session0 + fieldName: _id + filter: {} + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on runCommand insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + closeConnection: true + - name: runCommand + object: *database0 + arguments: + session: *session0 + commandName: insert + command: + insert: *collection_name + documents: + - { _id : 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on runCommand insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 91 + - name: runCommand + object: *database0 + arguments: + session: *session0 + commandName: insert + command: + insert: *collection_name + documents: + - { _id : 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on clientBulkWrite bulkWrite + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["bulkWrite"] + closeConnection: true + - name: clientBulkWrite + object: *client0 + arguments: + session: *session0 + models: + - insertOne: + namespace: database0.collection0 + document: { _id: 8, x: 88 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0" + + - description: unpin after transient ShutdownInProgress error on clientBulkWrite bulkWrite + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["bulkWrite"] + errorCode: 91 + - name: clientBulkWrite + object: *client0 + arguments: + session: *session0 + models: + - insertOne: + namespace: database0.collection0 + document: { _id: 8, x: 88 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0" From 246360d4baa6fd7a69ce7d77a0f7ca8baf29376e Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Wed, 25 Sep 2024 15:00:38 -0400 Subject: [PATCH 02/14] WIP --- mongo/bulk_write.go | 4 +- mongo/client.go | 73 +++++- mongo/client_bulk_write.go | 321 +++++++++++++++++++++++ mongo/client_bulk_write_models.go | 335 ++++++++++++++++++++++++ mongo/errors.go | 47 ++++ mongo/options/clientbulkwriteoptions.go | 119 +++++++++ mongo/results.go | 56 ++++ x/mongo/driver/operation/command.go | 45 +++- 8 files changed, 983 insertions(+), 17 deletions(-) create mode 100644 mongo/client_bulk_write.go create mode 100644 mongo/client_bulk_write_models.go create mode 100644 mongo/options/clientbulkwriteoptions.go diff --git a/mongo/bulk_write.go b/mongo/bulk_write.go index 40f1181e0e..402497b13e 100644 --- a/mongo/bulk_write.go +++ b/mongo/bulk_write.go @@ -164,8 +164,7 @@ func (bw *bulkWrite) runBatch(ctx context.Context, batch bulkWriteBatch) (BulkWr func (bw *bulkWrite) runInsert(ctx context.Context, batch bulkWriteBatch) (operation.InsertResult, error) { docs := make([]bsoncore.Document, len(batch.models)) - var i int - for _, model := range batch.models { + for i, model := range batch.models { converted := model.(*InsertOneModel) doc, err := marshal(converted.Document, bw.collection.bsonOpts, bw.collection.registry) if err != nil { @@ -177,7 +176,6 @@ func (bw *bulkWrite) runInsert(ctx context.Context, batch bulkWriteBatch) (opera } docs[i] = doc - i++ } op := operation.NewInsert(docs...). diff --git a/mongo/client.go b/mongo/client.go index 0ce6d2e24b..41112fd6fc 100644 --- a/mongo/client.go +++ b/mongo/client.go @@ -73,14 +73,15 @@ type Client struct { logger *logger.Logger // client-side encryption fields - keyVaultClientFLE *Client - keyVaultCollFLE *Collection - mongocryptdFLE *mongocryptdClient - cryptFLE driver.Crypt - metadataClientFLE *Client - internalClientFLE *Client - encryptedFieldsMap map[string]interface{} - authenticator driver.Authenticator + isAutoEncryptionSet bool + keyVaultClientFLE *Client + keyVaultCollFLE *Collection + mongocryptdFLE *mongocryptdClient + cryptFLE driver.Crypt + metadataClientFLE *Client + internalClientFLE *Client + encryptedFieldsMap map[string]interface{} + authenticator driver.Authenticator } // Connect creates a new Client and then initializes it using the Connect method. This is equivalent to calling @@ -194,6 +195,7 @@ func NewClient(opts ...*options.ClientOptions) (*Client, error) { } // AutoEncryptionOptions if clientOpt.AutoEncryptionOptions != nil { + client.isAutoEncryptionSet = true if err := client.configureAutoEncryption(clientOpt); err != nil { return nil, err } @@ -851,6 +853,61 @@ func (c *Client) createBaseCursorOptions() driver.CursorOptions { } } +// BulkWrite performs a client-levelbulk write operation. +func (c *Client) BulkWrite(ctx context.Context, models *ClientWriteModels, + opts ...*options.ClientBulkWriteOptions) (*ClientBulkWriteResult, error) { + if c.isAutoEncryptionSet { + return nil, errors.New("bulkWrite does not currently support automatic encryption") + } + bwo := options.MergeClientBulkWriteOptions(opts...) + + if ctx == nil { + ctx = context.Background() + } + + sess := sessionFromContext(ctx) + if sess == nil && c.sessionPool != nil { + sess = session.NewImplicitClientSession(c.sessionPool, c.id) + defer sess.EndSession() + } + + err := c.validSession(sess) + if err != nil { + return nil, err + } + + wc := c.writeConcern + if sess.TransactionRunning() { + wc = nil + } + if !writeconcern.AckWrite(wc) { + sess = nil + } + + writeSelector := description.CompositeSelector([]description.ServerSelector{ + description.WriteSelector(), + description.LatencySelector(c.localThreshold), + }) + selector := makePinnedSelector(sess, writeSelector) + + op := clientBulkWrite{ + models: models.models, + ordered: bwo.Ordered, + bypassDocumentValidation: bwo.BypassDocumentValidation, + comment: bwo.Comment, + let: bwo.Let, + session: sess, + client: c, + selector: selector, + writeConcern: wc, + } + if bwo.VerboseResults == nil || !(*bwo.VerboseResults) { + op.errorsOnly = true + } + err = op.execute(ctx) + return op.result, replaceErrors(err) +} + // newLogger will use the LoggerOptions to create an internal logger and publish // messages using a LogSink. func newLogger(opts *options.LoggerOptions) (*logger.Logger, error) { diff --git a/mongo/client_bulk_write.go b/mongo/client_bulk_write.go new file mode 100644 index 0000000000..da9d1422a0 --- /dev/null +++ b/mongo/client_bulk_write.go @@ -0,0 +1,321 @@ +// Copyright (C) MongoDB, Inc. 2017-present. +// +// 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 + +package mongo + +import ( + "context" + "strconv" + + "go.mongodb.org/mongo-driver/bson/bsoncodec" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo/description" + "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/mongo/writeconcern" + "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" + "go.mongodb.org/mongo-driver/x/mongo/driver" + "go.mongodb.org/mongo-driver/x/mongo/driver/operation" + "go.mongodb.org/mongo-driver/x/mongo/driver/session" +) + +// bulkWrite performs a bulkwrite operation +type clientBulkWrite struct { + models []interface{} + errorsOnly bool + ordered *bool + bypassDocumentValidation *bool + comment interface{} + let interface{} + + session *session.Client + client *Client + selector description.ServerSelector + writeConcern *writeconcern.WriteConcern + + result *ClientBulkWriteResult +} + +func (bw *clientBulkWrite) execute(ctx context.Context) error { + batches := &driver.Batches{ + Ordered: bw.ordered, + } + op := operation.NewCommandFn(bw.command).Batches(batches). + Session(bw.session).WriteConcern(bw.writeConcern).CommandMonitor(bw.client.monitor). + ServerSelector(bw.selector).ClusterClock(bw.client.clock). + Database("admin"). + Deployment(bw.client.deployment).Crypt(bw.client.cryptFLE). + ServerAPI(bw.client.serverAPI).Timeout(bw.client.timeout). + Logger(bw.client.logger).Authenticator(bw.client.authenticator) + err := op.Execute(ctx) + bw.result = newClientBulkWriteResult(op.Result()) + return err +} + +func (bw *clientBulkWrite) command(dst []byte, desc description.SelectedServer) ([]byte, error) { + dst = bsoncore.AppendInt32Element(dst, "bulkWrite", 1) + nsMap := make(map[string]int) + var nsList []string + getNsIndex := func(namespace string) int { + if v, ok := nsMap[namespace]; ok { + return v + } else { + nsIdx := len(nsList) + nsMap[namespace] = nsIdx + nsList = append(nsList, namespace) + return nsIdx + } + } + var err error + var idx int32 + idx, dst = bsoncore.AppendArrayElementStart(dst, "ops") + for i, v := range bw.models { + var doc bsoncore.Document + var nsIdx int + switch model := v.(type) { + case ClientInsertOneModel: + nsIdx = getNsIndex(model.Namespace) + doc, err = createClientInsertDoc(int32(nsIdx), model.Document, bw.client.bsonOpts, bw.client.registry) + if err != nil { + break + } + doc, _, err = ensureID(doc, primitive.NilObjectID, bw.client.bsonOpts, bw.client.registry) + case ClientUpdateOneModel: + nsIdx = getNsIndex(model.Namespace) + doc, err = createClientUpdateDoc( + int32(nsIdx), + model.Filter, + model.Update, + model.Hint, + model.ArrayFilters, + model.Collation, + model.Upsert, + false, + true, + bw.client.bsonOpts, + bw.client.registry) + case ClientUpdateManyModel: + nsIdx = getNsIndex(model.Namespace) + doc, err = createClientUpdateDoc( + int32(nsIdx), + model.Filter, + model.Update, + model.Hint, + model.ArrayFilters, + model.Collation, + model.Upsert, + true, + true, + bw.client.bsonOpts, + bw.client.registry) + case ClientReplaceOneModel: + nsIdx = getNsIndex(model.Namespace) + doc, err = createClientUpdateDoc( + int32(nsIdx), + model.Filter, + model.Replacement, + model.Hint, + nil, + model.Collation, + model.Upsert, + false, + false, + bw.client.bsonOpts, + bw.client.registry) + case ClientDeleteOneModel: + nsIdx = getNsIndex(model.Namespace) + doc, err = createClientDeleteDoc( + int32(nsIdx), + model.Filter, + model.Collation, + model.Hint, + true, + bw.client.bsonOpts, + bw.client.registry) + case ClientDeleteManyModel: + nsIdx = getNsIndex(model.Namespace) + doc, err = createClientDeleteDoc( + int32(nsIdx), + model.Filter, + model.Collation, + model.Hint, + false, + bw.client.bsonOpts, + bw.client.registry) + } + if err != nil { + return nil, err + } + dst = bsoncore.AppendDocumentElement(dst, strconv.Itoa(i), doc) + } + dst, err = bsoncore.AppendArrayEnd(dst, idx) + if err != nil { + return nil, err + } + + idx, dst = bsoncore.AppendArrayElementStart(dst, "nsInfo") + for i, v := range nsList { + doc, err := marshal(struct { + Namespace string `bson:"ns"` + }{v}, bw.client.bsonOpts, bw.client.registry) + if err != nil { + return nil, err + } + dst = bsoncore.AppendDocumentElement(dst, strconv.Itoa(i), doc) + } + dst, err = bsoncore.AppendArrayEnd(dst, idx) + if err != nil { + return nil, err + } + + if bw.errorsOnly { + dst = bsoncore.AppendBooleanElement(dst, "errorsOnly", bw.errorsOnly) + } + if bw.bypassDocumentValidation != nil && (desc.WireVersion != nil && desc.WireVersion.Includes(4)) { + dst = bsoncore.AppendBooleanElement(dst, "bypassDocumentValidation", *bw.bypassDocumentValidation) + } + if bw.comment != nil { + comment, err := marshalValue(bw.comment, bw.client.bsonOpts, bw.client.registry) + if err != nil { + return nil, err + } + dst = bsoncore.AppendValueElement(dst, "comment", comment) + } + if bw.ordered != nil { + dst = bsoncore.AppendBooleanElement(dst, "ordered", *bw.ordered) + } + if bw.let != nil { + let, err := marshal(bw.let, bw.client.bsonOpts, bw.client.registry) + if err != nil { + return nil, err + } + dst = bsoncore.AppendDocumentElement(dst, "let", let) + } + return dst, nil +} + +func createClientInsertDoc( + namespace int32, + document interface{}, + bsonOpts *options.BSONOptions, + registry *bsoncodec.Registry, +) (bsoncore.Document, error) { + uidx, insertDoc := bsoncore.AppendDocumentStart(nil) + + insertDoc = bsoncore.AppendInt32Element(insertDoc, "update", namespace) + f, err := marshal(document, bsonOpts, registry) + if err != nil { + return nil, err + } + insertDoc = bsoncore.AppendDocumentElement(insertDoc, "document", f) + + return bsoncore.AppendDocumentEnd(insertDoc, uidx) +} + +func createClientUpdateDoc( + namespace int32, + filter interface{}, + update interface{}, + hint interface{}, + arrayFilters *options.ArrayFilters, + collation *options.Collation, + upsert *bool, + multi bool, + checkDollarKey bool, + bsonOpts *options.BSONOptions, + registry *bsoncodec.Registry, +) (bsoncore.Document, error) { + uidx, doc := bsoncore.AppendDocumentStart(nil) + + doc = bsoncore.AppendInt32Element(doc, "update", namespace) + + f, err := marshal(filter, bsonOpts, registry) + if err != nil { + return nil, err + } + doc = bsoncore.AppendDocumentElement(doc, "filter", f) + + u, err := marshalUpdateValue(update, bsonOpts, registry, checkDollarKey) + if err != nil { + return nil, err + } + doc = bsoncore.AppendValueElement(doc, "updateMods", u) + + if multi { + doc = bsoncore.AppendBooleanElement(doc, "multi", multi) + } + + if arrayFilters != nil { + reg := registry + if arrayFilters.Registry != nil { + reg = arrayFilters.Registry + } + arr, err := marshalValue(arrayFilters.Filters, bsonOpts, reg) + if err != nil { + return nil, err + } + doc = bsoncore.AppendArrayElement(doc, "arrayFilters", arr.Data) + } + + if collation != nil { + doc = bsoncore.AppendDocumentElement(doc, "collation", bsoncore.Document(collation.ToDocument())) + } + + if upsert != nil { + doc = bsoncore.AppendBooleanElement(doc, "upsert", *upsert) + } + + if hint != nil { + if isUnorderedMap(hint) { + return nil, ErrMapForOrderedArgument{"hint"} + } + hintVal, err := marshalValue(hint, bsonOpts, registry) + if err != nil { + return nil, err + } + doc = bsoncore.AppendValueElement(doc, "hint", hintVal) + } + + return bsoncore.AppendDocumentEnd(doc, uidx) +} + +func createClientDeleteDoc( + namespace int32, + filter interface{}, + collation *options.Collation, + hint interface{}, + multi bool, + bsonOpts *options.BSONOptions, + registry *bsoncodec.Registry, +) (bsoncore.Document, error) { + didx, doc := bsoncore.AppendDocumentStart(nil) + + doc = bsoncore.AppendInt32Element(doc, "delete", namespace) + + f, err := marshal(filter, bsonOpts, registry) + if err != nil { + return nil, err + } + doc = bsoncore.AppendDocumentElement(doc, "filter", f) + + if multi { + doc = bsoncore.AppendBooleanElement(doc, "multi", multi) + } + + if collation != nil { + doc = bsoncore.AppendDocumentElement(doc, "collation", collation.ToDocument()) + } + if hint != nil { + if isUnorderedMap(hint) { + return nil, ErrMapForOrderedArgument{"hint"} + } + hintVal, err := marshalValue(hint, bsonOpts, registry) + if err != nil { + return nil, err + } + doc = bsoncore.AppendValueElement(doc, "hint", hintVal) + } + return bsoncore.AppendDocumentEnd(doc, didx) +} diff --git a/mongo/client_bulk_write_models.go b/mongo/client_bulk_write_models.go new file mode 100644 index 0000000000..b389019bb2 --- /dev/null +++ b/mongo/client_bulk_write_models.go @@ -0,0 +1,335 @@ +// Copyright (C) MongoDB, Inc. 2017-present. +// +// 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 + +package mongo + +import ( + "go.mongodb.org/mongo-driver/mongo/options" +) + +// ClientWriteModels is a struct that can be used in a client-level BulkWrite operation. +type ClientWriteModels struct { + models []interface{} +} + +func (m *ClientWriteModels) AppendInsertOne(models ...ClientInsertOneModel) *ClientWriteModels { + if m == nil { + m = &ClientWriteModels{} + } + for _, model := range models { + m.models = append(m.models, model) + } + return m +} + +func (m *ClientWriteModels) AppendUpdateOne(models ...ClientUpdateOneModel) *ClientWriteModels { + if m == nil { + m = &ClientWriteModels{} + } + for _, model := range models { + m.models = append(m.models, model) + } + return m +} + +func (m *ClientWriteModels) AppendUpdateMany(models ...ClientUpdateManyModel) *ClientWriteModels { + if m == nil { + m = &ClientWriteModels{} + } + for _, model := range models { + m.models = append(m.models, model) + } + return m +} + +func (m *ClientWriteModels) AppendReplaceOne(models ...ClientReplaceOneModel) *ClientWriteModels { + if m == nil { + m = &ClientWriteModels{} + } + for _, model := range models { + m.models = append(m.models, model) + } + return m +} + +func (m *ClientWriteModels) AppendDeleteOne(models ...ClientDeleteOneModel) *ClientWriteModels { + if m == nil { + m = &ClientWriteModels{} + } + for _, model := range models { + m.models = append(m.models, model) + } + return m +} + +func (m *ClientWriteModels) AppendDeleteMany(models ...ClientDeleteManyModel) *ClientWriteModels { + if m == nil { + m = &ClientWriteModels{} + } + for _, model := range models { + m.models = append(m.models, model) + } + return m +} + +// ClientInsertOneModel is used to insert a single document in a BulkWrite operation. +type ClientInsertOneModel struct { + Namespace string + Document interface{} +} + +// NewClientInsertOneModel creates a new ClientInsertOneModel. +func NewClientInsertOneModel(namespace string) *ClientInsertOneModel { + return &ClientInsertOneModel{Namespace: namespace} +} + +// SetDocument specifies the document to be inserted. The document cannot be nil. If it does not have an _id field when +// transformed into BSON, one will be added automatically to the marshalled document. The original document will not be +// modified. +func (iom *ClientInsertOneModel) SetDocument(doc interface{}) *ClientInsertOneModel { + iom.Document = doc + return iom +} + +// ClientUpdateOneModel is used to update at most one document in a client-level BulkWrite operation. +type ClientUpdateOneModel struct { + Namespace string + Collation *options.Collation + Upsert *bool + Filter interface{} + Update interface{} + ArrayFilters *options.ArrayFilters + Hint interface{} +} + +// ClientNewUpdateOneModel creates a new ClientUpdateOneModel. +func ClientNewUpdateOneModel(namespace string) *ClientUpdateOneModel { + return &ClientUpdateOneModel{Namespace: namespace} +} + +// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index +// specification as a document. The default value is nil, which means that no hint will be sent. +func (uom *ClientUpdateOneModel) SetHint(hint interface{}) *ClientUpdateOneModel { + uom.Hint = hint + return uom +} + +// SetFilter specifies a filter to use to select the document to update. The filter must be a document containing query +// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching +// documents. +func (uom *ClientUpdateOneModel) SetFilter(filter interface{}) *ClientUpdateOneModel { + uom.Filter = filter + return uom +} + +// SetUpdate specifies the modifications to be made to the selected document. The value must be a document containing +// update operators (https://www.mongodb.com/docs/manual/reference/operator/update/). It cannot be nil or empty. +func (uom *ClientUpdateOneModel) SetUpdate(update interface{}) *ClientUpdateOneModel { + uom.Update = update + return uom +} + +// SetArrayFilters specifies a set of filters to determine which elements should be modified when updating an array +// field. +func (uom *ClientUpdateOneModel) SetArrayFilters(filters options.ArrayFilters) *ClientUpdateOneModel { + uom.ArrayFilters = &filters + return uom +} + +// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be +// used. +func (uom *ClientUpdateOneModel) SetCollation(collation *options.Collation) *ClientUpdateOneModel { + uom.Collation = collation + return uom +} + +// SetUpsert specifies whether or not a new document should be inserted if no document matching the filter is found. If +// an upsert is performed, the _id of the upserted document can be retrieved from the UpsertedIDs field of the +// ClientBulkWriteResult. +func (uom *ClientUpdateOneModel) SetUpsert(upsert bool) *ClientUpdateOneModel { + uom.Upsert = &upsert + return uom +} + +// ClientUpdateManyModel is used to update multiple documents in a client-level BulkWrite operation. +type ClientUpdateManyModel struct { + Namespace string + Collation *options.Collation + Upsert *bool + Filter interface{} + Update interface{} + ArrayFilters *options.ArrayFilters + Hint interface{} +} + +// NewClientUpdateManyModel creates a new ClientUpdateManyModel. +func NewClientUpdateManyModel(namespace string) *ClientUpdateManyModel { + return &ClientUpdateManyModel{Namespace: namespace} +} + +// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index +// specification as a document. The default value is nil, which means that no hint will be sent. +func (umm *ClientUpdateManyModel) SetHint(hint interface{}) *ClientUpdateManyModel { + umm.Hint = hint + return umm +} + +// SetFilter specifies a filter to use to select documents to update. The filter must be a document containing query +// operators. It cannot be nil. +func (umm *ClientUpdateManyModel) SetFilter(filter interface{}) *ClientUpdateManyModel { + umm.Filter = filter + return umm +} + +// SetUpdate specifies the modifications to be made to the selected documents. The value must be a document containing +// update operators (https://www.mongodb.com/docs/manual/reference/operator/update/). It cannot be nil or empty. +func (umm *ClientUpdateManyModel) SetUpdate(update interface{}) *ClientUpdateManyModel { + umm.Update = update + return umm +} + +// SetArrayFilters specifies a set of filters to determine which elements should be modified when updating an array +// field. +func (umm *ClientUpdateManyModel) SetArrayFilters(filters options.ArrayFilters) *ClientUpdateManyModel { + umm.ArrayFilters = &filters + return umm +} + +// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be +// used. +func (umm *ClientUpdateManyModel) SetCollation(collation *options.Collation) *ClientUpdateManyModel { + umm.Collation = collation + return umm +} + +// SetUpsert specifies whether or not a new document should be inserted if no document matching the filter is found. If +// an upsert is performed, the _id of the upserted document can be retrieved from the UpsertedIDs field of the +// ClientBulkWriteResult. +func (umm *ClientUpdateManyModel) SetUpsert(upsert bool) *ClientUpdateManyModel { + umm.Upsert = &upsert + return umm +} + +// ClientReplaceOneModel is used to replace at most one document in a client-level BulkWrite operation. +type ClientReplaceOneModel struct { + Namespace string + Collation *options.Collation + Upsert *bool + Filter interface{} + Replacement interface{} + Hint interface{} +} + +// NewClientReplaceOneModel creates a new ClientReplaceOneModel. +func NewClientReplaceOneModel(namespace string) *ClientReplaceOneModel { + return &ClientReplaceOneModel{Namespace: namespace} +} + +// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index +// specification as a document. The default value is nil, which means that no hint will be sent. +func (rom *ClientReplaceOneModel) SetHint(hint interface{}) *ClientReplaceOneModel { + rom.Hint = hint + return rom +} + +// SetFilter specifies a filter to use to select the document to replace. The filter must be a document containing query +// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching +// documents. +func (rom *ClientReplaceOneModel) SetFilter(filter interface{}) *ClientReplaceOneModel { + rom.Filter = filter + return rom +} + +// SetReplacement specifies a document that will be used to replace the selected document. It cannot be nil and cannot +// contain any update operators (https://www.mongodb.com/docs/manual/reference/operator/update/). +func (rom *ClientReplaceOneModel) SetReplacement(rep interface{}) *ClientReplaceOneModel { + rom.Replacement = rep + return rom +} + +// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be +// used. +func (rom *ClientReplaceOneModel) SetCollation(collation *options.Collation) *ClientReplaceOneModel { + rom.Collation = collation + return rom +} + +// SetUpsert specifies whether or not the replacement document should be inserted if no document matching the filter is +// found. If an upsert is performed, the _id of the upserted document can be retrieved from the UpsertedIDs field of the +// BulkWriteResult. +func (rom *ClientReplaceOneModel) SetUpsert(upsert bool) *ClientReplaceOneModel { + rom.Upsert = &upsert + return rom +} + +// ClientDeleteOneModel is used to delete at most one document in a client-level BulkWriteOperation. +type ClientDeleteOneModel struct { + Namespace string + Filter interface{} + Collation *options.Collation + Hint interface{} +} + +// NewClientDeleteOneModel creates a new ClientDeleteOneModel. +func NewClientDeleteOneModel(namespace string) *ClientDeleteOneModel { + return &ClientDeleteOneModel{Namespace: namespace} +} + +// SetFilter specifies a filter to use to select the document to delete. The filter must be a document containing query +// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching +// documents. +func (dom *ClientDeleteOneModel) SetFilter(filter interface{}) *ClientDeleteOneModel { + dom.Filter = filter + return dom +} + +// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be +// used. +func (dom *ClientDeleteOneModel) SetCollation(collation *options.Collation) *ClientDeleteOneModel { + dom.Collation = collation + return dom +} + +// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index +// specification as a document. The default value is nil, which means that no hint will be sent. +func (dom *ClientDeleteOneModel) SetHint(hint interface{}) *ClientDeleteOneModel { + dom.Hint = hint + return dom +} + +// ClientDeleteManyModel is used to delete multiple documents in a client-level BulkWrite operation. +type ClientDeleteManyModel struct { + Namespace string + Filter interface{} + Collation *options.Collation + Hint interface{} +} + +// NewClientDeleteManyModel creates a new ClientDeleteManyModel. +func NewClientDeleteManyModel(namespace string) *ClientDeleteManyModel { + return &ClientDeleteManyModel{Namespace: namespace} +} + +// SetFilter specifies a filter to use to select documents to delete. The filter must be a document containing query +// operators. It cannot be nil. +func (dmm *ClientDeleteManyModel) SetFilter(filter interface{}) *ClientDeleteManyModel { + dmm.Filter = filter + return dmm +} + +// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be +// used. +func (dmm *ClientDeleteManyModel) SetCollation(collation *options.Collation) *ClientDeleteManyModel { + dmm.Collation = collation + return dmm +} + +// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index +// specification as a document. The default value is nil, which means that no hint will be sent. +func (dmm *ClientDeleteManyModel) SetHint(hint interface{}) *ClientDeleteManyModel { + dmm.Hint = hint + return dmm +} diff --git a/mongo/errors.go b/mongo/errors.go index d92c9ca9bd..2732a968ae 100644 --- a/mongo/errors.go +++ b/mongo/errors.go @@ -609,6 +609,53 @@ func (bwe BulkWriteException) HasErrorCodeWithMessage(code int, message string) // serverError implements the ServerError interface. func (bwe BulkWriteException) serverError() {} +// ClientBulkWriteException is the error type returned by ClientBulkWrite operations. +type ClientBulkWriteException struct { + TopLevelError *error + + // The write concern errors that occurred. + WriteConcernErrors []WriteConcernError + + // The write errors that occurred during individual operation execution. + WriteErrors map[int64]WriteError + + PartialResult *ClientBulkWriteResult +} + +// Error implements the error interface. +func (bwe ClientBulkWriteException) Error() string { + causes := make([]string, 0, 4) + if bwe.TopLevelError != nil { + causes = append(causes, "top level error: "+(*bwe.TopLevelError).Error()) + } + if len(bwe.WriteConcernErrors) > 0 { + errs := make([]error, len(bwe.WriteConcernErrors)) + for i := 0; i < len(bwe.WriteConcernErrors); i++ { + errs[i] = &bwe.WriteConcernErrors[i] + } + causes = append(causes, "write concern errors: "+joinBatchErrors(errs)) + } + if len(bwe.WriteErrors) > 0 { + errs := make([]error, 0, len(bwe.WriteErrors)) + for _, v := range bwe.WriteErrors { + errs = append(errs, &v) + } + causes = append(causes, "write errors: "+joinBatchErrors(errs)) + } + if bwe.PartialResult != nil { + causes = append(causes, "top level error: "+bwe.PartialResult.String()) + } + + message := "bulk write exception: " + if len(causes) == 0 { + return message + "no causes" + } + return "bulk write exception: " + strings.Join(causes, ", ") +} + +// serverError implements the ServerError interface. +func (bwe ClientBulkWriteException) serverError() {} + // returnResult is used to determine if a function calling processWriteError should return // the result or return nil. Since the processWriteError function is used by many different // methods, both *One and *Many, we need a way to differentiate if the method should return diff --git a/mongo/options/clientbulkwriteoptions.go b/mongo/options/clientbulkwriteoptions.go new file mode 100644 index 0000000000..050438dc2f --- /dev/null +++ b/mongo/options/clientbulkwriteoptions.go @@ -0,0 +1,119 @@ +// Copyright (C) MongoDB, Inc. 2017-present. +// +// 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 + +package options + +import ( + "go.mongodb.org/mongo-driver/mongo/writeconcern" +) + +// ClientBulkWriteOptions represents options that can be used to configure a client-level BulkWrite operation. +type ClientBulkWriteOptions struct { + // If true, writes executed as part of the operation will opt out of document-level validation on the server. This + // option is valid for MongoDB versions >= 3.2 and is ignored for previous server versions. The default value is + // false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about document + // validation. + BypassDocumentValidation *bool + + // A string or document that will be included in server logs, profiling logs, and currentOp queries to help trace + // the operation. The default value is nil, which means that no comment will be included in the logs. + Comment interface{} + + // If true, no writes will be executed after one fails. The default value is true. + Ordered *bool + + // Specifies parameters for all update and delete commands in the BulkWrite. This option is only valid for MongoDB + // versions >= 5.0. Older servers will report an error for using this option. This must be a document mapping + // parameter names to values. Values must be constant or closed expressions that do not reference document fields. + // Parameters can then be accessed as variables in an aggregate expression context (e.g. "$$var"). + Let interface{} + + // The write concern to use for this bulk write. + WriteConcern *writeconcern.WriteConcern + + // Whether detailed results for each successful operation should be included in the returned BulkWriteResult. + VerboseResults *bool +} + +// ClientBulkWrite creates a new *ClientBulkWriteOptions instance. +func ClientBulkWrite() *ClientBulkWriteOptions { + return &ClientBulkWriteOptions{ + Ordered: &DefaultOrdered, + } +} + +// SetComment sets the value for the Comment field. +func (b *ClientBulkWriteOptions) SetComment(comment interface{}) *ClientBulkWriteOptions { + b.Comment = comment + return b +} + +// SetOrdered sets the value for the Ordered field. +func (b *ClientBulkWriteOptions) SetOrdered(ordered bool) *ClientBulkWriteOptions { + b.Ordered = &ordered + return b +} + +// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. +func (b *ClientBulkWriteOptions) SetBypassDocumentValidation(bypass bool) *ClientBulkWriteOptions { + b.BypassDocumentValidation = &bypass + return b +} + +// SetLet sets the value for the Let field. Let specifies parameters for all update and delete commands in the BulkWrite. +// This option is only valid for MongoDB versions >= 5.0. Older servers will report an error for using this option. +// This must be a document mapping parameter names to values. Values must be constant or closed expressions that do not +// reference document fields. Parameters can then be accessed as variables in an aggregate expression context (e.g. "$$var"). +func (b *ClientBulkWriteOptions) SetLet(let interface{}) *ClientBulkWriteOptions { + b.Let = &let + return b +} + +// SetWriteConcern sets the value for the WriteConcern field. +func (b *ClientBulkWriteOptions) SetWriteConcern(wc *writeconcern.WriteConcern) *ClientBulkWriteOptions { + b.WriteConcern = wc + return b +} + +// SetVerboseResults sets the value for the VerboseResults field. +func (b *ClientBulkWriteOptions) SetVerboseResults(verboseResults bool) *ClientBulkWriteOptions { + b.VerboseResults = &verboseResults + return b +} + +// MergeClientBulkWriteOptions combines the given BulkWriteOptions instances into a single BulkWriteOptions in a last-one-wins +// fashion. +// +// Deprecated: Merging options structs will not be supported in Go Driver 2.0. Users should create a +// single options struct instead. +func MergeClientBulkWriteOptions(opts ...*ClientBulkWriteOptions) *ClientBulkWriteOptions { + b := ClientBulkWrite() + for _, opt := range opts { + if opt == nil { + continue + } + if opt.Comment != nil { + b.Comment = opt.Comment + } + if opt.Ordered != nil { + b.Ordered = opt.Ordered + } + if opt.BypassDocumentValidation != nil { + b.BypassDocumentValidation = opt.BypassDocumentValidation + } + if opt.Let != nil { + b.Let = opt.Let + } + if opt.WriteConcern != nil { + b.WriteConcern = opt.WriteConcern + } + if opt.VerboseResults != nil { + b.VerboseResults = opt.VerboseResults + } + } + + return b +} diff --git a/mongo/results.go b/mongo/results.go index 2dbaf2af62..3681b28e12 100644 --- a/mongo/results.go +++ b/mongo/results.go @@ -11,9 +11,65 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" "go.mongodb.org/mongo-driver/x/mongo/driver/operation" ) +// BulkWriteResult is the result type returned by a client-level BulkWrite operation. +type ClientBulkWriteResult struct { + // The number of documents inserted. + InsertedCount int64 + + // The number of documents matched by filters in update and replace operations. + MatchedCount int64 + + // The number of documents modified by update and replace operations. + ModifiedCount int64 + + // The number of documents deleted. + DeletedCount int64 + + // The number of documents upserted by update and replace operations. + UpsertedCount int64 + + // A map of operation index to the _id of each inserted document. + InsertResults map[int64]CientInsertOneResult + + // A map of operation index to the _id of each updated document. + UpdateResults map[int64]ClientUpdateResult + + // A map of operation index to the _id of each deleted document. + DeleteResults map[int64]ClientDeleteResult + + raw bsoncore.Document +} + +func newClientBulkWriteResult(result bsoncore.Document) *ClientBulkWriteResult { + return &ClientBulkWriteResult{raw: result} +} + +func (result *ClientBulkWriteResult) String() string { + return result.raw.String() +} + +// CientInsertOneResult is the result type returned by a client-level bulk write of InsertOne operation. +type CientInsertOneResult struct { + // The _id of the inserted document. A value generated by the driver will be of type primitive.ObjectID. + InsertedID interface{} +} + +// ClientUpdateResult is the result type returned from a client-level bulk write of UpdateOne, UpdateMany, and ReplaceOne operation. +type ClientUpdateResult struct { + MatchedCount int64 // The number of documents matched by the filter. + ModifiedCount int64 // The number of documents modified by the operation. + UpsertedID interface{} // The _id field of the upserted document, or nil if no upsert was done. +} + +// ClientDeleteResult is the result type returned by a client-level bulk write DeleteOne and DeleteMany operation. +type ClientDeleteResult struct { + DeletedCount int64 // `bson:"n"` // The number of documents deleted. +} + // BulkWriteResult is the result type returned by a BulkWrite operation. type BulkWriteResult struct { // The number of documents inserted. diff --git a/x/mongo/driver/operation/command.go b/x/mongo/driver/operation/command.go index 64c98ba19a..6936d91719 100644 --- a/x/mongo/driver/operation/command.go +++ b/x/mongo/driver/operation/command.go @@ -15,6 +15,7 @@ import ( "go.mongodb.org/mongo-driver/internal/logger" "go.mongodb.org/mongo-driver/mongo/description" "go.mongodb.org/mongo-driver/mongo/readpref" + "go.mongodb.org/mongo-driver/mongo/writeconcern" "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" "go.mongodb.org/mongo-driver/x/mongo/driver" "go.mongodb.org/mongo-driver/x/mongo/driver/session" @@ -23,10 +24,12 @@ import ( // Command is used to run a generic operation. type Command struct { authenticator driver.Authenticator - command bsoncore.Document + commandFn func([]byte, description.SelectedServer) ([]byte, error) + batches *driver.Batches database string deployment driver.Deployment selector description.ServerSelector + writeConcern *writeconcern.WriteConcern readPreference *readpref.ReadPref clock *session.ClusterClock session *session.Client @@ -45,7 +48,15 @@ type Command struct { // the Result() function. func NewCommand(command bsoncore.Document) *Command { return &Command{ - command: command, + commandFn: func(dst []byte, _ description.SelectedServer) ([]byte, error) { + return append(dst, command[4:len(command)-1]...), nil + }, + } +} + +func NewCommandFn(commandFn func([]byte, description.SelectedServer) ([]byte, error)) *Command { + return &Command{ + commandFn: commandFn, } } @@ -53,7 +64,9 @@ func NewCommand(command bsoncore.Document) *Command { // construct a cursor, which can be accessed via the ResultCursor() function. func NewCursorCommand(command bsoncore.Document, cursorOpts driver.CursorOptions) *Command { return &Command{ - command: command, + commandFn: func(dst []byte, _ description.SelectedServer) ([]byte, error) { + return append(dst, command[4:len(command)-1]...), nil + }, cursorOpts: cursorOpts, createCursor: true, } @@ -79,9 +92,8 @@ func (c *Command) Execute(ctx context.Context) error { } return driver.Operation{ - CommandFn: func(dst []byte, _ description.SelectedServer) ([]byte, error) { - return append(dst, c.command[4:len(c.command)-1]...), nil - }, + CommandFn: c.commandFn, + Batches: c.batches, ProcessResponseFn: func(info driver.ResponseInfo) error { c.resultResponse = info.ServerResponse @@ -104,6 +116,7 @@ func (c *Command) Execute(ctx context.Context) error { Deployment: c.deployment, ReadPreference: c.readPreference, Selector: c.selector, + WriteConcern: c.writeConcern, Crypt: c.crypt, ServerAPI: c.serverAPI, Timeout: c.timeout, @@ -142,6 +155,16 @@ func (c *Command) CommandMonitor(monitor *event.CommandMonitor) *Command { return c } +// Batches sets the batches for this operation. +func (c *Command) Batches(batches *driver.Batches) *Command { + if c == nil { + c = new(Command) + } + + c.batches = batches + return c +} + // Database sets the database to run this operation against. func (c *Command) Database(database string) *Command { if c == nil { @@ -182,6 +205,16 @@ func (c *Command) ServerSelector(selector description.ServerSelector) *Command { return c } +// WriteConcern sets the write concern for this operation. +func (c *Command) WriteConcern(writeConcern *writeconcern.WriteConcern) *Command { + if c == nil { + c = new(Command) + } + + c.writeConcern = writeConcern + return c +} + // Crypt sets the Crypt object to use for automatic encryption and decryption. func (c *Command) Crypt(crypt driver.Crypt) *Command { if c == nil { From 4aed0251753bf7ea3fd306981eb69e7865eaa5e6 Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Thu, 26 Sep 2024 17:33:17 -0400 Subject: [PATCH 03/14] WIP --- mongo/client.go | 5 +- mongo/client_bulk_write.go | 267 ++++++++++----- mongo/client_bulk_write_models.go | 12 +- mongo/errors.go | 2 +- .../unified/client_operation_execution.go | 322 ++++++++++++++++++ mongo/integration/unified/operation.go | 2 + mongo/results.go | 19 +- 7 files changed, 526 insertions(+), 103 deletions(-) diff --git a/mongo/client.go b/mongo/client.go index 41112fd6fc..039583372e 100644 --- a/mongo/client.go +++ b/mongo/client.go @@ -877,6 +877,9 @@ func (c *Client) BulkWrite(ctx context.Context, models *ClientWriteModels, } wc := c.writeConcern + if bwo.WriteConcern != nil { + wc = bwo.WriteConcern + } if sess.TransactionRunning() { wc = nil } @@ -905,7 +908,7 @@ func (c *Client) BulkWrite(ctx context.Context, models *ClientWriteModels, op.errorsOnly = true } err = op.execute(ctx) - return op.result, replaceErrors(err) + return &op.result, replaceErrors(err) } // newLogger will use the LoggerOptions to create an internal logger and publish diff --git a/mongo/client_bulk_write.go b/mongo/client_bulk_write.go index da9d1422a0..ba79069b42 100644 --- a/mongo/client_bulk_write.go +++ b/mongo/client_bulk_write.go @@ -10,6 +10,7 @@ import ( "context" "strconv" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/bsoncodec" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo/description" @@ -35,27 +36,11 @@ type clientBulkWrite struct { selector description.ServerSelector writeConcern *writeconcern.WriteConcern - result *ClientBulkWriteResult + result ClientBulkWriteResult } func (bw *clientBulkWrite) execute(ctx context.Context) error { - batches := &driver.Batches{ - Ordered: bw.ordered, - } - op := operation.NewCommandFn(bw.command).Batches(batches). - Session(bw.session).WriteConcern(bw.writeConcern).CommandMonitor(bw.client.monitor). - ServerSelector(bw.selector).ClusterClock(bw.client.clock). - Database("admin"). - Deployment(bw.client.deployment).Crypt(bw.client.cryptFLE). - ServerAPI(bw.client.serverAPI).Timeout(bw.client.timeout). - Logger(bw.client.logger).Authenticator(bw.client.authenticator) - err := op.Execute(ctx) - bw.result = newClientBulkWriteResult(op.Result()) - return err -} - -func (bw *clientBulkWrite) command(dst []byte, desc description.SelectedServer) ([]byte, error) { - dst = bsoncore.AppendInt32Element(dst, "bulkWrite", 1) + docs := make([]bsoncore.Document, len(bw.models)) nsMap := make(map[string]int) var nsList []string getNsIndex := func(namespace string) int { @@ -68,22 +53,31 @@ func (bw *clientBulkWrite) command(dst []byte, desc description.SelectedServer) return nsIdx } } - var err error - var idx int32 - idx, dst = bsoncore.AppendArrayElementStart(dst, "ops") + resMap := make([]interface{}, len(bw.models)) + insIdMap := make(map[int]interface{}) for i, v := range bw.models { var doc bsoncore.Document + var err error var nsIdx int switch model := v.(type) { - case ClientInsertOneModel: + case *ClientInsertOneModel: nsIdx = getNsIndex(model.Namespace) - doc, err = createClientInsertDoc(int32(nsIdx), model.Document, bw.client.bsonOpts, bw.client.registry) + if bw.result.InsertResults == nil { + bw.result.InsertResults = make(map[int64]ClientInsertResult) + } + resMap[i] = bw.result.InsertResults + var id interface{} + id, doc, err = createClientInsertDoc(int32(nsIdx), model.Document, bw.client.bsonOpts, bw.client.registry) if err != nil { break } - doc, _, err = ensureID(doc, primitive.NilObjectID, bw.client.bsonOpts, bw.client.registry) - case ClientUpdateOneModel: + insIdMap[i] = id + case *ClientUpdateOneModel: nsIdx = getNsIndex(model.Namespace) + if bw.result.UpdateResults == nil { + bw.result.UpdateResults = make(map[int64]ClientUpdateResult) + } + resMap[i] = bw.result.UpdateResults doc, err = createClientUpdateDoc( int32(nsIdx), model.Filter, @@ -96,8 +90,12 @@ func (bw *clientBulkWrite) command(dst []byte, desc description.SelectedServer) true, bw.client.bsonOpts, bw.client.registry) - case ClientUpdateManyModel: + case *ClientUpdateManyModel: nsIdx = getNsIndex(model.Namespace) + if bw.result.UpdateResults == nil { + bw.result.UpdateResults = make(map[int64]ClientUpdateResult) + } + resMap[i] = bw.result.UpdateResults doc, err = createClientUpdateDoc( int32(nsIdx), model.Filter, @@ -110,8 +108,12 @@ func (bw *clientBulkWrite) command(dst []byte, desc description.SelectedServer) true, bw.client.bsonOpts, bw.client.registry) - case ClientReplaceOneModel: + case *ClientReplaceOneModel: nsIdx = getNsIndex(model.Namespace) + if bw.result.UpdateResults == nil { + bw.result.UpdateResults = make(map[int64]ClientUpdateResult) + } + resMap[i] = bw.result.UpdateResults doc, err = createClientUpdateDoc( int32(nsIdx), model.Filter, @@ -124,76 +126,139 @@ func (bw *clientBulkWrite) command(dst []byte, desc description.SelectedServer) false, bw.client.bsonOpts, bw.client.registry) - case ClientDeleteOneModel: + case *ClientDeleteOneModel: nsIdx = getNsIndex(model.Namespace) + if bw.result.DeleteResults == nil { + bw.result.DeleteResults = make(map[int64]ClientDeleteResult) + } + resMap[i] = bw.result.DeleteResults doc, err = createClientDeleteDoc( int32(nsIdx), model.Filter, model.Collation, model.Hint, - true, + false, bw.client.bsonOpts, bw.client.registry) - case ClientDeleteManyModel: + case *ClientDeleteManyModel: nsIdx = getNsIndex(model.Namespace) + if bw.result.DeleteResults == nil { + bw.result.DeleteResults = make(map[int64]ClientDeleteResult) + } + resMap[i] = bw.result.DeleteResults doc, err = createClientDeleteDoc( int32(nsIdx), model.Filter, model.Collation, model.Hint, - false, + true, bw.client.bsonOpts, bw.client.registry) } if err != nil { - return nil, err + return err } - dst = bsoncore.AppendDocumentElement(dst, strconv.Itoa(i), doc) + docs[i] = doc } - dst, err = bsoncore.AppendArrayEnd(dst, idx) + batches := &driver.Batches{ + Identifier: "ops", + Documents: docs, + Ordered: bw.ordered, + } + op := operation.NewCommandFn(bw.newCommand(nsList)).Batches(batches). + Session(bw.session).WriteConcern(bw.writeConcern).CommandMonitor(bw.client.monitor). + ServerSelector(bw.selector).ClusterClock(bw.client.clock). + Database("admin"). + Deployment(bw.client.deployment).Crypt(bw.client.cryptFLE). + ServerAPI(bw.client.serverAPI).Timeout(bw.client.timeout). + Logger(bw.client.logger).Authenticator(bw.client.authenticator) + err := op.Execute(ctx) if err != nil { - return nil, err + return err } - - idx, dst = bsoncore.AppendArrayElementStart(dst, "nsInfo") - for i, v := range nsList { - doc, err := marshal(struct { - Namespace string `bson:"ns"` - }{v}, bw.client.bsonOpts, bw.client.registry) - if err != nil { - return nil, err + var res struct { + Cursor struct { + FirstBatch []bson.Raw } - dst = bsoncore.AppendDocumentElement(dst, strconv.Itoa(i), doc) + NDeleted int32 + NInserted int32 + NMatched int32 + NModified int32 + NUpserted int32 } - dst, err = bsoncore.AppendArrayEnd(dst, idx) + rawRes := op.Result() + err = bson.Unmarshal(rawRes, &res) if err != nil { - return nil, err - } - - if bw.errorsOnly { - dst = bsoncore.AppendBooleanElement(dst, "errorsOnly", bw.errorsOnly) - } - if bw.bypassDocumentValidation != nil && (desc.WireVersion != nil && desc.WireVersion.Includes(4)) { - dst = bsoncore.AppendBooleanElement(dst, "bypassDocumentValidation", *bw.bypassDocumentValidation) + return err } - if bw.comment != nil { - comment, err := marshalValue(bw.comment, bw.client.bsonOpts, bw.client.registry) - if err != nil { - return nil, err + bw.result.DeletedCount = int64(res.NDeleted) + bw.result.InsertedCount = int64(res.NInserted) + bw.result.MatchedCount = int64(res.NMatched) + bw.result.ModifiedCount = int64(res.NModified) + bw.result.UpsertedCount = int64(res.NUpserted) + for i, cur := range res.Cursor.FirstBatch { + switch res := resMap[i].(type) { + case map[int64]ClientDeleteResult: + if err = appendDeleteResult(cur, res); err != nil { + return err + } + case map[int64]ClientInsertResult: + if err = appendInsertResult(cur, res, insIdMap); err != nil { + return err + } + case map[int64]ClientUpdateResult: + if err = appendUpdateResult(cur, res); err != nil { + return err + } } - dst = bsoncore.AppendValueElement(dst, "comment", comment) - } - if bw.ordered != nil { - dst = bsoncore.AppendBooleanElement(dst, "ordered", *bw.ordered) } - if bw.let != nil { - let, err := marshal(bw.let, bw.client.bsonOpts, bw.client.registry) + return nil +} + +func (bw *clientBulkWrite) newCommand(nsList []string) func([]byte, description.SelectedServer) ([]byte, error) { + return func(dst []byte, desc description.SelectedServer) ([]byte, error) { + dst = bsoncore.AppendInt32Element(dst, "bulkWrite", 1) + + var idx int32 + var err error + idx, dst = bsoncore.AppendArrayElementStart(dst, "nsInfo") + for i, v := range nsList { + doc, err := marshal(struct { + Namespace string `bson:"ns"` + }{v}, bw.client.bsonOpts, bw.client.registry) + if err != nil { + return nil, err + } + dst = bsoncore.AppendDocumentElement(dst, strconv.Itoa(i), doc) + } + dst, err = bsoncore.AppendArrayEnd(dst, idx) if err != nil { return nil, err } - dst = bsoncore.AppendDocumentElement(dst, "let", let) + + dst = bsoncore.AppendBooleanElement(dst, "errorsOnly", bw.errorsOnly) + if bw.bypassDocumentValidation != nil && (desc.WireVersion != nil && desc.WireVersion.Includes(4)) { + dst = bsoncore.AppendBooleanElement(dst, "bypassDocumentValidation", *bw.bypassDocumentValidation) + } + if bw.comment != nil { + comment, err := marshalValue(bw.comment, bw.client.bsonOpts, bw.client.registry) + if err != nil { + return nil, err + } + dst = bsoncore.AppendValueElement(dst, "comment", comment) + } + if bw.ordered != nil { + dst = bsoncore.AppendBooleanElement(dst, "ordered", *bw.ordered) + } + if bw.let != nil { + let, err := marshal(bw.let, bw.client.bsonOpts, bw.client.registry) + if err != nil { + return nil, err + } + dst = bsoncore.AppendDocumentElement(dst, "let", let) + } + return dst, nil } - return dst, nil } func createClientInsertDoc( @@ -201,17 +266,22 @@ func createClientInsertDoc( document interface{}, bsonOpts *options.BSONOptions, registry *bsoncodec.Registry, -) (bsoncore.Document, error) { - uidx, insertDoc := bsoncore.AppendDocumentStart(nil) +) (interface{}, bsoncore.Document, error) { + uidx, doc := bsoncore.AppendDocumentStart(nil) - insertDoc = bsoncore.AppendInt32Element(insertDoc, "update", namespace) + doc = bsoncore.AppendInt32Element(doc, "insert", namespace) f, err := marshal(document, bsonOpts, registry) if err != nil { - return nil, err + return nil, nil, err } - insertDoc = bsoncore.AppendDocumentElement(insertDoc, "document", f) - - return bsoncore.AppendDocumentEnd(insertDoc, uidx) + var id interface{} + f, id, err = ensureID(f, primitive.NilObjectID, bsonOpts, registry) + if err != nil { + return nil, nil, err + } + doc = bsoncore.AppendDocumentElement(doc, "document", f) + doc, err = bsoncore.AppendDocumentEnd(doc, uidx) + return id, doc, err } func createClientUpdateDoc( @@ -242,10 +312,7 @@ func createClientUpdateDoc( return nil, err } doc = bsoncore.AppendValueElement(doc, "updateMods", u) - - if multi { - doc = bsoncore.AppendBooleanElement(doc, "multi", multi) - } + doc = bsoncore.AppendBooleanElement(doc, "multi", multi) if arrayFilters != nil { reg := registry @@ -299,10 +366,7 @@ func createClientDeleteDoc( return nil, err } doc = bsoncore.AppendDocumentElement(doc, "filter", f) - - if multi { - doc = bsoncore.AppendBooleanElement(doc, "multi", multi) - } + doc = bsoncore.AppendBooleanElement(doc, "multi", multi) if collation != nil { doc = bsoncore.AppendDocumentElement(doc, "collation", collation.ToDocument()) @@ -319,3 +383,46 @@ func createClientDeleteDoc( } return bsoncore.AppendDocumentEnd(doc, didx) } + +func appendDeleteResult(cur bson.Raw, m map[int64]ClientDeleteResult) error { + var res struct { + Idx int32 + N int32 + } + if err := bson.Unmarshal(cur, &res); err != nil { + return err + } + m[int64(res.Idx)] = ClientDeleteResult{int64(res.N)} + return nil +} + +func appendInsertResult(cur bson.Raw, m map[int64]ClientInsertResult, insIdMap map[int]interface{}) error { + var res struct { + Idx int32 + } + if err := bson.Unmarshal(cur, &res); err != nil { + return err + } + m[int64(res.Idx)] = ClientInsertResult{insIdMap[int(res.Idx)]} + return nil +} + +func appendUpdateResult(cur bson.Raw, m map[int64]ClientUpdateResult) error { + var res struct { + Idx int32 + N int32 + NModified int32 + Upserted struct { + Id interface{} `bson:"_id"` + } + } + if err := bson.Unmarshal(cur, &res); err != nil { + return err + } + m[int64(res.Idx)] = ClientUpdateResult{ + MatchedCount: int64(res.N), + ModifiedCount: int64(res.NModified), + UpsertedID: res.Upserted.Id, + } + return nil +} diff --git a/mongo/client_bulk_write_models.go b/mongo/client_bulk_write_models.go index b389019bb2..c14e37451f 100644 --- a/mongo/client_bulk_write_models.go +++ b/mongo/client_bulk_write_models.go @@ -15,7 +15,7 @@ type ClientWriteModels struct { models []interface{} } -func (m *ClientWriteModels) AppendInsertOne(models ...ClientInsertOneModel) *ClientWriteModels { +func (m *ClientWriteModels) AppendInsertOne(models ...*ClientInsertOneModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} } @@ -25,7 +25,7 @@ func (m *ClientWriteModels) AppendInsertOne(models ...ClientInsertOneModel) *Cli return m } -func (m *ClientWriteModels) AppendUpdateOne(models ...ClientUpdateOneModel) *ClientWriteModels { +func (m *ClientWriteModels) AppendUpdateOne(models ...*ClientUpdateOneModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} } @@ -35,7 +35,7 @@ func (m *ClientWriteModels) AppendUpdateOne(models ...ClientUpdateOneModel) *Cli return m } -func (m *ClientWriteModels) AppendUpdateMany(models ...ClientUpdateManyModel) *ClientWriteModels { +func (m *ClientWriteModels) AppendUpdateMany(models ...*ClientUpdateManyModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} } @@ -45,7 +45,7 @@ func (m *ClientWriteModels) AppendUpdateMany(models ...ClientUpdateManyModel) *C return m } -func (m *ClientWriteModels) AppendReplaceOne(models ...ClientReplaceOneModel) *ClientWriteModels { +func (m *ClientWriteModels) AppendReplaceOne(models ...*ClientReplaceOneModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} } @@ -55,7 +55,7 @@ func (m *ClientWriteModels) AppendReplaceOne(models ...ClientReplaceOneModel) *C return m } -func (m *ClientWriteModels) AppendDeleteOne(models ...ClientDeleteOneModel) *ClientWriteModels { +func (m *ClientWriteModels) AppendDeleteOne(models ...*ClientDeleteOneModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} } @@ -65,7 +65,7 @@ func (m *ClientWriteModels) AppendDeleteOne(models ...ClientDeleteOneModel) *Cli return m } -func (m *ClientWriteModels) AppendDeleteMany(models ...ClientDeleteManyModel) *ClientWriteModels { +func (m *ClientWriteModels) AppendDeleteMany(models ...*ClientDeleteManyModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} } diff --git a/mongo/errors.go b/mongo/errors.go index 2732a968ae..e20af03e95 100644 --- a/mongo/errors.go +++ b/mongo/errors.go @@ -643,7 +643,7 @@ func (bwe ClientBulkWriteException) Error() string { causes = append(causes, "write errors: "+joinBatchErrors(errs)) } if bwe.PartialResult != nil { - causes = append(causes, "top level error: "+bwe.PartialResult.String()) + causes = append(causes, fmt.Sprintf("result: %v", bwe.PartialResult)) } message := "bulk write exception: " diff --git a/mongo/integration/unified/client_operation_execution.go b/mongo/integration/unified/client_operation_execution.go index 5a69e77b1e..0a4c381638 100644 --- a/mongo/integration/unified/client_operation_execution.go +++ b/mongo/integration/unified/client_operation_execution.go @@ -9,8 +9,10 @@ package unified import ( "context" "fmt" + "strconv" "time" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/internal/bsonutil" "go.mongodb.org/mongo-driver/mongo" @@ -167,3 +169,323 @@ func executeListDatabases(ctx context.Context, operation *operation, nameOnly bo Build() return newDocumentResult(raw, nil), nil } + +func executeClientBulkWrite(ctx context.Context, operation *operation) (*operationResult, error) { + client, err := entities(ctx).client(operation.Object) + if err != nil { + return nil, err + } + + wirteModels := &mongo.ClientWriteModels{} + opts := options.ClientBulkWrite() + + elems, err := operation.Arguments.Elements() + if err != nil { + return nil, err + } + for _, elem := range elems { + key := elem.Key() + val := elem.Value() + + switch key { + case "models": + models, err := val.Array().Values() + if err != nil { + return nil, err + } + for _, m := range models { + model := m.Document().Index(0) + err = appendClientBulkWriteModel(model.Key(), model.Value().Document(), wirteModels) + if err != nil { + return nil, err + } + } + case "bypassDocumentValidation": + opts.SetBypassDocumentValidation(val.Boolean()) + case "comment": + opts.SetComment(val) + case "let": + opts.SetLet(val.Document()) + case "ordered": + opts.SetOrdered(val.Boolean()) + case "verboseResults": + opts.SetVerboseResults(val.Boolean()) + case "writeConcern": + var wc writeConcern + bson.Unmarshal(val.Value, &wc) + c, err := wc.toWriteConcernOption() + if err != nil { + return nil, err + } + opts.SetWriteConcern(c) + default: + return nil, fmt.Errorf("unrecognized bulkWrite option %q", key) + } + } + + res, err := client.BulkWrite(ctx, wirteModels, opts) + raw := emptyCoreDocument + if res != nil { + rawBuilder := bsoncore.NewDocumentBuilder(). + AppendInt64("deletedCount", res.DeletedCount). + AppendInt64("insertedCount", res.InsertedCount). + AppendInt64("matchedCount", res.MatchedCount). + AppendInt64("modifiedCount", res.ModifiedCount). + AppendInt64("upsertedCount", res.UpsertedCount) + + var resBuilder *bsoncore.DocumentBuilder + + resBuilder = bsoncore.NewDocumentBuilder() + for k, v := range res.DeleteResults { + resBuilder.AppendDocument(strconv.Itoa(int(k)), + bsoncore.NewDocumentBuilder(). + AppendInt64("deletedCount", v.DeletedCount). + Build(), + ) + } + rawBuilder.AppendDocument("deleteResults", resBuilder.Build()) + + resBuilder = bsoncore.NewDocumentBuilder() + for k, v := range res.InsertResults { + t, d, err := bson.MarshalValue(v.InsertedID) + if err != nil { + return nil, err + } + resBuilder.AppendDocument(strconv.Itoa(int(k)), + bsoncore.NewDocumentBuilder(). + AppendValue("insertedId", bsoncore.Value{Type: t, Data: d}). + Build(), + ) + } + rawBuilder.AppendDocument("insertResults", resBuilder.Build()) + + resBuilder = bsoncore.NewDocumentBuilder() + for k, v := range res.UpdateResults { + b := bsoncore.NewDocumentBuilder(). + AppendInt64("matchedCount", v.MatchedCount). + AppendInt64("modifiedCount", v.ModifiedCount) + if v.UpsertedID != nil { + t, d, err := bson.MarshalValue(v.UpsertedID) + if err != nil { + return nil, err + } + b.AppendValue("upsertedId", bsoncore.Value{Type: t, Data: d}) + } + resBuilder.AppendDocument(strconv.Itoa(int(k)), b.Build()) + } + rawBuilder.AppendDocument("updateResults", resBuilder.Build()) + + raw = rawBuilder.Build() + } + return newDocumentResult(raw, err), nil +} + +func appendClientBulkWriteModel(key string, value bson.Raw, model *mongo.ClientWriteModels) error { + switch key { + case "insertOne": + m, err := createClientInsertOneModel(value) + if err != nil { + return err + } + model.AppendInsertOne(m) + case "updateOne": + m, err := createClientUpdateOneModel(value) + if err != nil { + return err + } + model.AppendUpdateOne(m) + case "updateMany": + m, err := createClientUpdateManyModel(value) + if err != nil { + return err + } + model.AppendUpdateMany(m) + case "replaceOne": + m, err := createClientReplaceOneModel(value) + if err != nil { + return err + } + model.AppendReplaceOne(m) + case "deleteOne": + m, err := createClientDeleteOneModel(value) + if err != nil { + return err + } + model.AppendDeleteOne(m) + case "deleteMany": + m, err := createClientDeleteManyModel(value) + if err != nil { + return err + } + model.AppendDeleteMany(m) + } + return nil +} + +func createClientInsertOneModel(value bson.Raw) (*mongo.ClientInsertOneModel, error) { + var v struct { + Namespace string + Document bson.Raw + } + err := bson.Unmarshal(value, &v) + if err != nil { + return nil, err + } + return &mongo.ClientInsertOneModel{ + Namespace: v.Namespace, + Document: v.Document, + }, nil +} + +func createClientUpdateOneModel(value bson.Raw) (*mongo.ClientUpdateOneModel, error) { + var v struct { + Namespace string + Filter bson.Raw + Update interface{} + ArrayFilters []interface{} + Collation *options.Collation + Hint *bson.RawValue + Upsert *bool + } + err := bson.Unmarshal(value, &v) + if err != nil { + return nil, err + } + var hint interface{} + if v.Hint != nil { + hint, err = createHint(*v.Hint) + if err != nil { + return nil, err + } + } + model := &mongo.ClientUpdateOneModel{ + Namespace: v.Namespace, + Filter: v.Filter, + Update: v.Update, + Collation: v.Collation, + Hint: hint, + Upsert: v.Upsert, + } + if len(v.ArrayFilters) > 0 { + model.ArrayFilters = &options.ArrayFilters{Filters: v.ArrayFilters} + } + return model, nil + +} + +func createClientUpdateManyModel(value bson.Raw) (*mongo.ClientUpdateManyModel, error) { + var v struct { + Namespace string + Filter bson.Raw + Update interface{} + ArrayFilters []interface{} + Collation *options.Collation + Hint *bson.RawValue + Upsert *bool + } + err := bson.Unmarshal(value, &v) + if err != nil { + return nil, err + } + var hint interface{} + if v.Hint != nil { + hint, err = createHint(*v.Hint) + if err != nil { + return nil, err + } + } + model := &mongo.ClientUpdateManyModel{ + Namespace: v.Namespace, + Filter: v.Filter, + Update: v.Update, + Collation: v.Collation, + Hint: hint, + Upsert: v.Upsert, + } + if len(v.ArrayFilters) > 0 { + model.ArrayFilters = &options.ArrayFilters{Filters: v.ArrayFilters} + } + return model, nil +} + +func createClientReplaceOneModel(value bson.Raw) (*mongo.ClientReplaceOneModel, error) { + var v struct { + Namespace string + Filter bson.Raw + Replacement bson.Raw + Collation *options.Collation + Hint *bson.RawValue + Upsert *bool + } + err := bson.Unmarshal(value, &v) + if err != nil { + return nil, err + } + var hint interface{} + if v.Hint != nil { + hint, err = createHint(*v.Hint) + if err != nil { + return nil, err + } + } + return &mongo.ClientReplaceOneModel{ + Namespace: v.Namespace, + Filter: v.Filter, + Replacement: v.Replacement, + Collation: v.Collation, + Hint: hint, + Upsert: v.Upsert, + }, nil +} + +func createClientDeleteOneModel(value bson.Raw) (*mongo.ClientDeleteOneModel, error) { + var v struct { + Namespace string + Filter bson.Raw + Collation *options.Collation + Hint *bson.RawValue + } + err := bson.Unmarshal(value, &v) + if err != nil { + return nil, err + } + var hint interface{} + if v.Hint != nil { + hint, err = createHint(*v.Hint) + if err != nil { + return nil, err + } + } + return &mongo.ClientDeleteOneModel{ + Namespace: v.Namespace, + Filter: v.Filter, + Collation: v.Collation, + Hint: hint, + }, nil +} + +func createClientDeleteManyModel(value bson.Raw) (*mongo.ClientDeleteManyModel, error) { + var v struct { + Namespace string + Filter bson.Raw + Collation *options.Collation + Hint *bson.RawValue + } + err := bson.Unmarshal(value, &v) + if err != nil { + return nil, err + } + var hint interface{} + if v.Hint != nil { + hint, err = createHint(*v.Hint) + if err != nil { + return nil, err + } + } + return &mongo.ClientDeleteManyModel{ + Namespace: v.Namespace, + Filter: v.Filter, + Collation: v.Collation, + Hint: hint, + }, nil +} diff --git a/mongo/integration/unified/operation.go b/mongo/integration/unified/operation.go index 59aa36ae8c..179cf16793 100644 --- a/mongo/integration/unified/operation.go +++ b/mongo/integration/unified/operation.go @@ -148,6 +148,8 @@ func (op *operation) run(ctx context.Context, loopDone <-chan struct{}) (*operat return executeAggregate(ctx, op) case "bulkWrite": return executeBulkWrite(ctx, op) + case "clientBulkWrite": + return executeClientBulkWrite(ctx, op) case "countDocuments": return executeCountDocuments(ctx, op) case "createFindCursor": diff --git a/mongo/results.go b/mongo/results.go index 3681b28e12..0088e734fc 100644 --- a/mongo/results.go +++ b/mongo/results.go @@ -11,7 +11,6 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" "go.mongodb.org/mongo-driver/x/mongo/driver/operation" ) @@ -33,27 +32,17 @@ type ClientBulkWriteResult struct { UpsertedCount int64 // A map of operation index to the _id of each inserted document. - InsertResults map[int64]CientInsertOneResult + InsertResults map[int64]ClientInsertResult // A map of operation index to the _id of each updated document. UpdateResults map[int64]ClientUpdateResult // A map of operation index to the _id of each deleted document. DeleteResults map[int64]ClientDeleteResult - - raw bsoncore.Document -} - -func newClientBulkWriteResult(result bsoncore.Document) *ClientBulkWriteResult { - return &ClientBulkWriteResult{raw: result} -} - -func (result *ClientBulkWriteResult) String() string { - return result.raw.String() } -// CientInsertOneResult is the result type returned by a client-level bulk write of InsertOne operation. -type CientInsertOneResult struct { +// ClientInsertResult is the result type returned by a client-level bulk write of InsertOne operation. +type ClientInsertResult struct { // The _id of the inserted document. A value generated by the driver will be of type primitive.ObjectID. InsertedID interface{} } @@ -67,7 +56,7 @@ type ClientUpdateResult struct { // ClientDeleteResult is the result type returned by a client-level bulk write DeleteOne and DeleteMany operation. type ClientDeleteResult struct { - DeletedCount int64 // `bson:"n"` // The number of documents deleted. + DeletedCount int64 // The number of documents deleted. } // BulkWriteResult is the result type returned by a BulkWrite operation. From 7b71cc1cae1914a5729a2615c9ef20681cb95f5c Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Thu, 26 Sep 2024 18:10:20 -0400 Subject: [PATCH 04/14] WIP --- mongo/client_bulk_write.go | 19 +++++++++---------- mongo/client_bulk_write_models.go | 6 ++++++ mongo/errors.go | 2 +- .../unified/client_operation_execution.go | 5 ++++- mongo/results.go | 2 +- x/mongo/driver/operation/command.go | 1 + 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/mongo/client_bulk_write.go b/mongo/client_bulk_write.go index ba79069b42..f4b9466704 100644 --- a/mongo/client_bulk_write.go +++ b/mongo/client_bulk_write.go @@ -46,15 +46,14 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { getNsIndex := func(namespace string) int { if v, ok := nsMap[namespace]; ok { return v - } else { - nsIdx := len(nsList) - nsMap[namespace] = nsIdx - nsList = append(nsList, namespace) - return nsIdx } + nsIdx := len(nsList) + nsMap[namespace] = nsIdx + nsList = append(nsList, namespace) + return nsIdx } resMap := make([]interface{}, len(bw.models)) - insIdMap := make(map[int]interface{}) + insIDMap := make(map[int]interface{}) for i, v := range bw.models { var doc bsoncore.Document var err error @@ -71,7 +70,7 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { if err != nil { break } - insIdMap[i] = id + insIDMap[i] = id case *ClientUpdateOneModel: nsIdx = getNsIndex(model.Namespace) if bw.result.UpdateResults == nil { @@ -203,7 +202,7 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { return err } case map[int64]ClientInsertResult: - if err = appendInsertResult(cur, res, insIdMap); err != nil { + if err = appendInsertResult(cur, res, insIDMap); err != nil { return err } case map[int64]ClientUpdateResult: @@ -413,7 +412,7 @@ func appendUpdateResult(cur bson.Raw, m map[int64]ClientUpdateResult) error { N int32 NModified int32 Upserted struct { - Id interface{} `bson:"_id"` + ID interface{} `bson:"_id"` } } if err := bson.Unmarshal(cur, &res); err != nil { @@ -422,7 +421,7 @@ func appendUpdateResult(cur bson.Raw, m map[int64]ClientUpdateResult) error { m[int64(res.Idx)] = ClientUpdateResult{ MatchedCount: int64(res.N), ModifiedCount: int64(res.NModified), - UpsertedID: res.Upserted.Id, + UpsertedID: res.Upserted.ID, } return nil } diff --git a/mongo/client_bulk_write_models.go b/mongo/client_bulk_write_models.go index c14e37451f..e942a7af4c 100644 --- a/mongo/client_bulk_write_models.go +++ b/mongo/client_bulk_write_models.go @@ -15,6 +15,7 @@ type ClientWriteModels struct { models []interface{} } +// AppendInsertOne appends ClientInsertOneModels. func (m *ClientWriteModels) AppendInsertOne(models ...*ClientInsertOneModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} @@ -25,6 +26,7 @@ func (m *ClientWriteModels) AppendInsertOne(models ...*ClientInsertOneModel) *Cl return m } +// AppendUpdateOne appends ClientUpdateOneModels. func (m *ClientWriteModels) AppendUpdateOne(models ...*ClientUpdateOneModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} @@ -35,6 +37,7 @@ func (m *ClientWriteModels) AppendUpdateOne(models ...*ClientUpdateOneModel) *Cl return m } +// AppendUpdateMany appends ClientUpdateManyModels. func (m *ClientWriteModels) AppendUpdateMany(models ...*ClientUpdateManyModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} @@ -45,6 +48,7 @@ func (m *ClientWriteModels) AppendUpdateMany(models ...*ClientUpdateManyModel) * return m } +// AppendReplaceOne appends ClientReplaceOneModels. func (m *ClientWriteModels) AppendReplaceOne(models ...*ClientReplaceOneModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} @@ -55,6 +59,7 @@ func (m *ClientWriteModels) AppendReplaceOne(models ...*ClientReplaceOneModel) * return m } +// AppendDeleteOne appends ClientDeleteOneModels. func (m *ClientWriteModels) AppendDeleteOne(models ...*ClientDeleteOneModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} @@ -65,6 +70,7 @@ func (m *ClientWriteModels) AppendDeleteOne(models ...*ClientDeleteOneModel) *Cl return m } +// AppendDeleteMany appends ClientDeleteManyModels. func (m *ClientWriteModels) AppendDeleteMany(models ...*ClientDeleteManyModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} diff --git a/mongo/errors.go b/mongo/errors.go index e20af03e95..b4de6224b3 100644 --- a/mongo/errors.go +++ b/mongo/errors.go @@ -638,7 +638,7 @@ func (bwe ClientBulkWriteException) Error() string { if len(bwe.WriteErrors) > 0 { errs := make([]error, 0, len(bwe.WriteErrors)) for _, v := range bwe.WriteErrors { - errs = append(errs, &v) + errs = append(errs, v) } causes = append(causes, "write errors: "+joinBatchErrors(errs)) } diff --git a/mongo/integration/unified/client_operation_execution.go b/mongo/integration/unified/client_operation_execution.go index 0a4c381638..80fc1374b4 100644 --- a/mongo/integration/unified/client_operation_execution.go +++ b/mongo/integration/unified/client_operation_execution.go @@ -212,7 +212,10 @@ func executeClientBulkWrite(ctx context.Context, operation *operation) (*operati opts.SetVerboseResults(val.Boolean()) case "writeConcern": var wc writeConcern - bson.Unmarshal(val.Value, &wc) + err := bson.Unmarshal(val.Value, &wc) + if err != nil { + return nil, err + } c, err := wc.toWriteConcernOption() if err != nil { return nil, err diff --git a/mongo/results.go b/mongo/results.go index 0088e734fc..58da18e7fb 100644 --- a/mongo/results.go +++ b/mongo/results.go @@ -14,7 +14,7 @@ import ( "go.mongodb.org/mongo-driver/x/mongo/driver/operation" ) -// BulkWriteResult is the result type returned by a client-level BulkWrite operation. +// ClientBulkWriteResult is the result type returned by a client-level BulkWrite operation. type ClientBulkWriteResult struct { // The number of documents inserted. InsertedCount int64 diff --git a/x/mongo/driver/operation/command.go b/x/mongo/driver/operation/command.go index 6936d91719..443120d130 100644 --- a/x/mongo/driver/operation/command.go +++ b/x/mongo/driver/operation/command.go @@ -54,6 +54,7 @@ func NewCommand(command bsoncore.Document) *Command { } } +// NewCommandFn constructs and returns a new Command. func NewCommandFn(commandFn func([]byte, description.SelectedServer) ([]byte, error)) *Command { return &Command{ commandFn: commandFn, From 08a06dac265f48742aee647e51e11c94852ef2cb Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Fri, 27 Sep 2024 18:56:03 -0400 Subject: [PATCH 05/14] WIP --- mongo/client.go | 10 +- mongo/client_bulk_write.go | 100 +- mongo/errors.go | 13 +- mongo/integration/unified/error.go | 3 + mongo/integration/unified/schema_version.go | 2 +- .../unified/handshakeError.json | 2015 ------ .../unified/handshakeError.yml | 877 --- .../logging/operation-id.json | 2 +- .../server-selection/logging/operation-id.yml | 2 +- .../transactions/unified/mongos-pin-auto.json | 5474 ----------------- .../transactions/unified/mongos-pin-auto.yml | 1705 ----- .../versioned-api/crud-api-version-1.json | 83 +- testdata/versioned-api/crud-api-version-1.yml | 42 + x/mongo/driver/operation/command.go | 12 + 14 files changed, 233 insertions(+), 10107 deletions(-) delete mode 100644 testdata/retryable-writes/unified/handshakeError.json delete mode 100644 testdata/retryable-writes/unified/handshakeError.yml delete mode 100644 testdata/transactions/unified/mongos-pin-auto.json delete mode 100644 testdata/transactions/unified/mongos-pin-auto.yml diff --git a/mongo/client.go b/mongo/client.go index 039583372e..edc6cd487f 100644 --- a/mongo/client.go +++ b/mongo/client.go @@ -876,13 +876,17 @@ func (c *Client) BulkWrite(ctx context.Context, models *ClientWriteModels, return nil, err } + transactionRunning := sess.TransactionRunning() wc := c.writeConcern + if transactionRunning { + wc = nil + } if bwo.WriteConcern != nil { + if transactionRunning { + return nil, errors.New("cannot set write concern after starting a transaction") + } wc = bwo.WriteConcern } - if sess.TransactionRunning() { - wc = nil - } if !writeconcern.AckWrite(wc) { sess = nil } diff --git a/mongo/client_bulk_write.go b/mongo/client_bulk_write.go index f4b9466704..8ec9814d9f 100644 --- a/mongo/client_bulk_write.go +++ b/mongo/client_bulk_write.go @@ -8,6 +8,7 @@ package mongo import ( "context" + "errors" "strconv" "go.mongodb.org/mongo-driver/bson" @@ -40,6 +41,9 @@ type clientBulkWrite struct { } func (bw *clientBulkWrite) execute(ctx context.Context) error { + if len(bw.models) == 0 { + return errors.New("empty write models") + } docs := make([]bsoncore.Document, len(bw.models)) nsMap := make(map[string]int) var nsList []string @@ -170,12 +174,21 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { Database("admin"). Deployment(bw.client.deployment).Crypt(bw.client.cryptFLE). ServerAPI(bw.client.serverAPI).Timeout(bw.client.timeout). - Logger(bw.client.logger).Authenticator(bw.client.authenticator) - err := op.Execute(ctx) - if err != nil { - return err + Logger(bw.client.logger).Authenticator(bw.client.authenticator).Name("bulkWrite") + opErr := op.Execute(ctx) + var wcErrs []*WriteConcernError + if opErr != nil { + if errors.Is(opErr, driver.ErrUnacknowledgedWrite) { + return nil + } + var writeErr driver.WriteCommandError + if errors.As(opErr, &writeErr) { + wcErr := convertDriverWriteConcernError(writeErr.WriteConcernError) + wcErrs = append(wcErrs, wcErr) + } } var res struct { + Ok bool Cursor struct { FirstBatch []bson.Raw } @@ -184,10 +197,12 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { NMatched int32 NModified int32 NUpserted int32 + NErrors int32 + Code int32 + Errmsg string } rawRes := op.Result() - err = bson.Unmarshal(rawRes, &res) - if err != nil { + if err := bson.Unmarshal(rawRes, &res); err != nil { return err } bw.result.DeletedCount = int64(res.NDeleted) @@ -195,22 +210,35 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { bw.result.MatchedCount = int64(res.NMatched) bw.result.ModifiedCount = int64(res.NModified) bw.result.UpsertedCount = int64(res.NUpserted) + errors := make(map[int64]WriteError) for i, cur := range res.Cursor.FirstBatch { switch res := resMap[i].(type) { case map[int64]ClientDeleteResult: - if err = appendDeleteResult(cur, res); err != nil { + if err := appendDeleteResult(cur, res, errors); err != nil { return err } case map[int64]ClientInsertResult: - if err = appendInsertResult(cur, res, insIDMap); err != nil { + if err := appendInsertResult(cur, res, errors, insIDMap); err != nil { return err } case map[int64]ClientUpdateResult: - if err = appendUpdateResult(cur, res); err != nil { + if err := appendUpdateResult(cur, res, errors); err != nil { return err } } } + if !res.Ok || res.NErrors > 0 || opErr != nil { + return ClientBulkWriteException{ + TopLevelError: &WriteError{ + Code: int(res.Code), + Message: res.Errmsg, + Raw: bson.Raw(rawRes), + }, + WriteConcernErrors: wcErrs, + WriteErrors: errors, + PartialResult: &bw.result, + } + } return nil } @@ -383,45 +411,75 @@ func createClientDeleteDoc( return bsoncore.AppendDocumentEnd(doc, didx) } -func appendDeleteResult(cur bson.Raw, m map[int64]ClientDeleteResult) error { +func appendDeleteResult(cur bson.Raw, m map[int64]ClientDeleteResult, e map[int64]WriteError) error { var res struct { - Idx int32 - N int32 + Ok bool + Idx int32 + N int32 + Code int32 + Errmsg string } if err := bson.Unmarshal(cur, &res); err != nil { return err } - m[int64(res.Idx)] = ClientDeleteResult{int64(res.N)} + if res.Ok { + m[int64(res.Idx)] = ClientDeleteResult{int64(res.N)} + } else { + e[int64(res.Idx)] = WriteError{ + Code: int(res.Code), + Message: res.Errmsg, + } + } return nil } -func appendInsertResult(cur bson.Raw, m map[int64]ClientInsertResult, insIdMap map[int]interface{}) error { +func appendInsertResult(cur bson.Raw, m map[int64]ClientInsertResult, e map[int64]WriteError, insIDMap map[int]interface{}) error { var res struct { - Idx int32 + Ok bool + Idx int32 + Code int32 + Errmsg string } if err := bson.Unmarshal(cur, &res); err != nil { return err } - m[int64(res.Idx)] = ClientInsertResult{insIdMap[int(res.Idx)]} + if res.Ok { + m[int64(res.Idx)] = ClientInsertResult{insIDMap[int(res.Idx)]} + } else { + e[int64(res.Idx)] = WriteError{ + Code: int(res.Code), + Message: res.Errmsg, + } + } return nil } -func appendUpdateResult(cur bson.Raw, m map[int64]ClientUpdateResult) error { +func appendUpdateResult(cur bson.Raw, m map[int64]ClientUpdateResult, e map[int64]WriteError) error { var res struct { + Ok bool Idx int32 N int32 NModified int32 Upserted struct { ID interface{} `bson:"_id"` } + Code int32 + Errmsg string } if err := bson.Unmarshal(cur, &res); err != nil { return err } - m[int64(res.Idx)] = ClientUpdateResult{ - MatchedCount: int64(res.N), - ModifiedCount: int64(res.NModified), - UpsertedID: res.Upserted.ID, + if res.Ok { + m[int64(res.Idx)] = ClientUpdateResult{ + MatchedCount: int64(res.N), + ModifiedCount: int64(res.NModified), + UpsertedID: res.Upserted.ID, + } + } else { + e[int64(res.Idx)] = WriteError{ + Code: int(res.Code), + Message: res.Errmsg, + } } return nil } diff --git a/mongo/errors.go b/mongo/errors.go index b4de6224b3..180cc8ce7f 100644 --- a/mongo/errors.go +++ b/mongo/errors.go @@ -611,10 +611,10 @@ func (bwe BulkWriteException) serverError() {} // ClientBulkWriteException is the error type returned by ClientBulkWrite operations. type ClientBulkWriteException struct { - TopLevelError *error + TopLevelError *WriteError // The write concern errors that occurred. - WriteConcernErrors []WriteConcernError + WriteConcernErrors []*WriteConcernError // The write errors that occurred during individual operation execution. WriteErrors map[int64]WriteError @@ -626,12 +626,12 @@ type ClientBulkWriteException struct { func (bwe ClientBulkWriteException) Error() string { causes := make([]string, 0, 4) if bwe.TopLevelError != nil { - causes = append(causes, "top level error: "+(*bwe.TopLevelError).Error()) + causes = append(causes, "top level error: "+bwe.TopLevelError.Error()) } if len(bwe.WriteConcernErrors) > 0 { errs := make([]error, len(bwe.WriteConcernErrors)) for i := 0; i < len(bwe.WriteConcernErrors); i++ { - errs[i] = &bwe.WriteConcernErrors[i] + errs[i] = bwe.WriteConcernErrors[i] } causes = append(causes, "write concern errors: "+joinBatchErrors(errs)) } @@ -643,7 +643,7 @@ func (bwe ClientBulkWriteException) Error() string { causes = append(causes, "write errors: "+joinBatchErrors(errs)) } if bwe.PartialResult != nil { - causes = append(causes, fmt.Sprintf("result: %v", bwe.PartialResult)) + causes = append(causes, fmt.Sprintf("result: %v", *bwe.PartialResult)) } message := "bulk write exception: " @@ -653,9 +653,6 @@ func (bwe ClientBulkWriteException) Error() string { return "bulk write exception: " + strings.Join(causes, ", ") } -// serverError implements the ServerError interface. -func (bwe ClientBulkWriteException) serverError() {} - // returnResult is used to determine if a function calling processWriteError should return // the result or return nil. Since the processWriteError function is used by many different // methods, both *One and *Many, we need a way to differentiate if the method should return diff --git a/mongo/integration/unified/error.go b/mongo/integration/unified/error.go index 0edc79428a..f69a3da341 100644 --- a/mongo/integration/unified/error.go +++ b/mongo/integration/unified/error.go @@ -182,6 +182,9 @@ func extractErrorDetails(err error) (errorDetails, bool) { details.raw = we.Raw } details.labels = converted.Labels + case mongo.ClientBulkWriteException: + details.raw = converted.TopLevelError.Raw + details.codes = append(details.codes, int32(converted.TopLevelError.Code)) default: return errorDetails{}, false } diff --git a/mongo/integration/unified/schema_version.go b/mongo/integration/unified/schema_version.go index 9aec89a18d..c3d02068a3 100644 --- a/mongo/integration/unified/schema_version.go +++ b/mongo/integration/unified/schema_version.go @@ -16,7 +16,7 @@ import ( var ( supportedSchemaVersions = map[int]string{ - 1: "1.17", + 1: "1.21", } ) diff --git a/testdata/retryable-writes/unified/handshakeError.json b/testdata/retryable-writes/unified/handshakeError.json deleted file mode 100644 index 93cb2e849e..0000000000 --- a/testdata/retryable-writes/unified/handshakeError.json +++ /dev/null @@ -1,2015 +0,0 @@ -{ - "description": "retryable writes handshake failures", - "schemaVersion": "1.4", - "runOnRequirements": [ - { - "minServerVersion": "4.2", - "topologies": [ - "replicaset", - "sharded", - "load-balanced" - ], - "auth": true - } - ], - "createEntities": [ - { - "client": { - "id": "client", - "useMultipleMongoses": false, - "observeEvents": [ - "connectionCheckOutStartedEvent", - "commandStartedEvent", - "commandSucceededEvent", - "commandFailedEvent" - ] - } - }, - { - "database": { - "id": "database", - "client": "client", - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "collection": { - "id": "collection", - "database": "database", - "collectionName": "coll" - } - } - ], - "initialData": [ - { - "collectionName": "coll", - "databaseName": "retryable-writes-handshake-tests", - "documents": [ - { - "_id": 1, - "x": 11 - } - ] - } - ], - "tests": [ - { - "description": "client.clientBulkWrite succeeds after retryable handshake network error", - "runOnRequirements": [ - { - "minServerVersion": "8.0", - "serverless": "forbid" - } - ], - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "clientBulkWrite", - "object": "client", - "arguments": { - "models": [ - { - "insertOne": { - "namespace": "retryable-writes-handshake-tests.coll", - "document": { - "_id": 8, - "x": 88 - } - } - } - ] - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "bulkWrite" - } - }, - { - "commandSucceededEvent": { - "commandName": "bulkWrite" - } - } - ] - } - ] - }, - { - "description": "client.clientBulkWrite succeeds after retryable handshake server error (ShutdownInProgress)", - "runOnRequirements": [ - { - "minServerVersion": "8.0", - "serverless": "forbid" - } - ], - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "clientBulkWrite", - "object": "client", - "arguments": { - "models": [ - { - "insertOne": { - "namespace": "retryable-writes-handshake-tests.coll", - "document": { - "_id": 8, - "x": 88 - } - } - } - ] - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "bulkWrite" - } - }, - { - "commandSucceededEvent": { - "commandName": "bulkWrite" - } - } - ] - } - ] - }, - { - "description": "collection.insertOne succeeds after retryable handshake network error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandSucceededEvent": { - "commandName": "insert" - } - } - ] - } - ] - }, - { - "description": "collection.insertOne succeeds after retryable handshake server error (ShutdownInProgress)", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandSucceededEvent": { - "commandName": "insert" - } - } - ] - } - ] - }, - { - "description": "collection.insertMany succeeds after retryable handshake network error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 2, - "x": 22 - } - ] - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandSucceededEvent": { - "commandName": "insert" - } - } - ] - } - ] - }, - { - "description": "collection.insertMany succeeds after retryable handshake server error (ShutdownInProgress)", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 2, - "x": 22 - } - ] - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandSucceededEvent": { - "commandName": "insert" - } - } - ] - } - ] - }, - { - "description": "collection.deleteOne succeeds after retryable handshake network error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "deleteOne", - "object": "collection", - "arguments": { - "filter": {} - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "delete" - } - }, - { - "commandSucceededEvent": { - "commandName": "delete" - } - } - ] - } - ] - }, - { - "description": "collection.deleteOne succeeds after retryable handshake server error (ShutdownInProgress)", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "deleteOne", - "object": "collection", - "arguments": { - "filter": {} - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "delete" - } - }, - { - "commandSucceededEvent": { - "commandName": "delete" - } - } - ] - } - ] - }, - { - "description": "collection.replaceOne succeeds after retryable handshake network error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "replaceOne", - "object": "collection", - "arguments": { - "filter": {}, - "replacement": { - "x": 22 - } - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "update" - } - }, - { - "commandSucceededEvent": { - "commandName": "update" - } - } - ] - } - ] - }, - { - "description": "collection.replaceOne succeeds after retryable handshake server error (ShutdownInProgress)", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "replaceOne", - "object": "collection", - "arguments": { - "filter": {}, - "replacement": { - "x": 22 - } - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "update" - } - }, - { - "commandSucceededEvent": { - "commandName": "update" - } - } - ] - } - ] - }, - { - "description": "collection.updateOne succeeds after retryable handshake network error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "updateOne", - "object": "collection", - "arguments": { - "filter": {}, - "update": { - "$set": { - "x": 22 - } - } - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "update" - } - }, - { - "commandSucceededEvent": { - "commandName": "update" - } - } - ] - } - ] - }, - { - "description": "collection.updateOne succeeds after retryable handshake server error (ShutdownInProgress)", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "updateOne", - "object": "collection", - "arguments": { - "filter": {}, - "update": { - "$set": { - "x": 22 - } - } - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "update" - } - }, - { - "commandSucceededEvent": { - "commandName": "update" - } - } - ] - } - ] - }, - { - "description": "collection.findOneAndDelete succeeds after retryable handshake network error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "findOneAndDelete", - "object": "collection", - "arguments": { - "filter": {} - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandSucceededEvent": { - "commandName": "findAndModify" - } - } - ] - } - ] - }, - { - "description": "collection.findOneAndDelete succeeds after retryable handshake server error (ShutdownInProgress)", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "findOneAndDelete", - "object": "collection", - "arguments": { - "filter": {} - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandSucceededEvent": { - "commandName": "findAndModify" - } - } - ] - } - ] - }, - { - "description": "collection.findOneAndReplace succeeds after retryable handshake network error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "findOneAndReplace", - "object": "collection", - "arguments": { - "filter": {}, - "replacement": { - "x": 22 - } - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandSucceededEvent": { - "commandName": "findAndModify" - } - } - ] - } - ] - }, - { - "description": "collection.findOneAndReplace succeeds after retryable handshake server error (ShutdownInProgress)", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "findOneAndReplace", - "object": "collection", - "arguments": { - "filter": {}, - "replacement": { - "x": 22 - } - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandSucceededEvent": { - "commandName": "findAndModify" - } - } - ] - } - ] - }, - { - "description": "collection.findOneAndUpdate succeeds after retryable handshake network error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "findOneAndUpdate", - "object": "collection", - "arguments": { - "filter": {}, - "update": { - "$set": { - "x": 22 - } - } - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandSucceededEvent": { - "commandName": "findAndModify" - } - } - ] - } - ] - }, - { - "description": "collection.findOneAndUpdate succeeds after retryable handshake server error (ShutdownInProgress)", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "findOneAndUpdate", - "object": "collection", - "arguments": { - "filter": {}, - "update": { - "$set": { - "x": 22 - } - } - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandSucceededEvent": { - "commandName": "findAndModify" - } - } - ] - } - ] - }, - { - "description": "collection.bulkWrite succeeds after retryable handshake network error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "bulkWrite", - "object": "collection", - "arguments": { - "requests": [ - { - "insertOne": { - "document": { - "_id": 2, - "x": 22 - } - } - } - ] - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandSucceededEvent": { - "commandName": "insert" - } - } - ] - } - ] - }, - { - "description": "collection.bulkWrite succeeds after retryable handshake server error (ShutdownInProgress)", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "ping", - "saslContinue" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isError": true - } - }, - { - "name": "bulkWrite", - "object": "collection", - "arguments": { - "requests": [ - { - "insertOne": { - "document": { - "_id": 2, - "x": 22 - } - } - } - ] - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - }, - { - "connectionCheckOutStartedEvent": {} - } - ] - }, - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1 - }, - "databaseName": "retryable-writes-handshake-tests" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandSucceededEvent": { - "commandName": "insert" - } - } - ] - } - ] - } - ] -} diff --git a/testdata/retryable-writes/unified/handshakeError.yml b/testdata/retryable-writes/unified/handshakeError.yml deleted file mode 100644 index 1743463370..0000000000 --- a/testdata/retryable-writes/unified/handshakeError.yml +++ /dev/null @@ -1,877 +0,0 @@ -# Tests in this file are generated from handshakeError.yml.template. - -description: "retryable writes handshake failures" - -schemaVersion: "1.4" # For `serverless: forbid` - -runOnRequirements: - - minServerVersion: "4.2" - topologies: [replicaset, sharded, load-balanced] - auth: true - -createEntities: - - client: - id: &client client - useMultipleMongoses: false - observeEvents: - - connectionCheckOutStartedEvent - - commandStartedEvent - - commandSucceededEvent - - commandFailedEvent - - database: - id: &database database - client: *client - databaseName: &databaseName retryable-writes-handshake-tests - - collection: - id: &collection collection - database: *database - collectionName: &collectionName coll - -initialData: - - collectionName: *collectionName - databaseName: *databaseName - documents: - - { _id: 1, x: 11 } - -tests: - # Because setting a failPoint creates a connection in the connection pool, run - # a ping operation that fails immediately after the failPoint operation in - # order to discard the connection before running the actual operation to be - # tested. The saslContinue command is used to avoid SDAM errors. - # - # Description of events: - # - Failpoint operation. - # - Creates a connection in the connection pool that must be closed. - # - Ping operation. - # - Triggers failpoint (first time). - # - Closes the connection made by the fail point operation. - # - Test operation. - # - New connection is created. - # - Triggers failpoint (second time). - # - Tests whether operation successfully retries the handshake and succeeds. - - - description: "client.clientBulkWrite succeeds after retryable handshake network error" - runOnRequirements: - - minServerVersion: "8.0" # `bulkWrite` added to server 8.0 - serverless: forbid - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: clientBulkWrite - object: *client - arguments: - models: - - insertOne: - namespace: retryable-writes-handshake-tests.coll - document: { _id: 8, x: 88 } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: bulkWrite - - commandSucceededEvent: - commandName: bulkWrite - - - description: "client.clientBulkWrite succeeds after retryable handshake server error (ShutdownInProgress)" - runOnRequirements: - - minServerVersion: "8.0" # `bulkWrite` added to server 8.0 - serverless: forbid - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: clientBulkWrite - object: *client - arguments: - models: - - insertOne: - namespace: retryable-writes-handshake-tests.coll - document: { _id: 8, x: 88 } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: bulkWrite - - commandSucceededEvent: - commandName: bulkWrite - - - description: "collection.insertOne succeeds after retryable handshake network error" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: insertOne - object: *collection - arguments: - document: { _id: 2, x: 22 } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: insert - - commandSucceededEvent: - commandName: insert - - - description: "collection.insertOne succeeds after retryable handshake server error (ShutdownInProgress)" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: insertOne - object: *collection - arguments: - document: { _id: 2, x: 22 } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: insert - - commandSucceededEvent: - commandName: insert - - - description: "collection.insertMany succeeds after retryable handshake network error" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: insertMany - object: *collection - arguments: - documents: - - { _id: 2, x: 22 } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: insert - - commandSucceededEvent: - commandName: insert - - - description: "collection.insertMany succeeds after retryable handshake server error (ShutdownInProgress)" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: insertMany - object: *collection - arguments: - documents: - - { _id: 2, x: 22 } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: insert - - commandSucceededEvent: - commandName: insert - - - description: "collection.deleteOne succeeds after retryable handshake network error" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: deleteOne - object: *collection - arguments: - filter: {} - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: delete - - commandSucceededEvent: - commandName: delete - - - description: "collection.deleteOne succeeds after retryable handshake server error (ShutdownInProgress)" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: deleteOne - object: *collection - arguments: - filter: {} - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: delete - - commandSucceededEvent: - commandName: delete - - - description: "collection.replaceOne succeeds after retryable handshake network error" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: replaceOne - object: *collection - arguments: - filter: {} - replacement: { x: 22 } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: update - - commandSucceededEvent: - commandName: update - - - description: "collection.replaceOne succeeds after retryable handshake server error (ShutdownInProgress)" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: replaceOne - object: *collection - arguments: - filter: {} - replacement: { x: 22 } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: update - - commandSucceededEvent: - commandName: update - - - description: "collection.updateOne succeeds after retryable handshake network error" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: updateOne - object: *collection - arguments: - filter: {} - update: { $set: { x: 22 } } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: update - - commandSucceededEvent: - commandName: update - - - description: "collection.updateOne succeeds after retryable handshake server error (ShutdownInProgress)" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: updateOne - object: *collection - arguments: - filter: {} - update: { $set: { x: 22 } } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: update - - commandSucceededEvent: - commandName: update - - - description: "collection.findOneAndDelete succeeds after retryable handshake network error" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: findOneAndDelete - object: *collection - arguments: - filter: {} - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: findAndModify - - commandSucceededEvent: - commandName: findAndModify - - - description: "collection.findOneAndDelete succeeds after retryable handshake server error (ShutdownInProgress)" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: findOneAndDelete - object: *collection - arguments: - filter: {} - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: findAndModify - - commandSucceededEvent: - commandName: findAndModify - - - description: "collection.findOneAndReplace succeeds after retryable handshake network error" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: findOneAndReplace - object: *collection - arguments: - filter: {} - replacement: { x: 22 } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: findAndModify - - commandSucceededEvent: - commandName: findAndModify - - - description: "collection.findOneAndReplace succeeds after retryable handshake server error (ShutdownInProgress)" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: findOneAndReplace - object: *collection - arguments: - filter: {} - replacement: { x: 22 } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: findAndModify - - commandSucceededEvent: - commandName: findAndModify - - - description: "collection.findOneAndUpdate succeeds after retryable handshake network error" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: findOneAndUpdate - object: *collection - arguments: - filter: {} - update: { $set: { x: 22 } } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: findAndModify - - commandSucceededEvent: - commandName: findAndModify - - - description: "collection.findOneAndUpdate succeeds after retryable handshake server error (ShutdownInProgress)" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: findOneAndUpdate - object: *collection - arguments: - filter: {} - update: { $set: { x: 22 } } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: findAndModify - - commandSucceededEvent: - commandName: findAndModify - - - description: "collection.bulkWrite succeeds after retryable handshake network error" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: bulkWrite - object: *collection - arguments: - requests: - - insertOne: - document: { _id: 2, x: 22 } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: insert - - commandSucceededEvent: - commandName: insert - - - description: "collection.bulkWrite succeeds after retryable handshake server error (ShutdownInProgress)" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: [ping, saslContinue] - closeConnection: true - - name: runCommand - object: *database - arguments: { commandName: ping, command: { ping: 1 } } - expectError: { isError: true } - - name: bulkWrite - object: *collection - arguments: - requests: - - insertOne: - document: { _id: 2, x: 22 } - expectEvents: - - client: *client - eventType: cmap - events: - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - { connectionCheckOutStartedEvent: {} } - - client: *client - events: - - commandStartedEvent: - command: { ping: 1 } - databaseName: *databaseName - - commandFailedEvent: - commandName: ping - - commandStartedEvent: - commandName: insert - - commandSucceededEvent: - commandName: insert diff --git a/testdata/server-selection/logging/operation-id.json b/testdata/server-selection/logging/operation-id.json index c1024184ff..72ebff60d8 100644 --- a/testdata/server-selection/logging/operation-id.json +++ b/testdata/server-selection/logging/operation-id.json @@ -345,7 +345,7 @@ } }, { - "name": "bulkWrite", + "name": "clientBulkWrite", "object": "client", "arguments": { "models": [ diff --git a/testdata/server-selection/logging/operation-id.yml b/testdata/server-selection/logging/operation-id.yml index 1687fc392d..e5732893fa 100644 --- a/testdata/server-selection/logging/operation-id.yml +++ b/testdata/server-selection/logging/operation-id.yml @@ -191,7 +191,7 @@ tests: newDescription: type: Unknown count: 1 - - name: bulkWrite + - name: clientBulkWrite object: *client arguments: models: diff --git a/testdata/transactions/unified/mongos-pin-auto.json b/testdata/transactions/unified/mongos-pin-auto.json deleted file mode 100644 index 27db520401..0000000000 --- a/testdata/transactions/unified/mongos-pin-auto.json +++ /dev/null @@ -1,5474 +0,0 @@ -{ - "description": "mongos-pin-auto", - "schemaVersion": "1.4", - "runOnRequirements": [ - { - "minServerVersion": "4.1.8", - "topologies": [ - "sharded" - ], - "serverless": "forbid" - } - ], - "createEntities": [ - { - "client": { - "id": "client0", - "useMultipleMongoses": true, - "observeEvents": [ - "commandStartedEvent" - ] - } - }, - { - "database": { - "id": "database0", - "client": "client0", - "databaseName": "transaction-tests" - } - }, - { - "collection": { - "id": "collection0", - "database": "database0", - "collectionName": "test" - } - }, - { - "session": { - "id": "session0", - "client": "client0" - } - } - ], - "initialData": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ], - "tests": [ - { - "description": "remain pinned after non-transient Interrupted error on insertOne", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "object": "testRunner", - "name": "targetedFailPoint", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11601 - } - } - } - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 4 - } - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError", - "UnknownTransactionCommitResult" - ], - "errorCodeName": "Interrupted" - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "commitTransaction" - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 3 - } - ], - "ordered": true, - "readConcern": { - "$$exists": false - }, - "lsid": { - "$$sessionLsid": "session0" - }, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": { - "$$exists": false - } - }, - "commandName": "insert", - "databaseName": "transaction-tests" - } - }, - { - "commandStartedEvent": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 4 - } - ], - "ordered": true, - "readConcern": { - "$$exists": false - }, - "lsid": { - "$$sessionLsid": "session0" - }, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": { - "$$exists": false - }, - "autocommit": false, - "writeConcern": { - "$$exists": false - } - }, - "commandName": "insert", - "databaseName": "transaction-tests" - } - }, - { - "commandStartedEvent": { - "command": { - "commitTransaction": 1, - "lsid": { - "$$sessionLsid": "session0" - }, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": { - "$$exists": false - }, - "autocommit": false, - "writeConcern": { - "$$exists": false - }, - "recoveryToken": { - "$$exists": true - } - }, - "commandName": "commitTransaction", - "databaseName": "admin" - } - } - ] - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - } - ] - } - ] - }, - { - "description": "unpin after transient error within a transaction", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "object": "testRunner", - "name": "targetedFailPoint", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - } - } - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 4 - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "UnknownTransactionCommitResult" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 3 - } - ], - "ordered": true, - "readConcern": { - "$$exists": false - }, - "lsid": { - "$$sessionLsid": "session0" - }, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": { - "$$exists": false - } - }, - "commandName": "insert", - "databaseName": "transaction-tests" - } - }, - { - "commandStartedEvent": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 4 - } - ], - "ordered": true, - "readConcern": { - "$$exists": false - }, - "lsid": { - "$$sessionLsid": "session0" - }, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": { - "$$exists": false - }, - "autocommit": false, - "writeConcern": { - "$$exists": false - } - }, - "commandName": "insert", - "databaseName": "transaction-tests" - } - }, - { - "commandStartedEvent": { - "command": { - "abortTransaction": 1, - "lsid": { - "$$sessionLsid": "session0" - }, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": { - "$$exists": false - }, - "autocommit": false, - "writeConcern": { - "$$exists": false - }, - "recoveryToken": { - "$$exists": true - } - }, - "commandName": "abortTransaction", - "databaseName": "admin" - } - } - ] - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on insertOne insert", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "session": "session0", - "document": { - "_id": 4 - } - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on insertMany insert", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "insertMany", - "object": "collection0", - "arguments": { - "session": "session0", - "documents": [ - { - "_id": 4 - }, - { - "_id": 5 - } - ] - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on updateOne update", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "updateOne", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on replaceOne update", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "replaceOne", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "replacement": { - "y": 1 - } - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on updateMany update", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "updateMany", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 1 - } - }, - "update": { - "$set": { - "z": 1 - } - } - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on deleteOne delete", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "deleteOne", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on deleteMany delete", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "deleteMany", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 1 - } - } - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on findOneAndDelete findAndModify", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "findOneAndDelete", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on findOneAndUpdate findAndModify", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "findOneAndUpdate", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on findOneAndReplace findAndModify", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "findOneAndReplace", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "replacement": { - "y": 1 - }, - "returnDocument": "Before" - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on bulkWrite insert", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection0", - "arguments": { - "session": "session0", - "requests": [ - { - "insertOne": { - "document": { - "_id": 1 - } - } - } - ] - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on bulkWrite update", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection0", - "arguments": { - "session": "session0", - "requests": [ - { - "updateOne": { - "filter": { - "_id": 1 - }, - "update": { - "$set": { - "x": 1 - } - } - } - } - ] - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on bulkWrite delete", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection0", - "arguments": { - "session": "session0", - "requests": [ - { - "deleteOne": { - "filter": { - "_id": 1 - } - } - } - ] - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on find find", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "find", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on countDocuments aggregate", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "countDocuments", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": {} - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on aggregate aggregate", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "aggregate", - "object": "collection0", - "arguments": { - "session": "session0", - "pipeline": [] - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on distinct distinct", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "distinct", - "object": "collection0", - "arguments": { - "session": "session0", - "fieldName": "_id", - "filter": {} - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on runCommand insert", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "runCommand", - "object": "database0", - "arguments": { - "session": "session0", - "commandName": "insert", - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ] - } - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "remain pinned after non-transient Interrupted error on clientBulkWrite bulkWrite", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "bulkWrite" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "clientBulkWrite", - "object": "client0", - "arguments": { - "session": "session0", - "models": [ - { - "insertOne": { - "namespace": "database0.collection0", - "document": { - "_id": 8, - "x": 88 - } - } - } - ] - }, - "expectError": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionPinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ], - "runOnRequirements": [ - { - "minServerVersion": "8.0" - } - ] - }, - { - "description": "unpin after transient connection error on insertOne insert", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - } - } - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "session": "session0", - "document": { - "_id": 4 - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on insertOne insert", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "session": "session0", - "document": { - "_id": 4 - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on insertMany insert", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - } - } - }, - { - "name": "insertMany", - "object": "collection0", - "arguments": { - "session": "session0", - "documents": [ - { - "_id": 4 - }, - { - "_id": 5 - } - ] - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on insertMany insert", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "insertMany", - "object": "collection0", - "arguments": { - "session": "session0", - "documents": [ - { - "_id": 4 - }, - { - "_id": 5 - } - ] - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on updateOne update", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "closeConnection": true - } - } - } - }, - { - "name": "updateOne", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on updateOne update", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "updateOne", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on replaceOne update", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "closeConnection": true - } - } - } - }, - { - "name": "replaceOne", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "replacement": { - "y": 1 - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on replaceOne update", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "replaceOne", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "replacement": { - "y": 1 - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on updateMany update", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "closeConnection": true - } - } - } - }, - { - "name": "updateMany", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 1 - } - }, - "update": { - "$set": { - "z": 1 - } - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on updateMany update", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "updateMany", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 1 - } - }, - "update": { - "$set": { - "z": 1 - } - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on deleteOne delete", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "closeConnection": true - } - } - } - }, - { - "name": "deleteOne", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on deleteOne delete", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "deleteOne", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on deleteMany delete", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "closeConnection": true - } - } - } - }, - { - "name": "deleteMany", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 1 - } - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on deleteMany delete", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "deleteMany", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 1 - } - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on findOneAndDelete findAndModify", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "closeConnection": true - } - } - } - }, - { - "name": "findOneAndDelete", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on findOneAndDelete findAndModify", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "findOneAndDelete", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on findOneAndUpdate findAndModify", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "closeConnection": true - } - } - } - }, - { - "name": "findOneAndUpdate", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on findOneAndUpdate findAndModify", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "findOneAndUpdate", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on findOneAndReplace findAndModify", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "closeConnection": true - } - } - } - }, - { - "name": "findOneAndReplace", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "replacement": { - "y": 1 - }, - "returnDocument": "Before" - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on findOneAndReplace findAndModify", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "findOneAndReplace", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "replacement": { - "y": 1 - }, - "returnDocument": "Before" - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on bulkWrite insert", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection0", - "arguments": { - "session": "session0", - "requests": [ - { - "insertOne": { - "document": { - "_id": 1 - } - } - } - ] - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on bulkWrite insert", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection0", - "arguments": { - "session": "session0", - "requests": [ - { - "insertOne": { - "document": { - "_id": 1 - } - } - } - ] - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on bulkWrite update", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "closeConnection": true - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection0", - "arguments": { - "session": "session0", - "requests": [ - { - "updateOne": { - "filter": { - "_id": 1 - }, - "update": { - "$set": { - "x": 1 - } - } - } - } - ] - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on bulkWrite update", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection0", - "arguments": { - "session": "session0", - "requests": [ - { - "updateOne": { - "filter": { - "_id": 1 - }, - "update": { - "$set": { - "x": 1 - } - } - } - } - ] - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on bulkWrite delete", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "closeConnection": true - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection0", - "arguments": { - "session": "session0", - "requests": [ - { - "deleteOne": { - "filter": { - "_id": 1 - } - } - } - ] - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on bulkWrite delete", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection0", - "arguments": { - "session": "session0", - "requests": [ - { - "deleteOne": { - "filter": { - "_id": 1 - } - } - } - ] - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on find find", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - } - } - }, - { - "name": "find", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on find find", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "find", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on countDocuments aggregate", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - } - } - }, - { - "name": "countDocuments", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": {} - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on countDocuments aggregate", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "countDocuments", - "object": "collection0", - "arguments": { - "session": "session0", - "filter": {} - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on aggregate aggregate", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - } - } - }, - { - "name": "aggregate", - "object": "collection0", - "arguments": { - "session": "session0", - "pipeline": [] - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on aggregate aggregate", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "aggregate", - "object": "collection0", - "arguments": { - "session": "session0", - "pipeline": [] - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on distinct distinct", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "closeConnection": true - } - } - } - }, - { - "name": "distinct", - "object": "collection0", - "arguments": { - "session": "session0", - "fieldName": "_id", - "filter": {} - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on distinct distinct", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "distinct", - "object": "collection0", - "arguments": { - "session": "session0", - "fieldName": "_id", - "filter": {} - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on runCommand insert", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database0", - "arguments": { - "session": "session0", - "commandName": "insert", - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ] - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on runCommand insert", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "runCommand", - "object": "database0", - "arguments": { - "session": "session0", - "commandName": "insert", - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ] - } - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ] - }, - { - "description": "unpin after transient connection error on clientBulkWrite bulkWrite", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "bulkWrite" - ], - "closeConnection": true - } - } - } - }, - { - "name": "clientBulkWrite", - "object": "client0", - "arguments": { - "session": "session0", - "models": [ - { - "insertOne": { - "namespace": "database0.collection0", - "document": { - "_id": 8, - "x": 88 - } - } - } - ] - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ], - "runOnRequirements": [ - { - "minServerVersion": "8.0" - } - ] - }, - { - "description": "unpin after transient ShutdownInProgress error on clientBulkWrite bulkWrite", - "operations": [ - { - "object": "session0", - "name": "startTransaction" - }, - { - "object": "collection0", - "name": "insertOne", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 3 - } - } - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "bulkWrite" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "clientBulkWrite", - "object": "client0", - "arguments": { - "session": "session0", - "models": [ - { - "insertOne": { - "namespace": "database0.collection0", - "document": { - "_id": 8, - "x": 88 - } - } - } - ] - }, - "expectError": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "object": "testRunner", - "name": "assertSessionUnpinned", - "arguments": { - "session": "session0" - } - }, - { - "object": "session0", - "name": "abortTransaction" - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "transaction-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ], - "runOnRequirements": [ - { - "minServerVersion": "8.0" - } - ] - } - ] -} diff --git a/testdata/transactions/unified/mongos-pin-auto.yml b/testdata/transactions/unified/mongos-pin-auto.yml deleted file mode 100644 index db620c371b..0000000000 --- a/testdata/transactions/unified/mongos-pin-auto.yml +++ /dev/null @@ -1,1705 +0,0 @@ -# Autogenerated tests that transient errors in a transaction unpin the session. -# See mongos-pin-auto-tests.py - -description: mongos-pin-auto - -schemaVersion: '1.4' - -runOnRequirements: - - minServerVersion: "4.1.8" - # Note: tests utilize targetedFailPoint, which is incompatible with - # load-balanced and useMultipleMongoses:true - topologies: [ sharded ] - # serverless proxy doesn't append error labels to errors in transactions - # caused by failpoints (CLOUDP-88216) - serverless: "forbid" - -createEntities: - - client: - id: &client0 client0 - useMultipleMongoses: true - observeEvents: [ commandStartedEvent ] - - database: - id: &database0 database0 - client: *client0 - databaseName: &database_name transaction-tests - - collection: - id: &collection0 collection0 - database: *database0 - collectionName: &collection_name test - - session: - id: &session0 session0 - client: *client0 - -initialData: - - collectionName: *collection_name - databaseName: *database_name - documents: &data - - { _id: 1 } - - { _id: 2 } - -tests: - - description: remain pinned after non-transient Interrupted error on insertOne - operations: - - &startTransaction - object: session0 - name: startTransaction - - &initialCommand - object: *collection0 - name: insertOne - arguments: - session: *session0 - document: { _id: 3 } - expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } } - - object: testRunner - name: targetedFailPoint - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [ "insert" ] - errorCode: 11601 - - object: *collection0 - name: insertOne - arguments: - session: *session0 - document: { _id: 4 } - expectError: - errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] - errorCodeName: Interrupted - - &assertSessionPinned - object: testRunner - name: assertSessionPinned - arguments: - session: *session0 - - &commitTransaction - object: *session0 - name: commitTransaction - expectEvents: - - client: *client0 - events: - - commandStartedEvent: - command: - insert: *collection_name - documents: - - { _id: 3 } - ordered: true - readConcern: { $$exists: false } - lsid: { $$sessionLsid: *session0 } - txnNumber: { $numberLong: '1' } - startTransaction: true - autocommit: false - writeConcern: { $$exists: false } - commandName: insert - databaseName: *database_name - - commandStartedEvent: - command: - insert: *collection_name - documents: - - { _id: 4 } - ordered: true - readConcern: { $$exists: false } - lsid: { $$sessionLsid: *session0 } - txnNumber: { $numberLong: '1' } - startTransaction: { $$exists: false } - autocommit: false - writeConcern: { $$exists: false } - commandName: insert - databaseName: *database_name - - commandStartedEvent: - command: - commitTransaction: 1 - lsid: { $$sessionLsid: *session0 } - txnNumber: { $numberLong: '1' } - startTransaction: { $$exists: false } - autocommit: false - writeConcern: { $$exists: false } - recoveryToken: { $$exists: true } - commandName: commitTransaction - databaseName: admin - outcome: - - collectionName: *collection_name - databaseName: *database_name - documents: - - { _id: 1 } - - { _id: 2 } - - { _id: 3 } - - - description: 'unpin after transient error within a transaction' - operations: - - *startTransaction - - *initialCommand - - object: testRunner - name: targetedFailPoint - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [ "insert" ] - closeConnection: true - - object: *collection0 - name: insertOne - arguments: - session: *session0 - document: { _id: 4 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["UnknownTransactionCommitResult"] - - &assertSessionUnpinned - object: testRunner - name: assertSessionUnpinned - arguments: - session: *session0 - - &abortTransaction - object: *session0 - name: abortTransaction - expectEvents: - - client: *client0 - events: - - commandStartedEvent: - command: - insert: *collection_name - documents: - - { _id: 3 } - ordered: true - readConcern: { $$exists: false } - lsid: { $$sessionLsid: *session0 } - txnNumber: { $numberLong: '1' } - startTransaction: true - autocommit: false - writeConcern: { $$exists: false } - commandName: insert - databaseName: *database_name - - commandStartedEvent: - command: - insert: *collection_name - documents: - - { _id: 4 } - ordered: true - readConcern: { $$exists: false } - lsid: { $$sessionLsid: *session0 } - txnNumber: { $numberLong: '1' } - startTransaction: { $$exists: false } - autocommit: false - writeConcern: { $$exists: false } - commandName: insert - databaseName: *database_name - - commandStartedEvent: - command: - abortTransaction: 1 - lsid: { $$sessionLsid: *session0 } - txnNumber: { $numberLong: '1' } - startTransaction: { $$exists: false } - autocommit: false - writeConcern: { $$exists: false } - recoveryToken: { $$exists: true } - commandName: abortTransaction - databaseName: admin - outcome: &outcome - - collectionName: *collection_name - databaseName: *database_name - documents: *data - - # The rest of the tests in this file test every operation type against - # multiple types of transient errors (connection and error code). - - - description: remain pinned after non-transient Interrupted error on insertOne insert - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 11601 - - name: insertOne - object: *collection0 - arguments: - session: *session0 - document: { _id: 4 } - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on insertMany insert - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 11601 - - name: insertMany - object: *collection0 - arguments: - session: *session0 - documents: [ { _id: 4 }, { _id: 5 } ] - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on updateOne update - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 11601 - - name: updateOne - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - update: { $inc: { x: 1 } } - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on replaceOne update - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 11601 - - name: replaceOne - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - replacement: { y: 1 } - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on updateMany update - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 11601 - - name: updateMany - object: *collection0 - arguments: - session: *session0 - filter: { _id: { $gte: 1 } } - update: {$set: { z: 1 } } - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on deleteOne delete - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - errorCode: 11601 - - name: deleteOne - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on deleteMany delete - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - errorCode: 11601 - - name: deleteMany - object: *collection0 - arguments: - session: *session0 - filter: { _id: { $gte: 1 } } - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on findOneAndDelete findAndModify - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - errorCode: 11601 - - name: findOneAndDelete - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on findOneAndUpdate findAndModify - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - errorCode: 11601 - - name: findOneAndUpdate - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - update: { $inc: { x: 1 } } - returnDocument: Before - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on findOneAndReplace findAndModify - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - errorCode: 11601 - - name: findOneAndReplace - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - replacement: { y: 1 } - returnDocument: Before - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on bulkWrite insert - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 11601 - - name: bulkWrite - object: *collection0 - arguments: - session: *session0 - requests: - - insertOne: - document: { _id: 1 } - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on bulkWrite update - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 11601 - - name: bulkWrite - object: *collection0 - arguments: - session: *session0 - requests: - - updateOne: - filter: { _id: 1 } - update: { $set: { x: 1 } } - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on bulkWrite delete - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - errorCode: 11601 - - name: bulkWrite - object: *collection0 - arguments: - session: *session0 - requests: - - deleteOne: - filter: { _id: 1 } - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on find find - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["find"] - errorCode: 11601 - - name: find - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on countDocuments aggregate - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["aggregate"] - errorCode: 11601 - - name: countDocuments - object: *collection0 - arguments: - session: *session0 - filter: {} - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on aggregate aggregate - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["aggregate"] - errorCode: 11601 - - name: aggregate - object: *collection0 - arguments: - session: *session0 - pipeline: [] - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on distinct distinct - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["distinct"] - errorCode: 11601 - - name: distinct - object: *collection0 - arguments: - session: *session0 - fieldName: _id - filter: {} - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on runCommand insert - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 11601 - - name: runCommand - object: *database0 - arguments: - session: *session0 - commandName: insert - command: - insert: *collection_name - documents: - - { _id : 1 } - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on clientBulkWrite bulkWrite - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["bulkWrite"] - errorCode: 11601 - - name: clientBulkWrite - object: *client0 - arguments: - session: *session0 - models: - - insertOne: - namespace: database0.collection0 - document: { _id: 8, x: 88 } - expectError: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - runOnRequirements: - - minServerVersion: "8.0" # `bulkWrite` added to server 8.0" - - - description: unpin after transient connection error on insertOne insert - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - closeConnection: true - - name: insertOne - object: *collection0 - arguments: - session: *session0 - document: { _id: 4 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on insertOne insert - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 91 - - name: insertOne - object: *collection0 - arguments: - session: *session0 - document: { _id: 4 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on insertMany insert - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - closeConnection: true - - name: insertMany - object: *collection0 - arguments: - session: *session0 - documents: [ { _id: 4 }, { _id: 5 } ] - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on insertMany insert - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 91 - - name: insertMany - object: *collection0 - arguments: - session: *session0 - documents: [ { _id: 4 }, { _id: 5 } ] - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on updateOne update - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - closeConnection: true - - name: updateOne - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - update: { $inc: { x: 1 } } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on updateOne update - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 91 - - name: updateOne - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - update: { $inc: { x: 1 } } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on replaceOne update - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - closeConnection: true - - name: replaceOne - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - replacement: { y: 1 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on replaceOne update - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 91 - - name: replaceOne - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - replacement: { y: 1 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on updateMany update - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - closeConnection: true - - name: updateMany - object: *collection0 - arguments: - session: *session0 - filter: { _id: { $gte: 1 } } - update: {$set: { z: 1 } } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on updateMany update - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 91 - - name: updateMany - object: *collection0 - arguments: - session: *session0 - filter: { _id: { $gte: 1 } } - update: {$set: { z: 1 } } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on deleteOne delete - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - closeConnection: true - - name: deleteOne - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on deleteOne delete - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - errorCode: 91 - - name: deleteOne - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on deleteMany delete - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - closeConnection: true - - name: deleteMany - object: *collection0 - arguments: - session: *session0 - filter: { _id: { $gte: 1 } } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on deleteMany delete - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - errorCode: 91 - - name: deleteMany - object: *collection0 - arguments: - session: *session0 - filter: { _id: { $gte: 1 } } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on findOneAndDelete findAndModify - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - closeConnection: true - - name: findOneAndDelete - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on findOneAndDelete findAndModify - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - errorCode: 91 - - name: findOneAndDelete - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on findOneAndUpdate findAndModify - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - closeConnection: true - - name: findOneAndUpdate - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - update: { $inc: { x: 1 } } - returnDocument: Before - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on findOneAndUpdate findAndModify - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - errorCode: 91 - - name: findOneAndUpdate - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - update: { $inc: { x: 1 } } - returnDocument: Before - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on findOneAndReplace findAndModify - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - closeConnection: true - - name: findOneAndReplace - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - replacement: { y: 1 } - returnDocument: Before - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on findOneAndReplace findAndModify - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - errorCode: 91 - - name: findOneAndReplace - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - replacement: { y: 1 } - returnDocument: Before - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on bulkWrite insert - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - closeConnection: true - - name: bulkWrite - object: *collection0 - arguments: - session: *session0 - requests: - - insertOne: - document: { _id: 1 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on bulkWrite insert - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 91 - - name: bulkWrite - object: *collection0 - arguments: - session: *session0 - requests: - - insertOne: - document: { _id: 1 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on bulkWrite update - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - closeConnection: true - - name: bulkWrite - object: *collection0 - arguments: - session: *session0 - requests: - - updateOne: - filter: { _id: 1 } - update: { $set: { x: 1 } } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on bulkWrite update - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 91 - - name: bulkWrite - object: *collection0 - arguments: - session: *session0 - requests: - - updateOne: - filter: { _id: 1 } - update: { $set: { x: 1 } } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on bulkWrite delete - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - closeConnection: true - - name: bulkWrite - object: *collection0 - arguments: - session: *session0 - requests: - - deleteOne: - filter: { _id: 1 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on bulkWrite delete - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - errorCode: 91 - - name: bulkWrite - object: *collection0 - arguments: - session: *session0 - requests: - - deleteOne: - filter: { _id: 1 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on find find - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["find"] - closeConnection: true - - name: find - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on find find - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["find"] - errorCode: 91 - - name: find - object: *collection0 - arguments: - session: *session0 - filter: { _id: 1 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on countDocuments aggregate - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["aggregate"] - closeConnection: true - - name: countDocuments - object: *collection0 - arguments: - session: *session0 - filter: {} - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on countDocuments aggregate - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["aggregate"] - errorCode: 91 - - name: countDocuments - object: *collection0 - arguments: - session: *session0 - filter: {} - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on aggregate aggregate - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["aggregate"] - closeConnection: true - - name: aggregate - object: *collection0 - arguments: - session: *session0 - pipeline: [] - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on aggregate aggregate - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["aggregate"] - errorCode: 91 - - name: aggregate - object: *collection0 - arguments: - session: *session0 - pipeline: [] - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on distinct distinct - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["distinct"] - closeConnection: true - - name: distinct - object: *collection0 - arguments: - session: *session0 - fieldName: _id - filter: {} - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on distinct distinct - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["distinct"] - errorCode: 91 - - name: distinct - object: *collection0 - arguments: - session: *session0 - fieldName: _id - filter: {} - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on runCommand insert - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - closeConnection: true - - name: runCommand - object: *database0 - arguments: - session: *session0 - commandName: insert - command: - insert: *collection_name - documents: - - { _id : 1 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on runCommand insert - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 91 - - name: runCommand - object: *database0 - arguments: - session: *session0 - commandName: insert - command: - insert: *collection_name - documents: - - { _id : 1 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on clientBulkWrite bulkWrite - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["bulkWrite"] - closeConnection: true - - name: clientBulkWrite - object: *client0 - arguments: - session: *session0 - models: - - insertOne: - namespace: database0.collection0 - document: { _id: 8, x: 88 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - runOnRequirements: - - minServerVersion: "8.0" # `bulkWrite` added to server 8.0" - - - description: unpin after transient ShutdownInProgress error on clientBulkWrite bulkWrite - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: *session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["bulkWrite"] - errorCode: 91 - - name: clientBulkWrite - object: *client0 - arguments: - session: *session0 - models: - - insertOne: - namespace: database0.collection0 - document: { _id: 8, x: 88 } - expectError: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - runOnRequirements: - - minServerVersion: "8.0" # `bulkWrite` added to server 8.0" diff --git a/testdata/versioned-api/crud-api-version-1.json b/testdata/versioned-api/crud-api-version-1.json index a387d0587e..23ef59a6d9 100644 --- a/testdata/versioned-api/crud-api-version-1.json +++ b/testdata/versioned-api/crud-api-version-1.json @@ -50,7 +50,8 @@ }, "apiDeprecationErrors": true } - ] + ], + "namespace": "versioned-api-tests.test" }, "initialData": [ { @@ -426,6 +427,86 @@ } ] }, + { + "description": "client bulkWrite appends declared API version", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "operations": [ + { + "name": "clientBulkWrite", + "object": "client", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "versioned-api-tests.test", + "document": { + "_id": 6, + "x": 6 + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 6 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 6, + "x": 6 + } + } + ], + "nsInfo": [ + { + "ns": "versioned-api-tests.test" + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, { "description": "countDocuments appends declared API version", "operations": [ diff --git a/testdata/versioned-api/crud-api-version-1.yml b/testdata/versioned-api/crud-api-version-1.yml index 50135c1458..01e0323420 100644 --- a/testdata/versioned-api/crud-api-version-1.yml +++ b/testdata/versioned-api/crud-api-version-1.yml @@ -34,6 +34,7 @@ _yamlAnchors: apiVersion: "1" apiStrict: { $$unsetOrMatches: false } apiDeprecationErrors: true + namespace: &namespace "versioned-api-tests.test" initialData: - collectionName: *collectionName @@ -155,6 +156,47 @@ tests: multi: { $$unsetOrMatches: false } upsert: true <<: *expectedApiVersion + + - description: "client bulkWrite appends declared API version" + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0 + serverless: forbid + operations: + - name: clientBulkWrite + object: *client + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 6, x: 6 } + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 6 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 6, x: 6 } + nsInfo: + - { ns: *namespace } + <<: *expectedApiVersion - description: "countDocuments appends declared API version" operations: diff --git a/x/mongo/driver/operation/command.go b/x/mongo/driver/operation/command.go index 443120d130..82845f1d0b 100644 --- a/x/mongo/driver/operation/command.go +++ b/x/mongo/driver/operation/command.go @@ -23,6 +23,7 @@ import ( // Command is used to run a generic operation. type Command struct { + name string authenticator driver.Authenticator commandFn func([]byte, description.SelectedServer) ([]byte, error) batches *driver.Batches @@ -123,6 +124,7 @@ func (c *Command) Execute(ctx context.Context) error { Timeout: c.timeout, Logger: c.logger, Authenticator: c.authenticator, + Name: c.name, }.Execute(ctx) } @@ -265,3 +267,13 @@ func (c *Command) Authenticator(authenticator driver.Authenticator) *Command { c.authenticator = authenticator return c } + +// Name sets the name for this operation. +func (c *Command) Name(name string) *Command { + if c == nil { + c = new(Command) + } + + c.name = name + return c +} From 982b622e08fea2b8068e36ef19bc1ce781cec5d2 Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Sun, 29 Sep 2024 17:09:29 -0400 Subject: [PATCH 06/14] WIP --- mongo/client_bulk_write.go | 35 ++++++++++++++++------------- x/mongo/driver/operation.go | 2 +- x/mongo/driver/operation/command.go | 28 +++++++++++++++++++++-- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/mongo/client_bulk_write.go b/mongo/client_bulk_write.go index 8ec9814d9f..6ef4c0430f 100644 --- a/mongo/client_bulk_write.go +++ b/mongo/client_bulk_write.go @@ -58,6 +58,7 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { } resMap := make([]interface{}, len(bw.models)) insIDMap := make(map[int]interface{}) + canRetry := true for i, v := range bw.models { var doc bsoncore.Document var err error @@ -94,6 +95,7 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { bw.client.bsonOpts, bw.client.registry) case *ClientUpdateManyModel: + canRetry = false nsIdx = getNsIndex(model.Namespace) if bw.result.UpdateResults == nil { bw.result.UpdateResults = make(map[int64]ClientUpdateResult) @@ -144,6 +146,7 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { bw.client.bsonOpts, bw.client.registry) case *ClientDeleteManyModel: + canRetry = false nsIdx = getNsIndex(model.Namespace) if bw.result.DeleteResults == nil { bw.result.DeleteResults = make(map[int64]ClientDeleteResult) @@ -168,24 +171,22 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { Documents: docs, Ordered: bw.ordered, } + retry := driver.RetryNone + if bw.client.retryWrites && canRetry { + retry = driver.RetryOncePerCommand + } op := operation.NewCommandFn(bw.newCommand(nsList)).Batches(batches). Session(bw.session).WriteConcern(bw.writeConcern).CommandMonitor(bw.client.monitor). ServerSelector(bw.selector).ClusterClock(bw.client.clock). - Database("admin"). + Database("admin").Type(driver.Write).RetryMode(retry). Deployment(bw.client.deployment).Crypt(bw.client.cryptFLE). ServerAPI(bw.client.serverAPI).Timeout(bw.client.timeout). Logger(bw.client.logger).Authenticator(bw.client.authenticator).Name("bulkWrite") opErr := op.Execute(ctx) - var wcErrs []*WriteConcernError if opErr != nil { if errors.Is(opErr, driver.ErrUnacknowledgedWrite) { return nil } - var writeErr driver.WriteCommandError - if errors.As(opErr, &writeErr) { - wcErr := convertDriverWriteConcernError(writeErr.WriteConcernError) - wcErrs = append(wcErrs, wcErr) - } } var res struct { Ok bool @@ -202,6 +203,9 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { Errmsg string } rawRes := op.Result() + if len(rawRes) == 0 { + return opErr + } if err := bson.Unmarshal(rawRes, &res); err != nil { return err } @@ -212,31 +216,32 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { bw.result.UpsertedCount = int64(res.NUpserted) errors := make(map[int64]WriteError) for i, cur := range res.Cursor.FirstBatch { - switch res := resMap[i].(type) { + switch r := resMap[i].(type) { case map[int64]ClientDeleteResult: - if err := appendDeleteResult(cur, res, errors); err != nil { + if err := appendDeleteResult(cur, r, errors); err != nil { return err } case map[int64]ClientInsertResult: - if err := appendInsertResult(cur, res, errors, insIDMap); err != nil { + if err := appendInsertResult(cur, r, errors, insIDMap); err != nil { return err } case map[int64]ClientUpdateResult: - if err := appendUpdateResult(cur, res, errors); err != nil { + if err := appendUpdateResult(cur, r, errors); err != nil { return err } } } - if !res.Ok || res.NErrors > 0 || opErr != nil { + if opErr != nil { + return opErr + } else if !res.Ok || res.NErrors > 0 { return ClientBulkWriteException{ TopLevelError: &WriteError{ Code: int(res.Code), Message: res.Errmsg, Raw: bson.Raw(rawRes), }, - WriteConcernErrors: wcErrs, - WriteErrors: errors, - PartialResult: &bw.result, + WriteErrors: errors, + PartialResult: &bw.result, } } return nil diff --git a/x/mongo/driver/operation.go b/x/mongo/driver/operation.go index ec6f69eca0..140a86c388 100644 --- a/x/mongo/driver/operation.go +++ b/x/mongo/driver/operation.go @@ -598,7 +598,7 @@ func (op Operation) Execute(ctx context.Context) error { if conn != nil { // If we are dealing with a sharded cluster, then mark the failed server // as "deprioritized". - if desc := conn.Description; desc != nil && op.Deployment.Kind() == description.Sharded { + if op.Deployment.Kind() == description.Sharded { deprioritizedServers = []description.Server{conn.Description()} } diff --git a/x/mongo/driver/operation/command.go b/x/mongo/driver/operation/command.go index 82845f1d0b..fbdb31f534 100644 --- a/x/mongo/driver/operation/command.go +++ b/x/mongo/driver/operation/command.go @@ -26,13 +26,15 @@ type Command struct { name string authenticator driver.Authenticator commandFn func([]byte, description.SelectedServer) ([]byte, error) - batches *driver.Batches database string deployment driver.Deployment selector description.ServerSelector writeConcern *writeconcern.WriteConcern readPreference *readpref.ReadPref clock *session.ClusterClock + retry *driver.RetryMode + opType driver.Type + batches *driver.Batches session *session.Client monitor *event.CommandMonitor resultResponse bsoncore.Document @@ -95,7 +97,6 @@ func (c *Command) Execute(ctx context.Context) error { return driver.Operation{ CommandFn: c.commandFn, - Batches: c.batches, ProcessResponseFn: func(info driver.ResponseInfo) error { c.resultResponse = info.ServerResponse @@ -113,6 +114,9 @@ func (c *Command) Execute(ctx context.Context) error { }, Client: c.session, Clock: c.clock, + RetryMode: c.retry, + Type: c.opType, + Batches: c.batches, CommandMonitor: c.monitor, Database: c.database, Deployment: c.deployment, @@ -168,6 +172,26 @@ func (c *Command) Batches(batches *driver.Batches) *Command { return c } +// RetryMode sets the RetryMode for this operation. +func (c *Command) RetryMode(retry driver.RetryMode) *Command { + if c == nil { + c = new(Command) + } + + c.retry = &retry + return c +} + +// Type sets the opType for this operation. +func (c *Command) Type(t driver.Type) *Command { + if c == nil { + c = new(Command) + } + + c.opType = t + return c +} + // Database sets the database to run this operation against. func (c *Command) Database(database string) *Command { if c == nil { From ceab570a5cdb950749a4ed0befbbb39a829d161a Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Thu, 3 Oct 2024 17:22:12 -0400 Subject: [PATCH 07/14] WIP --- mongo/client_bulk_write.go | 436 ++++++++++-------- mongo/client_bulk_write_models.go | 2 +- mongo/errors.go | 4 +- mongo/integration/crud_prose_test.go | 271 +++++++++++ mongo/options/clientbulkwriteoptions.go | 2 +- mongo/results.go | 6 +- x/mongo/driver/batch_cursor.go | 2 +- x/mongo/driver/batches.go | 4 - x/mongo/driver/batches_test.go | 22 - x/mongo/driver/operation.go | 127 +++-- x/mongo/driver/operation/abort_transaction.go | 2 +- x/mongo/driver/operation/aggregate.go | 2 +- x/mongo/driver/operation/command.go | 84 +--- .../driver/operation/commit_transaction.go | 2 +- x/mongo/driver/operation/count.go | 2 +- x/mongo/driver/operation/create.go | 2 +- x/mongo/driver/operation/create_indexes.go | 2 +- .../driver/operation/create_search_indexes.go | 2 +- x/mongo/driver/operation/delete.go | 45 +- x/mongo/driver/operation/distinct.go | 2 +- x/mongo/driver/operation/drop_collection.go | 2 +- x/mongo/driver/operation/drop_indexes.go | 2 +- x/mongo/driver/operation/drop_search_index.go | 2 +- x/mongo/driver/operation/end_sessions.go | 2 +- x/mongo/driver/operation/find.go | 2 +- x/mongo/driver/operation/find_and_modify.go | 2 +- x/mongo/driver/operation/hello.go | 4 +- x/mongo/driver/operation/insert.go | 45 +- x/mongo/driver/operation/listDatabases.go | 2 +- x/mongo/driver/operation/list_collections.go | 2 +- x/mongo/driver/operation/list_indexes.go | 2 +- x/mongo/driver/operation/update.go | 45 +- .../driver/operation/update_search_index.go | 2 +- x/mongo/driver/operation_exhaust.go | 2 +- 34 files changed, 704 insertions(+), 433 deletions(-) diff --git a/mongo/client_bulk_write.go b/mongo/client_bulk_write.go index 6ef4c0430f..e55a52df9d 100644 --- a/mongo/client_bulk_write.go +++ b/mongo/client_bulk_write.go @@ -1,4 +1,4 @@ -// Copyright (C) MongoDB, Inc. 2017-present. +// Copyright (C) MongoDB, Inc. 2024-present. // // 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 @@ -9,7 +9,7 @@ package mongo import ( "context" "errors" - "strconv" + "fmt" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/bsoncodec" @@ -19,7 +19,6 @@ import ( "go.mongodb.org/mongo-driver/mongo/writeconcern" "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" "go.mongodb.org/mongo-driver/x/mongo/driver" - "go.mongodb.org/mongo-driver/x/mongo/driver/operation" "go.mongodb.org/mongo-driver/x/mongo/driver/session" ) @@ -31,57 +30,207 @@ type clientBulkWrite struct { bypassDocumentValidation *bool comment interface{} let interface{} + session *session.Client + client *Client + selector description.ServerSelector + writeConcern *writeconcern.WriteConcern - session *session.Client - client *Client - selector description.ServerSelector - writeConcern *writeconcern.WriteConcern + cursorHandlers []func(*cursorInfo, bson.Raw) error + insIDMap map[int]interface{} - result ClientBulkWriteResult + result ClientBulkWriteResult + writeConcernErrors []WriteConcernError + writeErrors map[int]WriteError } func (bw *clientBulkWrite) execute(ctx context.Context) error { if len(bw.models) == 0 { return errors.New("empty write models") } - docs := make([]bsoncore.Document, len(bw.models)) + bw.writeErrors = make(map[int]WriteError) + batches, retry, err := bw.processModels() + if err != nil { + return err + } + err = driver.Operation{ + CommandFn: bw.newCommand(), + ProcessResponseFn: bw.ProcessResponse, + Client: bw.session, + Clock: bw.client.clock, + RetryMode: retry, + Type: driver.Write, + Batches: batches, + CommandMonitor: bw.client.monitor, + Database: "admin", + Deployment: bw.client.deployment, + Selector: bw.selector, + WriteConcern: bw.writeConcern, + Crypt: bw.client.cryptFLE, + ServerAPI: bw.client.serverAPI, + Timeout: bw.client.timeout, + Logger: bw.client.logger, + Authenticator: bw.client.authenticator, + Name: "bulkWrite", + }.Execute(ctx) + if err != nil && errors.Is(err, driver.ErrUnacknowledgedWrite) { + return nil + } + fmt.Println("exec", len(bw.writeErrors), err) + return err +} + +type cursorInfo struct { + Ok bool + Idx int32 + Code *int32 + Errmsg *string + ErrInfo bson.Raw + N int32 + NModified *int32 + Upserted *struct { + ID interface{} `bson:"_id"` + } +} + +func (cur *cursorInfo) extractError() *WriteError { + if cur.Ok { + return nil + } + err := &WriteError{ + Index: int(cur.Idx), + Details: cur.ErrInfo, + } + if cur.Code != nil { + err.Code = int(*cur.Code) + } + if cur.Errmsg != nil { + err.Message = *cur.Errmsg + } + return err +} + +func (bw *clientBulkWrite) ProcessResponse(ctx context.Context, info driver.ResponseInfo) error { + fmt.Println("ProcessResponse", info.Error) + var writeCmdErr driver.WriteCommandError + if errors.As(info.Error, &writeCmdErr) && writeCmdErr.WriteConcernError != nil { + wce := convertDriverWriteConcernError(writeCmdErr.WriteConcernError) + if wce != nil { + bw.writeConcernErrors = append(bw.writeConcernErrors, *wce) + } + } + // closeImplicitSession(sess) + if len(info.ServerResponse) == 0 { + return nil + } + var res struct { + Ok bool + NDeleted int32 + NInserted int32 + NMatched int32 + NModified int32 + NUpserted int32 + NErrors int32 + Code int32 + Errmsg string + } + err := bson.Unmarshal(info.ServerResponse, &res) + if err != nil { + return err + } + bw.result.DeletedCount += int64(res.NDeleted) + bw.result.InsertedCount += int64(res.NInserted) + bw.result.MatchedCount += int64(res.NMatched) + bw.result.ModifiedCount += int64(res.NModified) + bw.result.UpsertedCount += int64(res.NUpserted) + + var cursorRes driver.CursorResponse + cursorRes, err = driver.NewCursorResponse(info) + if err != nil { + return err + } + var bCursor *driver.BatchCursor + bCursor, err = driver.NewBatchCursor(cursorRes, bw.session, bw.client.clock, + driver.CursorOptions{ + CommandMonitor: bw.client.monitor, + Crypt: bw.client.cryptFLE, + ServerAPI: bw.client.serverAPI, + MarshalValueEncoderFn: newEncoderFn(bw.client.bsonOpts, bw.client.registry), + }, + ) + if err != nil { + return err + } + var cursor *Cursor + cursor, err = newCursor(bCursor, bw.client.bsonOpts, bw.client.registry) + if err != nil { + return err + } + defer cursor.Close(ctx) + + for cursor.Next(ctx) { + var cur cursorInfo + cursor.Decode(&cur) + if int(cur.Idx) >= len(bw.cursorHandlers) { + continue + } + if err := bw.cursorHandlers[int(cur.Idx)](&cur, cursor.Current); err != nil { + fmt.Println("ProcessResponse cursorHandlers", err) + return err + } + } + err = cursor.Err() + if err != nil { + return err + } + fmt.Println("ProcessResponse toplevelerror", res.Ok, res.NErrors, res.Code, res.Errmsg) + // if !res.Ok || res.NErrors > 0 { + // exception := bw.formException() + // exception.TopLevelError = &WriteError{ + // Code: int(res.Code), + // Message: res.Errmsg, + // Raw: bson.Raw(info.ServerResponse), + // } + // return exception + // } + return nil +} + +func (bw *clientBulkWrite) processModels() ([]driver.Batches, *driver.RetryMode, error) { nsMap := make(map[string]int) - var nsList []string + var nsList []bsoncore.Document getNsIndex := func(namespace string) int { if v, ok := nsMap[namespace]; ok { return v } nsIdx := len(nsList) nsMap[namespace] = nsIdx - nsList = append(nsList, namespace) + idx, doc := bsoncore.AppendDocumentStart(nil) + doc = bsoncore.AppendStringElement(doc, "ns", namespace) + doc, _ = bsoncore.AppendDocumentEnd(doc, idx) + nsList = append(nsList, doc) return nsIdx } - resMap := make([]interface{}, len(bw.models)) - insIDMap := make(map[int]interface{}) + + bw.cursorHandlers = make([]func(*cursorInfo, bson.Raw) error, len(bw.models)) + bw.insIDMap = make(map[int]interface{}) canRetry := true + docs := make([]bsoncore.Document, len(bw.models)) for i, v := range bw.models { var doc bsoncore.Document var err error - var nsIdx int switch model := v.(type) { case *ClientInsertOneModel: - nsIdx = getNsIndex(model.Namespace) - if bw.result.InsertResults == nil { - bw.result.InsertResults = make(map[int64]ClientInsertResult) - } - resMap[i] = bw.result.InsertResults + nsIdx := getNsIndex(model.Namespace) + bw.cursorHandlers[i] = bw.appendInsertResult var id interface{} id, doc, err = createClientInsertDoc(int32(nsIdx), model.Document, bw.client.bsonOpts, bw.client.registry) if err != nil { break } - insIDMap[i] = id + bw.insIDMap[i] = id case *ClientUpdateOneModel: - nsIdx = getNsIndex(model.Namespace) - if bw.result.UpdateResults == nil { - bw.result.UpdateResults = make(map[int64]ClientUpdateResult) - } - resMap[i] = bw.result.UpdateResults + nsIdx := getNsIndex(model.Namespace) + bw.cursorHandlers[i] = bw.appendUpdateResult doc, err = createClientUpdateDoc( int32(nsIdx), model.Filter, @@ -96,11 +245,8 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { bw.client.registry) case *ClientUpdateManyModel: canRetry = false - nsIdx = getNsIndex(model.Namespace) - if bw.result.UpdateResults == nil { - bw.result.UpdateResults = make(map[int64]ClientUpdateResult) - } - resMap[i] = bw.result.UpdateResults + nsIdx := getNsIndex(model.Namespace) + bw.cursorHandlers[i] = bw.appendUpdateResult doc, err = createClientUpdateDoc( int32(nsIdx), model.Filter, @@ -114,11 +260,8 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { bw.client.bsonOpts, bw.client.registry) case *ClientReplaceOneModel: - nsIdx = getNsIndex(model.Namespace) - if bw.result.UpdateResults == nil { - bw.result.UpdateResults = make(map[int64]ClientUpdateResult) - } - resMap[i] = bw.result.UpdateResults + nsIdx := getNsIndex(model.Namespace) + bw.cursorHandlers[i] = bw.appendUpdateResult doc, err = createClientUpdateDoc( int32(nsIdx), model.Filter, @@ -132,11 +275,8 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { bw.client.bsonOpts, bw.client.registry) case *ClientDeleteOneModel: - nsIdx = getNsIndex(model.Namespace) - if bw.result.DeleteResults == nil { - bw.result.DeleteResults = make(map[int64]ClientDeleteResult) - } - resMap[i] = bw.result.DeleteResults + nsIdx := getNsIndex(model.Namespace) + bw.cursorHandlers[i] = bw.appendDeleteResult doc, err = createClientDeleteDoc( int32(nsIdx), model.Filter, @@ -147,11 +287,8 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { bw.client.registry) case *ClientDeleteManyModel: canRetry = false - nsIdx = getNsIndex(model.Namespace) - if bw.result.DeleteResults == nil { - bw.result.DeleteResults = make(map[int64]ClientDeleteResult) - } - resMap[i] = bw.result.DeleteResults + nsIdx := getNsIndex(model.Namespace) + bw.cursorHandlers[i] = bw.appendDeleteResult doc, err = createClientDeleteDoc( int32(nsIdx), model.Filter, @@ -162,112 +299,34 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { bw.client.registry) } if err != nil { - return err + return nil, nil, err } docs[i] = doc } - batches := &driver.Batches{ - Identifier: "ops", - Documents: docs, - Ordered: bw.ordered, - } retry := driver.RetryNone if bw.client.retryWrites && canRetry { retry = driver.RetryOncePerCommand } - op := operation.NewCommandFn(bw.newCommand(nsList)).Batches(batches). - Session(bw.session).WriteConcern(bw.writeConcern).CommandMonitor(bw.client.monitor). - ServerSelector(bw.selector).ClusterClock(bw.client.clock). - Database("admin").Type(driver.Write).RetryMode(retry). - Deployment(bw.client.deployment).Crypt(bw.client.cryptFLE). - ServerAPI(bw.client.serverAPI).Timeout(bw.client.timeout). - Logger(bw.client.logger).Authenticator(bw.client.authenticator).Name("bulkWrite") - opErr := op.Execute(ctx) - if opErr != nil { - if errors.Is(opErr, driver.ErrUnacknowledgedWrite) { - return nil - } - } - var res struct { - Ok bool - Cursor struct { - FirstBatch []bson.Raw - } - NDeleted int32 - NInserted int32 - NMatched int32 - NModified int32 - NUpserted int32 - NErrors int32 - Code int32 - Errmsg string - } - rawRes := op.Result() - if len(rawRes) == 0 { - return opErr - } - if err := bson.Unmarshal(rawRes, &res); err != nil { - return err - } - bw.result.DeletedCount = int64(res.NDeleted) - bw.result.InsertedCount = int64(res.NInserted) - bw.result.MatchedCount = int64(res.NMatched) - bw.result.ModifiedCount = int64(res.NModified) - bw.result.UpsertedCount = int64(res.NUpserted) - errors := make(map[int64]WriteError) - for i, cur := range res.Cursor.FirstBatch { - switch r := resMap[i].(type) { - case map[int64]ClientDeleteResult: - if err := appendDeleteResult(cur, r, errors); err != nil { - return err - } - case map[int64]ClientInsertResult: - if err := appendInsertResult(cur, r, errors, insIDMap); err != nil { - return err - } - case map[int64]ClientUpdateResult: - if err := appendUpdateResult(cur, r, errors); err != nil { - return err - } - } - } - if opErr != nil { - return opErr - } else if !res.Ok || res.NErrors > 0 { - return ClientBulkWriteException{ - TopLevelError: &WriteError{ - Code: int(res.Code), - Message: res.Errmsg, - Raw: bson.Raw(rawRes), + ordered := false + return []driver.Batches{ + { + Identifier: "ops", + Documents: docs, + Ordered: bw.ordered, }, - WriteErrors: errors, - PartialResult: &bw.result, - } - } - return nil + { + Identifier: "nsInfo", + Documents: nsList, + Ordered: &ordered, + }, + }, + &retry, nil } -func (bw *clientBulkWrite) newCommand(nsList []string) func([]byte, description.SelectedServer) ([]byte, error) { +func (bw *clientBulkWrite) newCommand() func([]byte, description.SelectedServer) ([]byte, error) { return func(dst []byte, desc description.SelectedServer) ([]byte, error) { dst = bsoncore.AppendInt32Element(dst, "bulkWrite", 1) - var idx int32 - var err error - idx, dst = bsoncore.AppendArrayElementStart(dst, "nsInfo") - for i, v := range nsList { - doc, err := marshal(struct { - Namespace string `bson:"ns"` - }{v}, bw.client.bsonOpts, bw.client.registry) - if err != nil { - return nil, err - } - dst = bsoncore.AppendDocumentElement(dst, strconv.Itoa(i), doc) - } - dst, err = bsoncore.AppendArrayEnd(dst, idx) - if err != nil { - return nil, err - } - dst = bsoncore.AppendBooleanElement(dst, "errorsOnly", bw.errorsOnly) if bw.bypassDocumentValidation != nil && (desc.WireVersion != nil && desc.WireVersion.Includes(4)) { dst = bsoncore.AppendBooleanElement(dst, "bypassDocumentValidation", *bw.bypassDocumentValidation) @@ -416,75 +475,64 @@ func createClientDeleteDoc( return bsoncore.AppendDocumentEnd(doc, didx) } -func appendDeleteResult(cur bson.Raw, m map[int64]ClientDeleteResult, e map[int64]WriteError) error { - var res struct { - Ok bool - Idx int32 - N int32 - Code int32 - Errmsg string - } - if err := bson.Unmarshal(cur, &res); err != nil { - return err - } - if res.Ok { - m[int64(res.Idx)] = ClientDeleteResult{int64(res.N)} - } else { - e[int64(res.Idx)] = WriteError{ - Code: int(res.Code), - Message: res.Errmsg, +func (bw *clientBulkWrite) appendDeleteResult(cur *cursorInfo, raw bson.Raw) error { + if bw.result.DeleteResults == nil { + bw.result.DeleteResults = make(map[int]ClientDeleteResult) + } + bw.result.DeleteResults[int(cur.Idx)] = ClientDeleteResult{int64(cur.N)} + if err := cur.extractError(); err != nil { + err.Raw = raw + bw.writeErrors[int(cur.Idx)] = *err + if bw.ordered != nil && *bw.ordered { + return bw.formException() } } return nil } -func appendInsertResult(cur bson.Raw, m map[int64]ClientInsertResult, e map[int64]WriteError, insIDMap map[int]interface{}) error { - var res struct { - Ok bool - Idx int32 - Code int32 - Errmsg string - } - if err := bson.Unmarshal(cur, &res); err != nil { - return err - } - if res.Ok { - m[int64(res.Idx)] = ClientInsertResult{insIDMap[int(res.Idx)]} - } else { - e[int64(res.Idx)] = WriteError{ - Code: int(res.Code), - Message: res.Errmsg, +func (bw *clientBulkWrite) appendInsertResult(cur *cursorInfo, raw bson.Raw) error { + if bw.result.InsertResults == nil { + bw.result.InsertResults = make(map[int]ClientInsertResult) + } + bw.result.InsertResults[int(cur.Idx)] = ClientInsertResult{bw.insIDMap[int(cur.Idx)]} + if err := cur.extractError(); err != nil { + err.Raw = raw + bw.writeErrors[int(cur.Idx)] = *err + if bw.ordered != nil && *bw.ordered { + return bw.formException() } } return nil } -func appendUpdateResult(cur bson.Raw, m map[int64]ClientUpdateResult, e map[int64]WriteError) error { - var res struct { - Ok bool - Idx int32 - N int32 - NModified int32 - Upserted struct { - ID interface{} `bson:"_id"` - } - Code int32 - Errmsg string +func (bw *clientBulkWrite) appendUpdateResult(cur *cursorInfo, raw bson.Raw) error { + if bw.result.UpdateResults == nil { + bw.result.UpdateResults = make(map[int]ClientUpdateResult) } - if err := bson.Unmarshal(cur, &res); err != nil { - return err + result := ClientUpdateResult{ + MatchedCount: int64(cur.N), } - if res.Ok { - m[int64(res.Idx)] = ClientUpdateResult{ - MatchedCount: int64(res.N), - ModifiedCount: int64(res.NModified), - UpsertedID: res.Upserted.ID, - } - } else { - e[int64(res.Idx)] = WriteError{ - Code: int(res.Code), - Message: res.Errmsg, + if cur.NModified != nil { + result.ModifiedCount = int64(*cur.NModified) + } + if cur.Upserted != nil { + result.UpsertedID = (*cur.Upserted).ID + } + bw.result.UpdateResults[int(cur.Idx)] = result + if err := cur.extractError(); err != nil { + err.Raw = raw + bw.writeErrors[int(cur.Idx)] = *err + if bw.ordered != nil && *bw.ordered { + return bw.formException() } } return nil } + +func (bw *clientBulkWrite) formException() ClientBulkWriteException { + return ClientBulkWriteException{ + WriteConcernErrors: bw.writeConcernErrors, + WriteErrors: bw.writeErrors, + PartialResult: &bw.result, + } +} diff --git a/mongo/client_bulk_write_models.go b/mongo/client_bulk_write_models.go index e942a7af4c..dfd090715e 100644 --- a/mongo/client_bulk_write_models.go +++ b/mongo/client_bulk_write_models.go @@ -1,4 +1,4 @@ -// Copyright (C) MongoDB, Inc. 2017-present. +// Copyright (C) MongoDB, Inc. 2024-present. // // 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 diff --git a/mongo/errors.go b/mongo/errors.go index 180cc8ce7f..547d27827a 100644 --- a/mongo/errors.go +++ b/mongo/errors.go @@ -614,10 +614,10 @@ type ClientBulkWriteException struct { TopLevelError *WriteError // The write concern errors that occurred. - WriteConcernErrors []*WriteConcernError + WriteConcernErrors []WriteConcernError // The write errors that occurred during individual operation execution. - WriteErrors map[int64]WriteError + WriteErrors map[int]WriteError PartialResult *ClientBulkWriteResult } diff --git a/mongo/integration/crud_prose_test.go b/mongo/integration/crud_prose_test.go index 00a1f0ff36..c3b3120d71 100644 --- a/mongo/integration/crud_prose_test.go +++ b/mongo/integration/crud_prose_test.go @@ -10,13 +10,17 @@ import ( "bytes" "context" "errors" + "strings" "testing" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/event" "go.mongodb.org/mongo-driver/internal/assert" + "go.mongodb.org/mongo-driver/internal/require" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/integration/mtest" "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/mongo/writeconcern" "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" ) @@ -408,3 +412,270 @@ func TestErrorsCodeNamePropagated(t *testing.T) { assert.Equal(mt, expectedCodeName, wce.Name, "expected code name %q, got %q", expectedCodeName, wce.Name) }) } + +func TestClientBulkWrite(t *testing.T) { + mtOpts := mtest.NewOptions().MinServerVersion("8.0").AtlasDataLake(false).CreateClient(false) + mt := mtest.New(t, mtOpts) + + mt.Run("input with greater than maxWriteBatchSize", func(mt *mtest.T) { + var opsCnt []int + monitor := &event.CommandMonitor{ + Started: func(ctx context.Context, e *event.CommandStartedEvent) { + if e.CommandName == "bulkWrite" { + v := e.Command.Lookup("ops") + elems, err := v.Array().Elements() + require.NoError(mt, err, "monitor error") + opsCnt = append(opsCnt, len(elems)) + } + }, + } + mt.ResetClient(options.Client().SetMonitor(monitor)) + var hello struct { + MaxWriteBatchSize int + } + require.NoError(mt, mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello), "Hello error") + models := &mongo.ClientWriteModels{} + for i := 0; i < hello.MaxWriteBatchSize+1; i++ { + models. + AppendInsertOne(&mongo.ClientInsertOneModel{ + Namespace: "db.coll", + Document: bson.D{{"a", "b"}}, + }) + } + result, err := mt.Client.BulkWrite(context.Background(), models) + require.NoError(mt, err, "BulkWrite error") + assert.Equal(mt, hello.MaxWriteBatchSize+1, int(result.InsertedCount), "InsertedCount expected to be %d", hello.MaxWriteBatchSize+1) + require.Len(mt, opsCnt, 2, "expected 2 bulkWrite commands") + assert.Equal(mt, hello.MaxWriteBatchSize, opsCnt[0], "the length of firstEvent.command.ops is %d", hello.MaxWriteBatchSize) + assert.Equal(mt, 1, opsCnt[1], "the length of secondEvent.command.ops is 1") + }) + + mt.Run("input with greater than maxMessageSizeBytes", func(mt *mtest.T) { + var opsCnt []int + monitor := &event.CommandMonitor{ + Started: func(ctx context.Context, e *event.CommandStartedEvent) { + if e.CommandName == "bulkWrite" { + v := e.Command.Lookup("ops") + elems, err := v.Array().Elements() + require.NoError(mt, err, "monitor error") + opsCnt = append(opsCnt, len(elems)) + } + }, + } + mt.ResetClient(options.Client().SetMonitor(monitor)) + var hello struct { + MaxBsonObjectSize int + MaxMessageSizeBytes int + } + require.NoError(mt, mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello), "Hello error") + models := &mongo.ClientWriteModels{} + numModels := hello.MaxMessageSizeBytes/hello.MaxBsonObjectSize + 1 + for i := 0; i < numModels; i++ { + models. + AppendInsertOne(&mongo.ClientInsertOneModel{ + Namespace: "db.coll", + Document: bson.D{{"a", strings.Repeat("b", hello.MaxBsonObjectSize-500)}}, + }) + } + result, err := mt.Client.BulkWrite(context.Background(), models) + require.NoError(mt, err, "BulkWrite error") + assert.Equal(mt, numModels, int(result.InsertedCount), "InsertedCount expected to be %d", numModels) + require.Len(mt, opsCnt, 2, "expected 2 bulkWrite commands") + assert.Equal(mt, numModels-1, opsCnt[0], "the length of firstEvent.command.ops is %d", numModels-1) + assert.Equal(mt, 1, opsCnt[1], "the length of secondEvent.command.ops is 1") + }) + + mt.Run("bulkWrite collects WriteConcernErrors across batches", func(mt *mtest.T) { + mt.ResetClient(options.Client().SetRetryWrites(false)) + var hello struct { + MaxWriteBatchSize int + } + require.NoError(mt, mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello), "Hello error") + + mt.SetFailPoint(mtest.FailPoint{ + ConfigureFailPoint: "failCommand", + Mode: mtest.FailPointMode{ + Times: 2, + }, + Data: mtest.FailPointData{ + FailCommands: []string{"bulkWrite"}, + WriteConcernError: &mtest.WriteConcernErrorData{ + Code: 91, + Errmsg: "Replication is being shut down", + }, + }, + }) + + models := &mongo.ClientWriteModels{} + for i := 0; i < hello.MaxWriteBatchSize+1; i++ { + models. + AppendInsertOne(&mongo.ClientInsertOneModel{ + Namespace: "db.coll", + Document: bson.D{{"a", "b"}}, + }) + } + _, err := mt.Client.BulkWrite(context.Background(), models) + require.Error(mt, err) + bwe, ok := err.(mongo.ClientBulkWriteException) + require.True(mt, ok, "expected a BulkWriteException, got %T", err) + assert.Len(mt, bwe.WriteConcernErrors, 2, "expected 2 writeConcernErrors") + require.NotNil(mt, bwe.PartialResult) + assert.Equal(mt, hello.MaxWriteBatchSize+1, int(bwe.PartialResult.InsertedCount), + "InsertedCount expected to be %d", hello.MaxWriteBatchSize+1) + }) + + mt.Run("bulkWrite handles individual WriteErrors across batches", func(mt *mtest.T) { + coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, true) + err := coll.Drop(context.Background()) + require.NoError(mt, err, "Drop error") + _, err = coll.InsertOne(context.Background(), bson.D{{"_id", 1}}) + require.NoError(mt, err, "InsertOne error") + + var eventCnt int + monitor := &event.CommandMonitor{ + Started: func(ctx context.Context, e *event.CommandStartedEvent) { + if e.CommandName == "bulkWrite" { + eventCnt++ + } + }, + } + + var hello struct { + MaxWriteBatchSize int + } + err = mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello) + require.NoError(mt, err, "Hello error") + models := &mongo.ClientWriteModels{} + for i := 0; i < hello.MaxWriteBatchSize+1; i++ { + models. + AppendInsertOne(&mongo.ClientInsertOneModel{ + Namespace: "db.coll", + Document: bson.D{{"_id", 1}}, + }) + } + + mt.Run("Unordered", func(mt *mtest.T) { + eventCnt = 0 + mt.ResetClient(options.Client().SetMonitor(monitor)) + _, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetOrdered(false)) + require.Error(mt, err) + bwe, ok := err.(mongo.ClientBulkWriteException) + require.True(mt, ok, "expected a BulkWriteException, got %T", err) + assert.Len(mt, bwe.WriteErrors, hello.MaxWriteBatchSize+1, "expected %d writeErrors, got %d", hello.MaxWriteBatchSize+1, len(bwe.WriteErrors)) + require.Equal(mt, 2, eventCnt, "expected 2 bulkWrite commands, got %d", eventCnt) + }) + mt.Run("Ordered", func(mt *mtest.T) { + eventCnt = 0 + mt.ResetClient(options.Client().SetMonitor(monitor)) + _, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetOrdered(true)) + require.Error(mt, err) + bwe, ok := err.(mongo.ClientBulkWriteException) + require.True(mt, ok, "expected a BulkWriteException, got %T", err) + assert.Len(mt, bwe.WriteErrors, 1, "expected %d writeErrors, got %d", 1, len(bwe.WriteErrors)) + require.Equal(mt, 1, eventCnt, "expected 1 bulkWrite commands, got %d", eventCnt) + }) + }) + + mt.Run("bulkWrite handles a cursor requiring a getMore", func(mt *mtest.T) { + coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, true) + coll.Drop(context.Background()) + + var getMoreCalled bool + monitor := &event.CommandMonitor{ + Started: func(ctx context.Context, e *event.CommandStartedEvent) { + if e.CommandName == "getMore" { + getMoreCalled = true + } + }, + } + mt.ResetClient(options.Client().SetMonitor(monitor)) + var hello struct { + MaxBsonObjectSize int + } + require.NoError(mt, mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello), "Hello error") + upsert := true + models := (&mongo.ClientWriteModels{}). + AppendUpdateOne(&mongo.ClientUpdateOneModel{ + Namespace: "db.coll", + Filter: bson.D{{"_id", strings.Repeat("a", hello.MaxBsonObjectSize/2)}}, + Update: bson.D{{"$set", bson.D{{"x", 1}}}}, + Upsert: &upsert, + }). + AppendUpdateOne(&mongo.ClientUpdateOneModel{ + Namespace: "db.coll", + Filter: bson.D{{"_id", strings.Repeat("b", hello.MaxBsonObjectSize/2)}}, + Update: bson.D{{"$set", bson.D{{"x", 1}}}}, + Upsert: &upsert, + }) + result, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetVerboseResults(true)) + require.NoError(mt, err, "BulkWrite error") + assert.Equal(mt, 2, int(result.UpsertedCount), "InsertedCount expected to be %d, got %d", 2, result.UpsertedCount) + assert.Len(mt, result.UpdateResults, 2, "expected %d UpdateResults, got %d", 2, len(result.UpdateResults)) + assert.True(mt, getMoreCalled, "the getMore was not called") + }) + + mt.Run("bulkWrite handles a cursor requiring a getMore within a transaction", func(mt *mtest.T) { + coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, true) + coll.Drop(context.Background()) + + var getMoreCalled bool + monitor := &event.CommandMonitor{ + Started: func(ctx context.Context, e *event.CommandStartedEvent) { + if e.CommandName == "getMore" { + getMoreCalled = true + } + }, + } + mt.ResetClient(options.Client().SetMonitor(monitor)) + var hello struct { + MaxBsonObjectSize int + } + require.NoError(mt, mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello), "Hello error") + session, err := mt.Client.StartSession() + require.NoError(mt, err, "StartSession error") + defer session.EndSession(context.Background()) + upsert := true + models := (&mongo.ClientWriteModels{}). + AppendUpdateOne(&mongo.ClientUpdateOneModel{ + Namespace: "db.coll", + Filter: bson.D{{"_id", strings.Repeat("a", hello.MaxBsonObjectSize/2)}}, + Update: bson.D{{"$set", bson.D{{"x", 1}}}}, + Upsert: &upsert, + }). + AppendUpdateOne(&mongo.ClientUpdateOneModel{ + Namespace: "db.coll", + Filter: bson.D{{"_id", strings.Repeat("b", hello.MaxBsonObjectSize/2)}}, + Update: bson.D{{"$set", bson.D{{"x", 1}}}}, + Upsert: &upsert, + }) + result, err := session.WithTransaction(context.TODO(), func(ctx mongo.SessionContext) (interface{}, error) { + return mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetVerboseResults(true)) + }) + require.NoError(mt, err, "BulkWrite error") + cbwResult, ok := result.(*mongo.ClientBulkWriteResult) + require.True(mt, ok, "expected a ClientBulkWriteResult") + assert.Equal(mt, 2, int(cbwResult.UpsertedCount), "InsertedCount expected to be %d, got %d", 2, cbwResult.UpsertedCount) + assert.Len(mt, cbwResult.UpdateResults, 2, "expected %d UpdateResults, got %d", 2, len(cbwResult.UpdateResults)) + assert.True(mt, getMoreCalled, "the getMore was not called") + }) + + mt.Run("bulkWrite handles a getMore error", func(mt *mtest.T) { + }) + + mt.Run("bulkWrite returns error for unacknowledged too-large insert", func(mt *mtest.T) { + mt.ResetClient(options.Client()) + var hello struct { + MaxBsonObjectSize int + } + require.NoError(mt, mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello), "Hello error") + models := (&mongo.ClientWriteModels{}). + AppendInsertOne(&mongo.ClientInsertOneModel{ + Namespace: "db.coll", + Document: bson.D{{"a", strings.Repeat("b", hello.MaxBsonObjectSize)}}, + }) + result, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetWriteConcern(writeconcern.Unacknowledged())) + require.NoError(mt, err, "BulkWrite error") + assert.Equal(mt, 2, int(result.UpsertedCount), "InsertedCount expected to be %d, got %d", 2, result.UpsertedCount) + assert.Len(mt, result.UpdateResults, 2, "expected %d UpdateResults, got %d", 2, len(result.UpdateResults)) + }) +} diff --git a/mongo/options/clientbulkwriteoptions.go b/mongo/options/clientbulkwriteoptions.go index 050438dc2f..ad91f37488 100644 --- a/mongo/options/clientbulkwriteoptions.go +++ b/mongo/options/clientbulkwriteoptions.go @@ -1,4 +1,4 @@ -// Copyright (C) MongoDB, Inc. 2017-present. +// Copyright (C) MongoDB, Inc. 2024-present. // // 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 diff --git a/mongo/results.go b/mongo/results.go index 58da18e7fb..f7658306f5 100644 --- a/mongo/results.go +++ b/mongo/results.go @@ -32,13 +32,13 @@ type ClientBulkWriteResult struct { UpsertedCount int64 // A map of operation index to the _id of each inserted document. - InsertResults map[int64]ClientInsertResult + InsertResults map[int]ClientInsertResult // A map of operation index to the _id of each updated document. - UpdateResults map[int64]ClientUpdateResult + UpdateResults map[int]ClientUpdateResult // A map of operation index to the _id of each deleted document. - DeleteResults map[int64]ClientDeleteResult + DeleteResults map[int]ClientDeleteResult } // ClientInsertResult is the result type returned by a client-level bulk write of InsertOne operation. diff --git a/x/mongo/driver/batch_cursor.go b/x/mongo/driver/batch_cursor.go index 2aa0aca694..db9e24fe48 100644 --- a/x/mongo/driver/batch_cursor.go +++ b/x/mongo/driver/batch_cursor.go @@ -393,7 +393,7 @@ func (bc *BatchCursor) getMore(ctx context.Context) { }, Database: bc.database, Deployment: bc.getOperationDeployment(), - ProcessResponseFn: func(info ResponseInfo) error { + ProcessResponseFn: func(_ context.Context, info ResponseInfo) error { response := info.ServerResponse id, ok := response.Lookup("cursor", "id").Int64OK() if !ok { diff --git a/x/mongo/driver/batches.go b/x/mongo/driver/batches.go index be430afa15..c6b17ceb08 100644 --- a/x/mongo/driver/batches.go +++ b/x/mongo/driver/batches.go @@ -25,10 +25,6 @@ type Batches struct { Ordered *bool } -// Valid returns true if Batches contains both an identifier and the length of Documents is greater -// than zero. -func (b *Batches) Valid() bool { return b != nil && b.Identifier != "" && len(b.Documents) > 0 } - // ClearBatch clears the Current batch. This must be called before AdvanceBatch will advance to the // next batch. func (b *Batches) ClearBatch() { b.Current = b.Current[:0] } diff --git a/x/mongo/driver/batches_test.go b/x/mongo/driver/batches_test.go index 353368a33c..a72813833a 100644 --- a/x/mongo/driver/batches_test.go +++ b/x/mongo/driver/batches_test.go @@ -15,28 +15,6 @@ import ( ) func TestBatches(t *testing.T) { - t.Run("Valid", func(t *testing.T) { - testCases := []struct { - name string - batches *Batches - want bool - }{ - {"nil", nil, false}, - {"missing identifier", &Batches{}, false}, - {"no documents", &Batches{Identifier: "documents"}, false}, - {"valid", &Batches{Identifier: "documents", Documents: make([]bsoncore.Document, 5)}, true}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - want := tc.want - got := tc.batches.Valid() - if got != want { - t.Errorf("Did not get expected result from Valid. got %t; want %t", got, want) - } - }) - } - }) t.Run("ClearBatch", func(t *testing.T) { batches := &Batches{Identifier: "documents", Current: make([]bsoncore.Document, 2, 10)} if len(batches.Current) != 2 { diff --git a/x/mongo/driver/operation.go b/x/mongo/driver/operation.go index 140a86c388..4f45a78ce4 100644 --- a/x/mongo/driver/operation.go +++ b/x/mongo/driver/operation.go @@ -156,9 +156,10 @@ type ResponseInfo struct { Connection Connection ConnectionDescription description.Server CurrentIndex int + Error error } -func redactStartedInformationCmd(op Operation, info startedInformation) bson.Raw { +func redactStartedInformationCmd(info startedInformation, batches []Batches) bson.Raw { var cmdCopy bson.Raw // Make a copy of the command. Redact if the command is security @@ -171,7 +172,9 @@ func redactStartedInformationCmd(op Operation, info startedInformation) bson.Raw if info.documentSequenceIncluded { // remove 0 byte at end cmdCopy = cmdCopy[:len(info.cmd)-1] - cmdCopy = op.addBatchArray(cmdCopy) + for i := 0; i < len(batches); i++ { + cmdCopy = addBatchArray(cmdCopy, batches[i].Identifier, batches[i].Current) + } // add back 0 byte and update length cmdCopy, _ = bsoncore.AppendDocumentEnd(cmdCopy, 0) @@ -218,7 +221,7 @@ type Operation struct { // ProcessResponseFn is called after a response to the command is returned. The server is // provided for types like Cursor that are required to run subsequent commands using the same // server. - ProcessResponseFn func(ResponseInfo) error + ProcessResponseFn func(context.Context, ResponseInfo) error // Selector is the server selector that's used during both initial server selection and // subsequent selection for retries. Depending on the Deployment implementation, the @@ -276,7 +279,7 @@ type Operation struct { // has more documents than can fit in a single command. This should only be specified for // commands that are batch compatible. For more information, please refer to the definition of // Batches. - Batches *Batches + Batches []Batches // Legacy sets the legacy type for this operation. There are only 3 types that require legacy // support: find, getMore, and killCursors. For more information about LegacyOperationKind, @@ -559,7 +562,7 @@ func (op Operation) Execute(ctx context.Context) error { var operationErr WriteCommandError var prevErr error var prevIndefiniteErr error - batching := op.Batches.Valid() + batching := len(op.Batches) > 0 retrySupported := false first := true currIndex := 0 @@ -722,10 +725,12 @@ func (op Operation) Execute(ctx context.Context) error { targetBatchSize = cryptMaxBsonObjectSize } - err = op.Batches.AdvanceBatch(int(desc.MaxBatchCount), int(targetBatchSize), int(maxDocSize)) - if err != nil { - // TODO(GODRIVER-982): Should we also be returning operationErr? - return err + for i := 0; i < len(op.Batches); i++ { + err = op.Batches[i].AdvanceBatch(int(desc.MaxBatchCount), int(targetBatchSize), int(maxDocSize)) + if err != nil { + // TODO(GODRIVER-982): Should we also be returning operationErr? + return err + } } } @@ -827,7 +832,6 @@ func (op Operation) Execute(ctx context.Context) error { // TODO(GODRIVER-2579): When refactoring the "Execute" method, consider creating a separate method for the // error handling logic below. This will remove the necessity of the "checkError" goto label. checkError: - var perr error switch tt := err.(type) { case WriteCommandError: if e := err.(WriteCommandError); retrySupported && op.Type == Write && e.UnsupportedStorageEngine() { @@ -876,8 +880,12 @@ func (op Operation) Execute(ctx context.Context) error { Connection: conn, ConnectionDescription: desc.Server, CurrentIndex: currIndex, + Error: tt, } - _ = op.ProcessResponseFn(info) + _ = op.ProcessResponseFn(ctx, info) + // if perr != nil { + // return perr + // } } if batching && len(tt.WriteErrors) > 0 && currIndex > 0 { @@ -888,7 +896,14 @@ func (op Operation) Execute(ctx context.Context) error { // If batching is enabled and either ordered is the default (which is true) or // explicitly set to true and we have write errors, return the errors. - if batching && (op.Batches.Ordered == nil || *op.Batches.Ordered) && len(tt.WriteErrors) > 0 { + var ordered bool + for i := 0; i < len(op.Batches); i++ { + if op.Batches[i].Ordered == nil || *op.Batches[i].Ordered { + ordered = true + break + } + } + if batching && ordered && len(tt.WriteErrors) > 0 { return tt } if op.Client != nil && op.Client.Committing && tt.WriteConcernError != nil { @@ -995,8 +1010,12 @@ func (op Operation) Execute(ctx context.Context) error { Connection: conn, ConnectionDescription: desc.Server, CurrentIndex: currIndex, + Error: tt, } - _ = op.ProcessResponseFn(info) + _ = op.ProcessResponseFn(ctx, info) + // if perr != nil { + // return perr + // } } if op.Client != nil && op.Client.Committing && (retryableErr || tt.Code == 50) { @@ -1015,11 +1034,13 @@ func (op Operation) Execute(ctx context.Context) error { Connection: conn, ConnectionDescription: desc.Server, CurrentIndex: currIndex, + Error: tt, + } + perr := op.ProcessResponseFn(ctx, info) + if perr != nil { + fmt.Println("op", perr) + return perr } - perr = op.ProcessResponseFn(info) - } - if perr != nil { - return perr } default: if op.ProcessResponseFn != nil { @@ -1029,8 +1050,9 @@ func (op Operation) Execute(ctx context.Context) error { Connection: conn, ConnectionDescription: desc.Server, CurrentIndex: currIndex, + Error: tt, } - _ = op.ProcessResponseFn(info) + _ = op.ProcessResponseFn(ctx, info) } return err } @@ -1038,7 +1060,13 @@ func (op Operation) Execute(ctx context.Context) error { // If we're batching and there are batches remaining, advance to the next batch. This isn't // a retry, so increment the transaction number, reset the retries number, and don't set // server or connection to nil to continue using the same connection. - if batching && len(op.Batches.Documents) > 0 { + var advancing []int + for i := 0; i < len(op.Batches); i++ { + if len(op.Batches[i].Documents) > 0 { + advancing = append(advancing, i) + } + } + if batching && len(advancing) > 0 { // If retries are supported for the current operation on the current server description, // the session isn't nil, and client retries are enabled, increment the txn number. // Calling IncrementTxnNumber() for server descriptions or topologies that do not @@ -1053,8 +1081,10 @@ func (op Operation) Execute(ctx context.Context) error { retries = 1 } } - currIndex += len(op.Batches.Current) - op.Batches.ClearBatch() + for _, i := range advancing { + currIndex += len(op.Batches[i].Current) + op.Batches[i].ClearBatch() + } continue } break @@ -1211,9 +1241,12 @@ func (Operation) decompressWireMessage(wm []byte) (wiremessage.OpCode, []byte, e return opcode, uncompressed, nil } -func (op Operation) addBatchArray(dst []byte) []byte { - aidx, dst := bsoncore.AppendArrayElementStart(dst, op.Batches.Identifier) - for i, doc := range op.Batches.Current { +func addBatchArray(dst []byte, identifier string, docs []bsoncore.Document) []byte { + if len(docs) == 0 { + return dst + } + aidx, dst := bsoncore.AppendArrayElementStart(dst, identifier) + for i, doc := range docs { dst = bsoncore.AppendDocumentElement(dst, strconv.Itoa(i), doc) } dst, _ = bsoncore.AppendArrayEnd(dst, aidx) @@ -1256,8 +1289,8 @@ func (op Operation) createLegacyHandshakeWireMessage( return dst, info, err } - if op.Batches != nil && len(op.Batches.Current) > 0 { - dst = op.addBatchArray(dst) + for i := 0; i < len(op.Batches); i++ { + dst = addBatchArray(dst, op.Batches[i].Identifier, op.Batches[i].Current) } dst, err = op.addReadConcern(dst, desc) @@ -1312,7 +1345,14 @@ func (op Operation) createMsgWireMessage( var wmindex int32 // We set the MoreToCome bit if we have a write concern, it's unacknowledged, and we either // aren't batching or we are encoding the last batch. - if op.WriteConcern != nil && !writeconcern.AckWrite(op.WriteConcern) && (op.Batches == nil || len(op.Batches.Documents) == 0) { + var batching bool + for i := 0; i < len(op.Batches); i++ { + if len(op.Batches[i].Documents) > 0 { + batching = true + break + } + } + if op.WriteConcern != nil && !writeconcern.AckWrite(op.WriteConcern) && !batching { flags = wiremessage.MoreToCome } // Set the ExhaustAllowed flag if the connection supports streaming. This will tell the server that it can @@ -1369,19 +1409,24 @@ func (op Operation) createMsgWireMessage( // add batch as a document sequence if auto encryption is not enabled // if auto encryption is enabled, the batch will already be an array in the command document - if !op.shouldEncrypt() && op.Batches != nil && len(op.Batches.Current) > 0 { - info.documentSequenceIncluded = true - dst = wiremessage.AppendMsgSectionType(dst, wiremessage.DocumentSequence) - idx, dst = bsoncore.ReserveLength(dst) + if !op.shouldEncrypt() { + for i := 0; i < len(op.Batches); i++ { + if len(op.Batches[i].Current) == 0 { + continue + } + info.documentSequenceIncluded = true + dst = wiremessage.AppendMsgSectionType(dst, wiremessage.DocumentSequence) + idx, dst = bsoncore.ReserveLength(dst) - dst = append(dst, op.Batches.Identifier...) - dst = append(dst, 0x00) + dst = append(dst, op.Batches[i].Identifier...) + dst = append(dst, 0x00) - for _, doc := range op.Batches.Current { - dst = append(dst, doc...) - } + for _, doc := range op.Batches[i].Current { + dst = append(dst, doc...) + } - dst = bsoncore.UpdateLength(dst, idx, int32(len(dst[idx:]))) + dst = bsoncore.UpdateLength(dst, idx, int32(len(dst[idx:]))) + } } return bsoncore.UpdateLength(dst, wmindex, int32(len(dst[wmindex:]))), info, nil @@ -1429,8 +1474,8 @@ func (op Operation) addCommandFields(ctx context.Context, dst []byte, desc descr return dst, err } // use a BSON array instead of a type 1 payload because mongocryptd will convert to arrays regardless - if op.Batches != nil && len(op.Batches.Current) > 0 { - cmdDst = op.addBatchArray(cmdDst) + for i := 0; i < len(op.Batches); i++ { + cmdDst = addBatchArray(cmdDst, op.Batches[i].Identifier, op.Batches[i].Current) } cmdDst, _ = bsoncore.AppendDocumentEnd(cmdDst, cidx) @@ -1977,7 +2022,7 @@ func (op Operation) publishStartedEvent(ctx context.Context, info startedInforma if op.canLogCommandMessage() { host, port, _ := net.SplitHostPort(info.serverAddress.String()) - redactedCmd := redactStartedInformationCmd(op, info).String() + redactedCmd := redactStartedInformationCmd(info, op.Batches).String() formattedCmd := logger.FormatMessage(redactedCmd, op.Logger.MaxDocumentLength) op.Logger.Print(logger.LevelDebug, @@ -2000,7 +2045,7 @@ func (op Operation) publishStartedEvent(ctx context.Context, info startedInforma if op.canPublishStartedEvent() { started := &event.CommandStartedEvent{ - Command: redactStartedInformationCmd(op, info), + Command: redactStartedInformationCmd(info, op.Batches), DatabaseName: op.Database, CommandName: info.cmdName, RequestID: int64(info.requestID), diff --git a/x/mongo/driver/operation/abort_transaction.go b/x/mongo/driver/operation/abort_transaction.go index aeee533533..3155233e42 100644 --- a/x/mongo/driver/operation/abort_transaction.go +++ b/x/mongo/driver/operation/abort_transaction.go @@ -41,7 +41,7 @@ func NewAbortTransaction() *AbortTransaction { return &AbortTransaction{} } -func (at *AbortTransaction) processResponse(driver.ResponseInfo) error { +func (at *AbortTransaction) processResponse(context.Context, driver.ResponseInfo) error { var err error return err } diff --git a/x/mongo/driver/operation/aggregate.go b/x/mongo/driver/operation/aggregate.go index df6b8fa9dd..17b0c6a70e 100644 --- a/x/mongo/driver/operation/aggregate.go +++ b/x/mongo/driver/operation/aggregate.go @@ -79,7 +79,7 @@ func (a *Aggregate) ResultCursorResponse() driver.CursorResponse { return a.result } -func (a *Aggregate) processResponse(info driver.ResponseInfo) error { +func (a *Aggregate) processResponse(_ context.Context, info driver.ResponseInfo) error { var err error a.result, err = driver.NewCursorResponse(info) diff --git a/x/mongo/driver/operation/command.go b/x/mongo/driver/operation/command.go index fbdb31f534..d099067392 100644 --- a/x/mongo/driver/operation/command.go +++ b/x/mongo/driver/operation/command.go @@ -15,7 +15,6 @@ import ( "go.mongodb.org/mongo-driver/internal/logger" "go.mongodb.org/mongo-driver/mongo/description" "go.mongodb.org/mongo-driver/mongo/readpref" - "go.mongodb.org/mongo-driver/mongo/writeconcern" "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" "go.mongodb.org/mongo-driver/x/mongo/driver" "go.mongodb.org/mongo-driver/x/mongo/driver/session" @@ -23,18 +22,13 @@ import ( // Command is used to run a generic operation. type Command struct { - name string authenticator driver.Authenticator - commandFn func([]byte, description.SelectedServer) ([]byte, error) + command bsoncore.Document database string deployment driver.Deployment selector description.ServerSelector - writeConcern *writeconcern.WriteConcern readPreference *readpref.ReadPref clock *session.ClusterClock - retry *driver.RetryMode - opType driver.Type - batches *driver.Batches session *session.Client monitor *event.CommandMonitor resultResponse bsoncore.Document @@ -51,16 +45,7 @@ type Command struct { // the Result() function. func NewCommand(command bsoncore.Document) *Command { return &Command{ - commandFn: func(dst []byte, _ description.SelectedServer) ([]byte, error) { - return append(dst, command[4:len(command)-1]...), nil - }, - } -} - -// NewCommandFn constructs and returns a new Command. -func NewCommandFn(commandFn func([]byte, description.SelectedServer) ([]byte, error)) *Command { - return &Command{ - commandFn: commandFn, + command: command, } } @@ -68,9 +53,7 @@ func NewCommandFn(commandFn func([]byte, description.SelectedServer) ([]byte, er // construct a cursor, which can be accessed via the ResultCursor() function. func NewCursorCommand(command bsoncore.Document, cursorOpts driver.CursorOptions) *Command { return &Command{ - commandFn: func(dst []byte, _ description.SelectedServer) ([]byte, error) { - return append(dst, command[4:len(command)-1]...), nil - }, + command: command, cursorOpts: cursorOpts, createCursor: true, } @@ -96,8 +79,10 @@ func (c *Command) Execute(ctx context.Context) error { } return driver.Operation{ - CommandFn: c.commandFn, - ProcessResponseFn: func(info driver.ResponseInfo) error { + CommandFn: func(dst []byte, _ description.SelectedServer) ([]byte, error) { + return append(dst, c.command[4:len(c.command)-1]...), nil + }, + ProcessResponseFn: func(_ context.Context, info driver.ResponseInfo) error { c.resultResponse = info.ServerResponse if c.createCursor { @@ -114,21 +99,16 @@ func (c *Command) Execute(ctx context.Context) error { }, Client: c.session, Clock: c.clock, - RetryMode: c.retry, - Type: c.opType, - Batches: c.batches, CommandMonitor: c.monitor, Database: c.database, Deployment: c.deployment, ReadPreference: c.readPreference, Selector: c.selector, - WriteConcern: c.writeConcern, Crypt: c.crypt, ServerAPI: c.serverAPI, Timeout: c.timeout, Logger: c.logger, Authenticator: c.authenticator, - Name: c.name, }.Execute(ctx) } @@ -162,36 +142,6 @@ func (c *Command) CommandMonitor(monitor *event.CommandMonitor) *Command { return c } -// Batches sets the batches for this operation. -func (c *Command) Batches(batches *driver.Batches) *Command { - if c == nil { - c = new(Command) - } - - c.batches = batches - return c -} - -// RetryMode sets the RetryMode for this operation. -func (c *Command) RetryMode(retry driver.RetryMode) *Command { - if c == nil { - c = new(Command) - } - - c.retry = &retry - return c -} - -// Type sets the opType for this operation. -func (c *Command) Type(t driver.Type) *Command { - if c == nil { - c = new(Command) - } - - c.opType = t - return c -} - // Database sets the database to run this operation against. func (c *Command) Database(database string) *Command { if c == nil { @@ -232,16 +182,6 @@ func (c *Command) ServerSelector(selector description.ServerSelector) *Command { return c } -// WriteConcern sets the write concern for this operation. -func (c *Command) WriteConcern(writeConcern *writeconcern.WriteConcern) *Command { - if c == nil { - c = new(Command) - } - - c.writeConcern = writeConcern - return c -} - // Crypt sets the Crypt object to use for automatic encryption and decryption. func (c *Command) Crypt(crypt driver.Crypt) *Command { if c == nil { @@ -291,13 +231,3 @@ func (c *Command) Authenticator(authenticator driver.Authenticator) *Command { c.authenticator = authenticator return c } - -// Name sets the name for this operation. -func (c *Command) Name(name string) *Command { - if c == nil { - c = new(Command) - } - - c.name = name - return c -} diff --git a/x/mongo/driver/operation/commit_transaction.go b/x/mongo/driver/operation/commit_transaction.go index 6b402bdf63..cf20d9abec 100644 --- a/x/mongo/driver/operation/commit_transaction.go +++ b/x/mongo/driver/operation/commit_transaction.go @@ -42,7 +42,7 @@ func NewCommitTransaction() *CommitTransaction { return &CommitTransaction{} } -func (ct *CommitTransaction) processResponse(driver.ResponseInfo) error { +func (ct *CommitTransaction) processResponse(context.Context, driver.ResponseInfo) error { var err error return err } diff --git a/x/mongo/driver/operation/count.go b/x/mongo/driver/operation/count.go index eaafc9a244..8c0e30c031 100644 --- a/x/mongo/driver/operation/count.go +++ b/x/mongo/driver/operation/count.go @@ -99,7 +99,7 @@ func NewCount() *Count { // Result returns the result of executing this operation. func (c *Count) Result() CountResult { return c.result } -func (c *Count) processResponse(info driver.ResponseInfo) error { +func (c *Count) processResponse(_ context.Context, info driver.ResponseInfo) error { var err error c.result, err = buildCountResult(info.ServerResponse) return err diff --git a/x/mongo/driver/operation/create.go b/x/mongo/driver/operation/create.go index 4878e2c777..6a6301d6b1 100644 --- a/x/mongo/driver/operation/create.go +++ b/x/mongo/driver/operation/create.go @@ -56,7 +56,7 @@ func NewCreate(collectionName string) *Create { } } -func (c *Create) processResponse(driver.ResponseInfo) error { +func (c *Create) processResponse(context.Context, driver.ResponseInfo) error { return nil } diff --git a/x/mongo/driver/operation/create_indexes.go b/x/mongo/driver/operation/create_indexes.go index 464c1762de..64bd90940e 100644 --- a/x/mongo/driver/operation/create_indexes.go +++ b/x/mongo/driver/operation/create_indexes.go @@ -93,7 +93,7 @@ func NewCreateIndexes(indexes bsoncore.Document) *CreateIndexes { // Result returns the result of executing this operation. func (ci *CreateIndexes) Result() CreateIndexesResult { return ci.result } -func (ci *CreateIndexes) processResponse(info driver.ResponseInfo) error { +func (ci *CreateIndexes) processResponse(_ context.Context, info driver.ResponseInfo) error { var err error ci.result, err = buildCreateIndexesResult(info.ServerResponse) return err diff --git a/x/mongo/driver/operation/create_search_indexes.go b/x/mongo/driver/operation/create_search_indexes.go index 8185d27fe1..90495231c7 100644 --- a/x/mongo/driver/operation/create_search_indexes.go +++ b/x/mongo/driver/operation/create_search_indexes.go @@ -93,7 +93,7 @@ func NewCreateSearchIndexes(indexes bsoncore.Document) *CreateSearchIndexes { // Result returns the result of executing this operation. func (csi *CreateSearchIndexes) Result() CreateSearchIndexesResult { return csi.result } -func (csi *CreateSearchIndexes) processResponse(info driver.ResponseInfo) error { +func (csi *CreateSearchIndexes) processResponse(_ context.Context, info driver.ResponseInfo) error { var err error csi.result, err = buildCreateSearchIndexesResult(info.ServerResponse) return err diff --git a/x/mongo/driver/operation/delete.go b/x/mongo/driver/operation/delete.go index 4b520a5480..e03047a9cf 100644 --- a/x/mongo/driver/operation/delete.go +++ b/x/mongo/driver/operation/delete.go @@ -81,7 +81,7 @@ func NewDelete(deletes ...bsoncore.Document) *Delete { // Result returns the result of executing this operation. func (d *Delete) Result() DeleteResult { return d.result } -func (d *Delete) processResponse(info driver.ResponseInfo) error { +func (d *Delete) processResponse(_ context.Context, info driver.ResponseInfo) error { dr, err := buildDeleteResult(info.ServerResponse) d.result.N += dr.N return err @@ -92,31 +92,32 @@ func (d *Delete) Execute(ctx context.Context) error { if d.deployment == nil { return errors.New("the Delete operation must have a Deployment set before Execute can be called") } - batches := &driver.Batches{ - Identifier: "deletes", - Documents: d.deletes, - Ordered: d.ordered, - } return driver.Operation{ CommandFn: d.command, ProcessResponseFn: d.processResponse, - Batches: batches, - RetryMode: d.retry, - Type: driver.Write, - Client: d.session, - Clock: d.clock, - CommandMonitor: d.monitor, - Crypt: d.crypt, - Database: d.database, - Deployment: d.deployment, - Selector: d.selector, - WriteConcern: d.writeConcern, - ServerAPI: d.serverAPI, - Timeout: d.timeout, - Logger: d.logger, - Name: driverutil.DeleteOp, - Authenticator: d.authenticator, + Batches: []driver.Batches{ + { + Identifier: "deletes", + Documents: d.deletes, + Ordered: d.ordered, + }, + }, + RetryMode: d.retry, + Type: driver.Write, + Client: d.session, + Clock: d.clock, + CommandMonitor: d.monitor, + Crypt: d.crypt, + Database: d.database, + Deployment: d.deployment, + Selector: d.selector, + WriteConcern: d.writeConcern, + ServerAPI: d.serverAPI, + Timeout: d.timeout, + Logger: d.logger, + Name: driverutil.DeleteOp, + Authenticator: d.authenticator, }.Execute(ctx) } diff --git a/x/mongo/driver/operation/distinct.go b/x/mongo/driver/operation/distinct.go index 0c39027e76..946aa8aa4c 100644 --- a/x/mongo/driver/operation/distinct.go +++ b/x/mongo/driver/operation/distinct.go @@ -77,7 +77,7 @@ func NewDistinct(key string, query bsoncore.Document) *Distinct { // Result returns the result of executing this operation. func (d *Distinct) Result() DistinctResult { return d.result } -func (d *Distinct) processResponse(info driver.ResponseInfo) error { +func (d *Distinct) processResponse(_ context.Context, info driver.ResponseInfo) error { var err error d.result, err = buildDistinctResult(info.ServerResponse) return err diff --git a/x/mongo/driver/operation/drop_collection.go b/x/mongo/driver/operation/drop_collection.go index 5a32c2f8d4..11f4b74f82 100644 --- a/x/mongo/driver/operation/drop_collection.go +++ b/x/mongo/driver/operation/drop_collection.go @@ -79,7 +79,7 @@ func NewDropCollection() *DropCollection { // Result returns the result of executing this operation. func (dc *DropCollection) Result() DropCollectionResult { return dc.result } -func (dc *DropCollection) processResponse(info driver.ResponseInfo) error { +func (dc *DropCollection) processResponse(_ context.Context, info driver.ResponseInfo) error { var err error dc.result, err = buildDropCollectionResult(info.ServerResponse) return err diff --git a/x/mongo/driver/operation/drop_indexes.go b/x/mongo/driver/operation/drop_indexes.go index a22496b1e8..70932ed0fb 100644 --- a/x/mongo/driver/operation/drop_indexes.go +++ b/x/mongo/driver/operation/drop_indexes.go @@ -74,7 +74,7 @@ func NewDropIndexes(index any) *DropIndexes { // Result returns the result of executing this operation. func (di *DropIndexes) Result() DropIndexesResult { return di.result } -func (di *DropIndexes) processResponse(info driver.ResponseInfo) error { +func (di *DropIndexes) processResponse(_ context.Context, info driver.ResponseInfo) error { var err error di.result, err = buildDropIndexesResult(info.ServerResponse) return err diff --git a/x/mongo/driver/operation/drop_search_index.go b/x/mongo/driver/operation/drop_search_index.go index 94e4ddfb0d..688a6e2280 100644 --- a/x/mongo/driver/operation/drop_search_index.go +++ b/x/mongo/driver/operation/drop_search_index.go @@ -69,7 +69,7 @@ func NewDropSearchIndex(index string) *DropSearchIndex { // Result returns the result of executing this operation. func (dsi *DropSearchIndex) Result() DropSearchIndexResult { return dsi.result } -func (dsi *DropSearchIndex) processResponse(info driver.ResponseInfo) error { +func (dsi *DropSearchIndex) processResponse(_ context.Context, info driver.ResponseInfo) error { var err error dsi.result, err = buildDropSearchIndexResult(info.ServerResponse) return err diff --git a/x/mongo/driver/operation/end_sessions.go b/x/mongo/driver/operation/end_sessions.go index 8b24b3d8c2..4ddf781683 100644 --- a/x/mongo/driver/operation/end_sessions.go +++ b/x/mongo/driver/operation/end_sessions.go @@ -39,7 +39,7 @@ func NewEndSessions(sessionIDs bsoncore.Document) *EndSessions { } } -func (es *EndSessions) processResponse(driver.ResponseInfo) error { +func (es *EndSessions) processResponse(context.Context, driver.ResponseInfo) error { var err error return err } diff --git a/x/mongo/driver/operation/find.go b/x/mongo/driver/operation/find.go index c71b7d755e..fd611ebde8 100644 --- a/x/mongo/driver/operation/find.go +++ b/x/mongo/driver/operation/find.go @@ -80,7 +80,7 @@ func (f *Find) Result(opts driver.CursorOptions) (*driver.BatchCursor, error) { return driver.NewBatchCursor(f.result, f.session, f.clock, opts) } -func (f *Find) processResponse(info driver.ResponseInfo) error { +func (f *Find) processResponse(_ context.Context, info driver.ResponseInfo) error { var err error f.result, err = driver.NewCursorResponse(info) return err diff --git a/x/mongo/driver/operation/find_and_modify.go b/x/mongo/driver/operation/find_and_modify.go index ea365ccb23..1ec799be7c 100644 --- a/x/mongo/driver/operation/find_and_modify.go +++ b/x/mongo/driver/operation/find_and_modify.go @@ -114,7 +114,7 @@ func NewFindAndModify(query bsoncore.Document) *FindAndModify { // Result returns the result of executing this operation. func (fam *FindAndModify) Result() FindAndModifyResult { return fam.result } -func (fam *FindAndModify) processResponse(info driver.ResponseInfo) error { +func (fam *FindAndModify) processResponse(_ context.Context, info driver.ResponseInfo) error { var err error fam.result, err = buildFindAndModifyResult(info.ServerResponse) diff --git a/x/mongo/driver/operation/hello.go b/x/mongo/driver/operation/hello.go index 60c99f063d..8e38dd0473 100644 --- a/x/mongo/driver/operation/hello.go +++ b/x/mongo/driver/operation/hello.go @@ -586,7 +586,7 @@ func (h *Hello) createOperation() driver.Operation { CommandFn: h.command, Database: "admin", Deployment: h.d, - ProcessResponseFn: func(info driver.ResponseInfo) error { + ProcessResponseFn: func(_ context.Context, info driver.ResponseInfo) error { h.res = info.ServerResponse return nil }, @@ -610,7 +610,7 @@ func (h *Hello) GetHandshakeInformation(ctx context.Context, _ address.Address, CommandFn: h.handshakeCommand, Deployment: deployment, Database: "admin", - ProcessResponseFn: func(info driver.ResponseInfo) error { + ProcessResponseFn: func(_ context.Context, info driver.ResponseInfo) error { h.res = info.ServerResponse return nil }, diff --git a/x/mongo/driver/operation/insert.go b/x/mongo/driver/operation/insert.go index a65a4895f0..71817a9c42 100644 --- a/x/mongo/driver/operation/insert.go +++ b/x/mongo/driver/operation/insert.go @@ -80,7 +80,7 @@ func NewInsert(documents ...bsoncore.Document) *Insert { // Result returns the result of executing this operation. func (i *Insert) Result() InsertResult { return i.result } -func (i *Insert) processResponse(info driver.ResponseInfo) error { +func (i *Insert) processResponse(_ context.Context, info driver.ResponseInfo) error { ir, err := buildInsertResult(info.ServerResponse) i.result.N += ir.N return err @@ -91,31 +91,32 @@ func (i *Insert) Execute(ctx context.Context) error { if i.deployment == nil { return errors.New("the Insert operation must have a Deployment set before Execute can be called") } - batches := &driver.Batches{ - Identifier: "documents", - Documents: i.documents, - Ordered: i.ordered, - } return driver.Operation{ CommandFn: i.command, ProcessResponseFn: i.processResponse, - Batches: batches, - RetryMode: i.retry, - Type: driver.Write, - Client: i.session, - Clock: i.clock, - CommandMonitor: i.monitor, - Crypt: i.crypt, - Database: i.database, - Deployment: i.deployment, - Selector: i.selector, - WriteConcern: i.writeConcern, - ServerAPI: i.serverAPI, - Timeout: i.timeout, - Logger: i.logger, - Name: driverutil.InsertOp, - Authenticator: i.authenticator, + Batches: []driver.Batches{ + { + Identifier: "documents", + Documents: i.documents, + Ordered: i.ordered, + }, + }, + RetryMode: i.retry, + Type: driver.Write, + Client: i.session, + Clock: i.clock, + CommandMonitor: i.monitor, + Crypt: i.crypt, + Database: i.database, + Deployment: i.deployment, + Selector: i.selector, + WriteConcern: i.writeConcern, + ServerAPI: i.serverAPI, + Timeout: i.timeout, + Logger: i.logger, + Name: driverutil.InsertOp, + Authenticator: i.authenticator, }.Execute(ctx) } diff --git a/x/mongo/driver/operation/listDatabases.go b/x/mongo/driver/operation/listDatabases.go index 3df171e37a..1f8719e9fc 100644 --- a/x/mongo/driver/operation/listDatabases.go +++ b/x/mongo/driver/operation/listDatabases.go @@ -135,7 +135,7 @@ func NewListDatabases(filter bsoncore.Document) *ListDatabases { // Result returns the result of executing this operation. func (ld *ListDatabases) Result() ListDatabasesResult { return ld.result } -func (ld *ListDatabases) processResponse(info driver.ResponseInfo) error { +func (ld *ListDatabases) processResponse(_ context.Context, info driver.ResponseInfo) error { var err error ld.result, err = buildListDatabasesResult(info.ServerResponse) diff --git a/x/mongo/driver/operation/list_collections.go b/x/mongo/driver/operation/list_collections.go index 1e39f5bfbe..927f82be48 100644 --- a/x/mongo/driver/operation/list_collections.go +++ b/x/mongo/driver/operation/list_collections.go @@ -55,7 +55,7 @@ func (lc *ListCollections) Result(opts driver.CursorOptions) (*driver.BatchCurso return driver.NewBatchCursor(lc.result, lc.session, lc.clock, opts) } -func (lc *ListCollections) processResponse(info driver.ResponseInfo) error { +func (lc *ListCollections) processResponse(_ context.Context, info driver.ResponseInfo) error { var err error lc.result, err = driver.NewCursorResponse(info) return err diff --git a/x/mongo/driver/operation/list_indexes.go b/x/mongo/driver/operation/list_indexes.go index 433344f307..93cd288842 100644 --- a/x/mongo/driver/operation/list_indexes.go +++ b/x/mongo/driver/operation/list_indexes.go @@ -54,7 +54,7 @@ func (li *ListIndexes) Result(opts driver.CursorOptions) (*driver.BatchCursor, e return driver.NewBatchCursor(li.result, clientSession, clock, opts) } -func (li *ListIndexes) processResponse(info driver.ResponseInfo) error { +func (li *ListIndexes) processResponse(_ context.Context, info driver.ResponseInfo) error { var err error li.result, err = driver.NewCursorResponse(info) diff --git a/x/mongo/driver/operation/update.go b/x/mongo/driver/operation/update.go index 1070e7ca70..2b63e750a2 100644 --- a/x/mongo/driver/operation/update.go +++ b/x/mongo/driver/operation/update.go @@ -124,7 +124,7 @@ func NewUpdate(updates ...bsoncore.Document) *Update { // Result returns the result of executing this operation. func (u *Update) Result() UpdateResult { return u.result } -func (u *Update) processResponse(info driver.ResponseInfo) error { +func (u *Update) processResponse(_ context.Context, info driver.ResponseInfo) error { ur, err := buildUpdateResult(info.ServerResponse) u.result.N += ur.N @@ -144,31 +144,32 @@ func (u *Update) Execute(ctx context.Context) error { if u.deployment == nil { return errors.New("the Update operation must have a Deployment set before Execute can be called") } - batches := &driver.Batches{ - Identifier: "updates", - Documents: u.updates, - Ordered: u.ordered, - } return driver.Operation{ CommandFn: u.command, ProcessResponseFn: u.processResponse, - Batches: batches, - RetryMode: u.retry, - Type: driver.Write, - Client: u.session, - Clock: u.clock, - CommandMonitor: u.monitor, - Database: u.database, - Deployment: u.deployment, - Selector: u.selector, - WriteConcern: u.writeConcern, - Crypt: u.crypt, - ServerAPI: u.serverAPI, - Timeout: u.timeout, - Logger: u.logger, - Name: driverutil.UpdateOp, - Authenticator: u.authenticator, + Batches: []driver.Batches{ + { + Identifier: "updates", + Documents: u.updates, + Ordered: u.ordered, + }, + }, + RetryMode: u.retry, + Type: driver.Write, + Client: u.session, + Clock: u.clock, + CommandMonitor: u.monitor, + Database: u.database, + Deployment: u.deployment, + Selector: u.selector, + WriteConcern: u.writeConcern, + Crypt: u.crypt, + ServerAPI: u.serverAPI, + Timeout: u.timeout, + Logger: u.logger, + Name: driverutil.UpdateOp, + Authenticator: u.authenticator, }.Execute(ctx) } diff --git a/x/mongo/driver/operation/update_search_index.go b/x/mongo/driver/operation/update_search_index.go index c63e048f21..6df4874790 100644 --- a/x/mongo/driver/operation/update_search_index.go +++ b/x/mongo/driver/operation/update_search_index.go @@ -71,7 +71,7 @@ func NewUpdateSearchIndex(index string, definition bsoncore.Document) *UpdateSea // Result returns the result of executing this operation. func (usi *UpdateSearchIndex) Result() UpdateSearchIndexResult { return usi.result } -func (usi *UpdateSearchIndex) processResponse(info driver.ResponseInfo) error { +func (usi *UpdateSearchIndex) processResponse(_ context.Context, info driver.ResponseInfo) error { var err error usi.result, err = buildUpdateSearchIndexResult(info.ServerResponse) return err diff --git a/x/mongo/driver/operation_exhaust.go b/x/mongo/driver/operation_exhaust.go index e0879de316..1836ec4657 100644 --- a/x/mongo/driver/operation_exhaust.go +++ b/x/mongo/driver/operation_exhaust.go @@ -28,7 +28,7 @@ func (op Operation) ExecuteExhaust(ctx context.Context, conn StreamerConnection) ServerResponse: res, Connection: conn, } - if err = op.ProcessResponseFn(info); err != nil { + if err = op.ProcessResponseFn(ctx, info); err != nil { return err } } From 63a973bbed89375a0f85bfd90eadbfe96d4d435e Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Sat, 12 Oct 2024 01:30:57 -0400 Subject: [PATCH 08/14] WIP --- mongo/client_bulk_write.go | 698 ++++++++++-------- x/mongo/driver/batch_cursor.go | 36 +- x/mongo/driver/batches.go | 109 ++- x/mongo/driver/batches_test.go | 189 +++-- x/mongo/driver/operation.go | 338 +++++---- x/mongo/driver/operation/abort_transaction.go | 5 +- x/mongo/driver/operation/aggregate.go | 10 +- x/mongo/driver/operation/command.go | 10 +- .../driver/operation/commit_transaction.go | 5 +- x/mongo/driver/operation/count.go | 4 +- x/mongo/driver/operation/create.go | 2 +- x/mongo/driver/operation/create_indexes.go | 4 +- .../driver/operation/create_search_indexes.go | 4 +- x/mongo/driver/operation/delete.go | 14 +- x/mongo/driver/operation/distinct.go | 4 +- x/mongo/driver/operation/drop_collection.go | 4 +- x/mongo/driver/operation/drop_indexes.go | 4 +- x/mongo/driver/operation/drop_search_index.go | 4 +- x/mongo/driver/operation/end_sessions.go | 5 +- x/mongo/driver/operation/find.go | 9 +- x/mongo/driver/operation/find_and_modify.go | 4 +- x/mongo/driver/operation/hello.go | 8 +- x/mongo/driver/operation/insert.go | 14 +- x/mongo/driver/operation/list_collections.go | 9 +- .../{listDatabases.go => list_databases.go} | 4 +- x/mongo/driver/operation/list_indexes.go | 10 +- x/mongo/driver/operation/update.go | 14 +- .../driver/operation/update_search_index.go | 4 +- x/mongo/driver/operation_exhaust.go | 5 +- x/mongo/driver/wiremessage/wiremessage.go | 33 + 30 files changed, 854 insertions(+), 709 deletions(-) rename x/mongo/driver/operation/{listDatabases.go => list_databases.go} (98%) diff --git a/mongo/client_bulk_write.go b/mongo/client_bulk_write.go index e55a52df9d..962b00efd3 100644 --- a/mongo/client_bulk_write.go +++ b/mongo/client_bulk_write.go @@ -10,6 +10,8 @@ import ( "context" "errors" "fmt" + "io" + "strconv" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/bsoncodec" @@ -20,6 +22,7 @@ import ( "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" "go.mongodb.org/mongo-driver/x/mongo/driver" "go.mongodb.org/mongo-driver/x/mongo/driver/session" + "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" ) // bulkWrite performs a bulkwrite operation @@ -35,29 +38,26 @@ type clientBulkWrite struct { selector description.ServerSelector writeConcern *writeconcern.WriteConcern - cursorHandlers []func(*cursorInfo, bson.Raw) error - insIDMap map[int]interface{} - - result ClientBulkWriteResult - writeConcernErrors []WriteConcernError - writeErrors map[int]WriteError + result ClientBulkWriteResult } func (bw *clientBulkWrite) execute(ctx context.Context) error { if len(bw.models) == 0 { return errors.New("empty write models") } - bw.writeErrors = make(map[int]WriteError) - batches, retry, err := bw.processModels() - if err != nil { - return err + batches := &modelBatches{ + session: bw.session, + client: bw.client, + ordered: bw.ordered, + models: bw.models, + result: &bw.result, } - err = driver.Operation{ + err := driver.Operation{ CommandFn: bw.newCommand(), - ProcessResponseFn: bw.ProcessResponse, + ProcessResponseFn: batches.processResponse, Client: bw.session, Clock: bw.client.clock, - RetryMode: retry, + RetryMode: &batches.retryMode, Type: driver.Write, Batches: batches, CommandMonitor: bw.client.monitor, @@ -75,10 +75,38 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { if err != nil && errors.Is(err, driver.ErrUnacknowledgedWrite) { return nil } - fmt.Println("exec", len(bw.writeErrors), err) return err } +func (bw *clientBulkWrite) newCommand() func([]byte, description.SelectedServer) ([]byte, error) { + return func(dst []byte, desc description.SelectedServer) ([]byte, error) { + dst = bsoncore.AppendInt32Element(dst, "bulkWrite", 1) + + dst = bsoncore.AppendBooleanElement(dst, "errorsOnly", bw.errorsOnly) + if bw.bypassDocumentValidation != nil && (desc.WireVersion != nil && desc.WireVersion.Includes(4)) { + dst = bsoncore.AppendBooleanElement(dst, "bypassDocumentValidation", *bw.bypassDocumentValidation) + } + if bw.comment != nil { + comment, err := marshalValue(bw.comment, bw.client.bsonOpts, bw.client.registry) + if err != nil { + return nil, err + } + dst = bsoncore.AppendValueElement(dst, "comment", comment) + } + if bw.ordered != nil { + dst = bsoncore.AppendBooleanElement(dst, "ordered", *bw.ordered) + } + if bw.let != nil { + let, err := marshal(bw.let, bw.client.bsonOpts, bw.client.registry) + if err != nil { + return nil, err + } + dst = bsoncore.AppendDocumentElement(dst, "let", let) + } + return dst, nil + } +} + type cursorInfo struct { Ok bool Idx int32 @@ -109,21 +137,240 @@ func (cur *cursorInfo) extractError() *WriteError { return err } -func (bw *clientBulkWrite) ProcessResponse(ctx context.Context, info driver.ResponseInfo) error { +type modelBatches struct { + session *session.Client + client *Client + + ordered *bool + models []interface{} + + offset int + + retryMode driver.RetryMode // RetryNone by default + cursorHandlers []func(*cursorInfo, bson.Raw) bool + newIDMap map[int]interface{} + + result *ClientBulkWriteResult + writeConcernErrors []WriteConcernError + writeErrors map[int]WriteError +} + +func (mb *modelBatches) IsOrdered() *bool { + return mb.ordered +} + +func (mb *modelBatches) AdvanceBatches(n int) { + mb.offset += n +} + +func (mb *modelBatches) End() bool { + return len(mb.models) <= mb.offset +} + +func (mb *modelBatches) AppendBatchSequence(dst []byte, maxCount, maxDocSize, totalSize int) (int, []byte, error) { + fn := functionSet{ + appendStart: func(dst []byte, identifier string) (int32, []byte) { + var idx int32 + dst = wiremessage.AppendMsgSectionType(dst, wiremessage.DocumentSequence) + idx, dst = bsoncore.ReserveLength(dst) + dst = append(dst, identifier...) + dst = append(dst, 0x00) + return idx, dst + }, + appendDocument: func(dst []byte, _ string, doc []byte) []byte { + dst = append(dst, doc...) + return dst + }, + appendEnd: func(dst []byte, idx, length int32) []byte { + dst = bsoncore.UpdateLength(dst, idx, length) + return dst + }, + } + return mb.appendBatches(fn, dst, maxCount, maxDocSize, totalSize) +} + +func (mb *modelBatches) AppendBatchArray(dst []byte, maxCount, maxDocSize, totalSize int) (int, []byte, error) { + fn := functionSet{ + appendStart: bsoncore.AppendArrayElementStart, + appendDocument: bsoncore.AppendDocumentElement, + appendEnd: func(dst []byte, idx, _ int32) []byte { + dst, _ = bsoncore.AppendArrayEnd(dst, idx) + return dst + }, + } + return mb.appendBatches(fn, dst, maxCount, maxDocSize, totalSize) +} + +type functionSet struct { + appendStart func([]byte, string) (int32, []byte) + appendDocument func([]byte, string, []byte) []byte + appendEnd func([]byte, int32, int32) []byte +} + +func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxDocSize, totalSize int) (int, []byte, error) { + if mb.End() { + return 0, dst, io.EOF + } + + mb.cursorHandlers = make([]func(*cursorInfo, bson.Raw) bool, len(mb.models)) + mb.newIDMap = make(map[int]interface{}) + + nsMap := make(map[string]int) + getNsIndex := func(namespace string) (int, bsoncore.Document) { + idx, doc := bsoncore.AppendDocumentStart(nil) + doc = bsoncore.AppendStringElement(doc, "ns", namespace) + doc, _ = bsoncore.AppendDocumentEnd(doc, idx) + + if v, ok := nsMap[namespace]; ok { + return v, doc + } + nsIdx := len(nsMap) + nsMap[namespace] = nsIdx + return nsIdx, doc + } + + canRetry := true + + l := len(dst) + + opsIdx, dst := fn.appendStart(dst, "ops") + nsIdx, nsDst := fn.appendStart(nil, "nsInfo") + + size := (len(dst) - l) * 2 + var n int + for i := mb.offset; i < len(mb.models); i++ { + if n == maxCount { + break + } + + var nsIdx int + var ns, doc bsoncore.Document + var err error + switch model := mb.models[i].(type) { + case *ClientInsertOneModel: + nsIdx, ns = getNsIndex(model.Namespace) + mb.cursorHandlers[i] = mb.appendInsertResult + var id interface{} + id, doc, err = (&clientInsertDoc{ + namespace: nsIdx, + document: model.Document, + }).marshal(mb.client.bsonOpts, mb.client.registry) + if err != nil { + break + } + mb.newIDMap[i] = id + case *ClientUpdateOneModel: + nsIdx, ns = getNsIndex(model.Namespace) + mb.cursorHandlers[i] = mb.appendUpdateResult + doc, err = (&clientUpdateDoc{ + namespace: nsIdx, + filter: model.Filter, + update: model.Update, + hint: model.Hint, + arrayFilters: model.ArrayFilters, + collation: model.Collation, + upsert: model.Upsert, + multi: false, + checkDollarKey: true, + }).marshal(mb.client.bsonOpts, mb.client.registry) + case *ClientUpdateManyModel: + canRetry = false + nsIdx, ns = getNsIndex(model.Namespace) + mb.cursorHandlers[i] = mb.appendUpdateResult + doc, err = (&clientUpdateDoc{ + namespace: nsIdx, + filter: model.Filter, + update: model.Update, + hint: model.Hint, + arrayFilters: model.ArrayFilters, + collation: model.Collation, + upsert: model.Upsert, + multi: true, + checkDollarKey: true, + }).marshal(mb.client.bsonOpts, mb.client.registry) + case *ClientReplaceOneModel: + nsIdx, ns = getNsIndex(model.Namespace) + mb.cursorHandlers[i] = mb.appendUpdateResult + doc, err = (&clientUpdateDoc{ + namespace: nsIdx, + filter: model.Filter, + update: model.Replacement, + hint: model.Hint, + arrayFilters: nil, + collation: model.Collation, + upsert: model.Upsert, + multi: false, + checkDollarKey: false, + }).marshal(mb.client.bsonOpts, mb.client.registry) + case *ClientDeleteOneModel: + nsIdx, ns = getNsIndex(model.Namespace) + mb.cursorHandlers[i] = mb.appendDeleteResult + doc, err = (&clientDeleteDoc{ + namespace: nsIdx, + filter: model.Filter, + collation: model.Collation, + hint: model.Hint, + multi: false, + }).marshal(mb.client.bsonOpts, mb.client.registry) + case *ClientDeleteManyModel: + canRetry = false + nsIdx, ns = getNsIndex(model.Namespace) + mb.cursorHandlers[i] = mb.appendDeleteResult + doc, err = (&clientDeleteDoc{ + namespace: nsIdx, + filter: model.Filter, + collation: model.Collation, + hint: model.Hint, + multi: true, + }).marshal(mb.client.bsonOpts, mb.client.registry) + } + if err != nil { + return 0, nil, err + } + length := len(doc) + len(ns) + if length > maxDocSize { + break + } + size += length + if size >= totalSize { + break + } + + dst = fn.appendDocument(dst, strconv.Itoa(n), doc) + nsDst = fn.appendDocument(nsDst, strconv.Itoa(n), ns) + n++ + } + if n == 0 { + return 0, dst[:l], nil + } + + dst = fn.appendEnd(dst, opsIdx, int32(len(dst[opsIdx:]))) + nsDst = fn.appendEnd(nsDst, nsIdx, int32(len(nsDst[nsIdx:]))) + dst = append(dst, nsDst...) + + mb.retryMode = driver.RetryNone + if mb.client.retryWrites && canRetry { + mb.retryMode = driver.RetryOncePerCommand + } + return n, dst, nil +} + +func (mb *modelBatches) processResponse(ctx context.Context, resp bsoncore.Document, info driver.ResponseInfo) error { fmt.Println("ProcessResponse", info.Error) var writeCmdErr driver.WriteCommandError if errors.As(info.Error, &writeCmdErr) && writeCmdErr.WriteConcernError != nil { wce := convertDriverWriteConcernError(writeCmdErr.WriteConcernError) if wce != nil { - bw.writeConcernErrors = append(bw.writeConcernErrors, *wce) + mb.writeConcernErrors = append(mb.writeConcernErrors, *wce) } } // closeImplicitSession(sess) - if len(info.ServerResponse) == 0 { + if len(resp) == 0 { return nil } var res struct { Ok bool + Cursor bsoncore.Document NDeleted int32 NInserted int32 NMatched int32 @@ -133,235 +380,130 @@ func (bw *clientBulkWrite) ProcessResponse(ctx context.Context, info driver.Resp Code int32 Errmsg string } - err := bson.Unmarshal(info.ServerResponse, &res) + err := bson.UnmarshalWithRegistry(mb.client.registry, resp, &res) if err != nil { return err } - bw.result.DeletedCount += int64(res.NDeleted) - bw.result.InsertedCount += int64(res.NInserted) - bw.result.MatchedCount += int64(res.NMatched) - bw.result.ModifiedCount += int64(res.NModified) - bw.result.UpsertedCount += int64(res.NUpserted) + mb.result.DeletedCount += int64(res.NDeleted) + mb.result.InsertedCount += int64(res.NInserted) + mb.result.MatchedCount += int64(res.NMatched) + mb.result.ModifiedCount += int64(res.NModified) + mb.result.UpsertedCount += int64(res.NUpserted) var cursorRes driver.CursorResponse - cursorRes, err = driver.NewCursorResponse(info) + cursorRes, err = driver.NewCursorResponse(res.Cursor, info) if err != nil { return err } var bCursor *driver.BatchCursor - bCursor, err = driver.NewBatchCursor(cursorRes, bw.session, bw.client.clock, + bCursor, err = driver.NewBatchCursor(cursorRes, mb.session, mb.client.clock, driver.CursorOptions{ - CommandMonitor: bw.client.monitor, - Crypt: bw.client.cryptFLE, - ServerAPI: bw.client.serverAPI, - MarshalValueEncoderFn: newEncoderFn(bw.client.bsonOpts, bw.client.registry), + CommandMonitor: mb.client.monitor, + Crypt: mb.client.cryptFLE, + ServerAPI: mb.client.serverAPI, + MarshalValueEncoderFn: newEncoderFn(mb.client.bsonOpts, mb.client.registry), }, ) if err != nil { return err } var cursor *Cursor - cursor, err = newCursor(bCursor, bw.client.bsonOpts, bw.client.registry) + cursor, err = newCursorWithSession(bCursor, mb.client.bsonOpts, mb.client.registry, mb.session) if err != nil { return err } defer cursor.Close(ctx) + ok := true for cursor.Next(ctx) { var cur cursorInfo cursor.Decode(&cur) - if int(cur.Idx) >= len(bw.cursorHandlers) { + if int(cur.Idx) >= len(mb.cursorHandlers) { continue } - if err := bw.cursorHandlers[int(cur.Idx)](&cur, cursor.Current); err != nil { - fmt.Println("ProcessResponse cursorHandlers", err) - return err - } + ok = ok && mb.cursorHandlers[int(cur.Idx)](&cur, cursor.Current) } err = cursor.Err() if err != nil { return err } fmt.Println("ProcessResponse toplevelerror", res.Ok, res.NErrors, res.Code, res.Errmsg) - // if !res.Ok || res.NErrors > 0 { - // exception := bw.formException() - // exception.TopLevelError = &WriteError{ - // Code: int(res.Code), - // Message: res.Errmsg, - // Raw: bson.Raw(info.ServerResponse), - // } - // return exception - // } + if writeCmdErr.WriteConcernError != nil || !ok || !res.Ok || res.NErrors > 0 { + exception := ClientBulkWriteException{ + WriteConcernErrors: mb.writeConcernErrors, + WriteErrors: mb.writeErrors, + PartialResult: mb.result, + } + if !res.Ok || res.NErrors > 0 { + exception.TopLevelError = &WriteError{ + Code: int(res.Code), + Message: res.Errmsg, + Raw: bson.Raw(resp), + } + } + return exception + } return nil } -func (bw *clientBulkWrite) processModels() ([]driver.Batches, *driver.RetryMode, error) { - nsMap := make(map[string]int) - var nsList []bsoncore.Document - getNsIndex := func(namespace string) int { - if v, ok := nsMap[namespace]; ok { - return v - } - nsIdx := len(nsList) - nsMap[namespace] = nsIdx - idx, doc := bsoncore.AppendDocumentStart(nil) - doc = bsoncore.AppendStringElement(doc, "ns", namespace) - doc, _ = bsoncore.AppendDocumentEnd(doc, idx) - nsList = append(nsList, doc) - return nsIdx +func (mb *modelBatches) appendDeleteResult(cur *cursorInfo, raw bson.Raw) bool { + if mb.result.DeleteResults == nil { + mb.result.DeleteResults = make(map[int]ClientDeleteResult) } - - bw.cursorHandlers = make([]func(*cursorInfo, bson.Raw) error, len(bw.models)) - bw.insIDMap = make(map[int]interface{}) - canRetry := true - docs := make([]bsoncore.Document, len(bw.models)) - for i, v := range bw.models { - var doc bsoncore.Document - var err error - switch model := v.(type) { - case *ClientInsertOneModel: - nsIdx := getNsIndex(model.Namespace) - bw.cursorHandlers[i] = bw.appendInsertResult - var id interface{} - id, doc, err = createClientInsertDoc(int32(nsIdx), model.Document, bw.client.bsonOpts, bw.client.registry) - if err != nil { - break - } - bw.insIDMap[i] = id - case *ClientUpdateOneModel: - nsIdx := getNsIndex(model.Namespace) - bw.cursorHandlers[i] = bw.appendUpdateResult - doc, err = createClientUpdateDoc( - int32(nsIdx), - model.Filter, - model.Update, - model.Hint, - model.ArrayFilters, - model.Collation, - model.Upsert, - false, - true, - bw.client.bsonOpts, - bw.client.registry) - case *ClientUpdateManyModel: - canRetry = false - nsIdx := getNsIndex(model.Namespace) - bw.cursorHandlers[i] = bw.appendUpdateResult - doc, err = createClientUpdateDoc( - int32(nsIdx), - model.Filter, - model.Update, - model.Hint, - model.ArrayFilters, - model.Collation, - model.Upsert, - true, - true, - bw.client.bsonOpts, - bw.client.registry) - case *ClientReplaceOneModel: - nsIdx := getNsIndex(model.Namespace) - bw.cursorHandlers[i] = bw.appendUpdateResult - doc, err = createClientUpdateDoc( - int32(nsIdx), - model.Filter, - model.Replacement, - model.Hint, - nil, - model.Collation, - model.Upsert, - false, - false, - bw.client.bsonOpts, - bw.client.registry) - case *ClientDeleteOneModel: - nsIdx := getNsIndex(model.Namespace) - bw.cursorHandlers[i] = bw.appendDeleteResult - doc, err = createClientDeleteDoc( - int32(nsIdx), - model.Filter, - model.Collation, - model.Hint, - false, - bw.client.bsonOpts, - bw.client.registry) - case *ClientDeleteManyModel: - canRetry = false - nsIdx := getNsIndex(model.Namespace) - bw.cursorHandlers[i] = bw.appendDeleteResult - doc, err = createClientDeleteDoc( - int32(nsIdx), - model.Filter, - model.Collation, - model.Hint, - true, - bw.client.bsonOpts, - bw.client.registry) - } - if err != nil { - return nil, nil, err - } - docs[i] = doc - } - retry := driver.RetryNone - if bw.client.retryWrites && canRetry { - retry = driver.RetryOncePerCommand - } - ordered := false - return []driver.Batches{ - { - Identifier: "ops", - Documents: docs, - Ordered: bw.ordered, - }, - { - Identifier: "nsInfo", - Documents: nsList, - Ordered: &ordered, - }, - }, - &retry, nil + mb.result.DeleteResults[int(cur.Idx)] = ClientDeleteResult{int64(cur.N)} + if err := cur.extractError(); err != nil { + err.Raw = raw + mb.writeErrors[int(cur.Idx)] = *err + return false + } + return true } -func (bw *clientBulkWrite) newCommand() func([]byte, description.SelectedServer) ([]byte, error) { - return func(dst []byte, desc description.SelectedServer) ([]byte, error) { - dst = bsoncore.AppendInt32Element(dst, "bulkWrite", 1) +func (mb *modelBatches) appendInsertResult(cur *cursorInfo, raw bson.Raw) bool { + if mb.result.InsertResults == nil { + mb.result.InsertResults = make(map[int]ClientInsertResult) + } + mb.result.InsertResults[int(cur.Idx)] = ClientInsertResult{mb.newIDMap[int(cur.Idx)]} + if err := cur.extractError(); err != nil { + err.Raw = raw + mb.writeErrors[int(cur.Idx)] = *err + return false + } + return true +} - dst = bsoncore.AppendBooleanElement(dst, "errorsOnly", bw.errorsOnly) - if bw.bypassDocumentValidation != nil && (desc.WireVersion != nil && desc.WireVersion.Includes(4)) { - dst = bsoncore.AppendBooleanElement(dst, "bypassDocumentValidation", *bw.bypassDocumentValidation) - } - if bw.comment != nil { - comment, err := marshalValue(bw.comment, bw.client.bsonOpts, bw.client.registry) - if err != nil { - return nil, err - } - dst = bsoncore.AppendValueElement(dst, "comment", comment) - } - if bw.ordered != nil { - dst = bsoncore.AppendBooleanElement(dst, "ordered", *bw.ordered) - } - if bw.let != nil { - let, err := marshal(bw.let, bw.client.bsonOpts, bw.client.registry) - if err != nil { - return nil, err - } - dst = bsoncore.AppendDocumentElement(dst, "let", let) - } - return dst, nil +func (mb *modelBatches) appendUpdateResult(cur *cursorInfo, raw bson.Raw) bool { + if mb.result.UpdateResults == nil { + mb.result.UpdateResults = make(map[int]ClientUpdateResult) + } + result := ClientUpdateResult{ + MatchedCount: int64(cur.N), + } + if cur.NModified != nil { + result.ModifiedCount = int64(*cur.NModified) } + if cur.Upserted != nil { + result.UpsertedID = (*cur.Upserted).ID + } + mb.result.UpdateResults[int(cur.Idx)] = result + if err := cur.extractError(); err != nil { + err.Raw = raw + mb.writeErrors[int(cur.Idx)] = *err + return false + } + return true +} + +type clientInsertDoc struct { + namespace int + document interface{} } -func createClientInsertDoc( - namespace int32, - document interface{}, - bsonOpts *options.BSONOptions, - registry *bsoncodec.Registry, -) (interface{}, bsoncore.Document, error) { +func (d *clientInsertDoc) marshal(bsonOpts *options.BSONOptions, registry *bsoncodec.Registry) (interface{}, bsoncore.Document, error) { uidx, doc := bsoncore.AppendDocumentStart(nil) - doc = bsoncore.AppendInt32Element(doc, "insert", namespace) - f, err := marshal(document, bsonOpts, registry) + doc = bsoncore.AppendInt32Element(doc, "insert", int32(d.namespace)) + f, err := marshal(d.document, bsonOpts, registry) if err != nil { return nil, nil, err } @@ -375,61 +517,61 @@ func createClientInsertDoc( return id, doc, err } -func createClientUpdateDoc( - namespace int32, - filter interface{}, - update interface{}, - hint interface{}, - arrayFilters *options.ArrayFilters, - collation *options.Collation, - upsert *bool, - multi bool, - checkDollarKey bool, - bsonOpts *options.BSONOptions, - registry *bsoncodec.Registry, -) (bsoncore.Document, error) { +type clientUpdateDoc struct { + namespace int + filter interface{} + update interface{} + hint interface{} + arrayFilters *options.ArrayFilters + collation *options.Collation + upsert *bool + multi bool + checkDollarKey bool +} + +func (d *clientUpdateDoc) marshal(bsonOpts *options.BSONOptions, registry *bsoncodec.Registry) (bsoncore.Document, error) { uidx, doc := bsoncore.AppendDocumentStart(nil) - doc = bsoncore.AppendInt32Element(doc, "update", namespace) + doc = bsoncore.AppendInt32Element(doc, "update", int32(d.namespace)) - f, err := marshal(filter, bsonOpts, registry) + f, err := marshal(d.filter, bsonOpts, registry) if err != nil { return nil, err } doc = bsoncore.AppendDocumentElement(doc, "filter", f) - u, err := marshalUpdateValue(update, bsonOpts, registry, checkDollarKey) + u, err := marshalUpdateValue(d.update, bsonOpts, registry, d.checkDollarKey) if err != nil { return nil, err } doc = bsoncore.AppendValueElement(doc, "updateMods", u) - doc = bsoncore.AppendBooleanElement(doc, "multi", multi) + doc = bsoncore.AppendBooleanElement(doc, "multi", d.multi) - if arrayFilters != nil { + if d.arrayFilters != nil { reg := registry - if arrayFilters.Registry != nil { - reg = arrayFilters.Registry + if d.arrayFilters.Registry != nil { + reg = d.arrayFilters.Registry } - arr, err := marshalValue(arrayFilters.Filters, bsonOpts, reg) + arr, err := marshalValue(d.arrayFilters.Filters, bsonOpts, reg) if err != nil { return nil, err } doc = bsoncore.AppendArrayElement(doc, "arrayFilters", arr.Data) } - if collation != nil { - doc = bsoncore.AppendDocumentElement(doc, "collation", bsoncore.Document(collation.ToDocument())) + if d.collation != nil { + doc = bsoncore.AppendDocumentElement(doc, "collation", bsoncore.Document(d.collation.ToDocument())) } - if upsert != nil { - doc = bsoncore.AppendBooleanElement(doc, "upsert", *upsert) + if d.upsert != nil { + doc = bsoncore.AppendBooleanElement(doc, "upsert", *d.upsert) } - if hint != nil { - if isUnorderedMap(hint) { + if d.hint != nil { + if isUnorderedMap(d.hint) { return nil, ErrMapForOrderedArgument{"hint"} } - hintVal, err := marshalValue(hint, bsonOpts, registry) + hintVal, err := marshalValue(d.hint, bsonOpts, registry) if err != nil { return nil, err } @@ -439,34 +581,34 @@ func createClientUpdateDoc( return bsoncore.AppendDocumentEnd(doc, uidx) } -func createClientDeleteDoc( - namespace int32, - filter interface{}, - collation *options.Collation, - hint interface{}, - multi bool, - bsonOpts *options.BSONOptions, - registry *bsoncodec.Registry, -) (bsoncore.Document, error) { +type clientDeleteDoc struct { + namespace int + filter interface{} + collation *options.Collation + hint interface{} + multi bool +} + +func (d *clientDeleteDoc) marshal(bsonOpts *options.BSONOptions, registry *bsoncodec.Registry) (bsoncore.Document, error) { didx, doc := bsoncore.AppendDocumentStart(nil) - doc = bsoncore.AppendInt32Element(doc, "delete", namespace) + doc = bsoncore.AppendInt32Element(doc, "delete", int32(d.namespace)) - f, err := marshal(filter, bsonOpts, registry) + f, err := marshal(d.filter, bsonOpts, registry) if err != nil { return nil, err } doc = bsoncore.AppendDocumentElement(doc, "filter", f) - doc = bsoncore.AppendBooleanElement(doc, "multi", multi) + doc = bsoncore.AppendBooleanElement(doc, "multi", d.multi) - if collation != nil { - doc = bsoncore.AppendDocumentElement(doc, "collation", collation.ToDocument()) + if d.collation != nil { + doc = bsoncore.AppendDocumentElement(doc, "collation", d.collation.ToDocument()) } - if hint != nil { - if isUnorderedMap(hint) { + if d.hint != nil { + if isUnorderedMap(d.hint) { return nil, ErrMapForOrderedArgument{"hint"} } - hintVal, err := marshalValue(hint, bsonOpts, registry) + hintVal, err := marshalValue(d.hint, bsonOpts, registry) if err != nil { return nil, err } @@ -474,65 +616,3 @@ func createClientDeleteDoc( } return bsoncore.AppendDocumentEnd(doc, didx) } - -func (bw *clientBulkWrite) appendDeleteResult(cur *cursorInfo, raw bson.Raw) error { - if bw.result.DeleteResults == nil { - bw.result.DeleteResults = make(map[int]ClientDeleteResult) - } - bw.result.DeleteResults[int(cur.Idx)] = ClientDeleteResult{int64(cur.N)} - if err := cur.extractError(); err != nil { - err.Raw = raw - bw.writeErrors[int(cur.Idx)] = *err - if bw.ordered != nil && *bw.ordered { - return bw.formException() - } - } - return nil -} - -func (bw *clientBulkWrite) appendInsertResult(cur *cursorInfo, raw bson.Raw) error { - if bw.result.InsertResults == nil { - bw.result.InsertResults = make(map[int]ClientInsertResult) - } - bw.result.InsertResults[int(cur.Idx)] = ClientInsertResult{bw.insIDMap[int(cur.Idx)]} - if err := cur.extractError(); err != nil { - err.Raw = raw - bw.writeErrors[int(cur.Idx)] = *err - if bw.ordered != nil && *bw.ordered { - return bw.formException() - } - } - return nil -} - -func (bw *clientBulkWrite) appendUpdateResult(cur *cursorInfo, raw bson.Raw) error { - if bw.result.UpdateResults == nil { - bw.result.UpdateResults = make(map[int]ClientUpdateResult) - } - result := ClientUpdateResult{ - MatchedCount: int64(cur.N), - } - if cur.NModified != nil { - result.ModifiedCount = int64(*cur.NModified) - } - if cur.Upserted != nil { - result.UpsertedID = (*cur.Upserted).ID - } - bw.result.UpdateResults[int(cur.Idx)] = result - if err := cur.extractError(); err != nil { - err.Raw = raw - bw.writeErrors[int(cur.Idx)] = *err - if bw.ordered != nil && *bw.ordered { - return bw.formException() - } - } - return nil -} - -func (bw *clientBulkWrite) formException() ClientBulkWriteException { - return ClientBulkWriteException{ - WriteConcernErrors: bw.writeConcernErrors, - WriteErrors: bw.writeErrors, - PartialResult: &bw.result, - } -} diff --git a/x/mongo/driver/batch_cursor.go b/x/mongo/driver/batch_cursor.go index db9e24fe48..fcc3895589 100644 --- a/x/mongo/driver/batch_cursor.go +++ b/x/mongo/driver/batch_cursor.go @@ -71,25 +71,29 @@ type CursorResponse struct { postBatchResumeToken bsoncore.Document } -// NewCursorResponse constructs a cursor response from the given response and -// server. If the provided database response does not contain a cursor, it -// returns ErrNoCursor. -// -// NewCursorResponse can be used within the ProcessResponse method for an operation. -func NewCursorResponse(info ResponseInfo) (CursorResponse, error) { - response := info.ServerResponse +// ExtractCursorDocument retrieves cursor document from a database response. If the +// provided response does not contain a cursor, it returns ErrNoCursor. +func ExtractCursorDocument(response bsoncore.Document) (bsoncore.Document, error) { cur, err := response.LookupErr("cursor") if errors.Is(err, bsoncore.ErrElementNotFound) { - return CursorResponse{}, ErrNoCursor + return nil, ErrNoCursor } if err != nil { - return CursorResponse{}, fmt.Errorf("error getting cursor from database response: %w", err) + return nil, fmt.Errorf("error getting cursor from database response: %w", err) } curDoc, ok := cur.DocumentOK() if !ok { - return CursorResponse{}, fmt.Errorf("cursor should be an embedded document but is BSON type %s", cur.Type) + return nil, fmt.Errorf("cursor should be an embedded document but is BSON type %s", cur.Type) } - elems, err := curDoc.Elements() + return curDoc, nil +} + +// NewCursorResponse constructs a cursor response from the given cursor document +// extracted from a database response. +// +// NewCursorResponse can be used within the ProcessResponse method for an operation. +func NewCursorResponse(response bsoncore.Document, info ResponseInfo) (CursorResponse, error) { + elems, err := response.Elements() if err != nil { return CursorResponse{}, fmt.Errorf("error getting elements from cursor: %w", err) } @@ -115,15 +119,17 @@ func NewCursorResponse(info ResponseInfo) (CursorResponse, error) { curresp.Database = database curresp.Collection = collection case "id": - curresp.ID, ok = elem.Value().Int64OK() + id, ok := elem.Value().Int64OK() if !ok { return CursorResponse{}, fmt.Errorf("id should be an int64 but it is a BSON %s", elem.Value().Type) } + curresp.ID = id case "postBatchResumeToken": - curresp.postBatchResumeToken, ok = elem.Value().DocumentOK() + token, ok := elem.Value().DocumentOK() if !ok { return CursorResponse{}, fmt.Errorf("post batch resume token should be a document but it is a BSON %s", elem.Value().Type) } + curresp.postBatchResumeToken = token } } @@ -393,8 +399,8 @@ func (bc *BatchCursor) getMore(ctx context.Context) { }, Database: bc.database, Deployment: bc.getOperationDeployment(), - ProcessResponseFn: func(_ context.Context, info ResponseInfo) error { - response := info.ServerResponse + ProcessResponseFn: func(_ context.Context, response bsoncore.Document, info ResponseInfo) error { + // response := info.ServerResponse id, ok := response.Lookup("cursor", "id").Int64OK() if !ok { return fmt.Errorf("cursor.id should be an int64 but is a BSON %s", response.Lookup("cursor", "id").Type) diff --git a/x/mongo/driver/batches.go b/x/mongo/driver/batches.go index c6b17ceb08..fbcd169a9f 100644 --- a/x/mongo/driver/batches.go +++ b/x/mongo/driver/batches.go @@ -7,66 +7,99 @@ package driver import ( - "errors" + "io" + "strconv" "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" + "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" ) -// ErrDocumentTooLarge occurs when a document that is larger than the maximum size accepted by a -// server is passed to an insert command. -var ErrDocumentTooLarge = errors.New("an inserted document is too large") - // Batches contains the necessary information to batch split an operation. This is only used for write // operations. type Batches struct { Identifier string Documents []bsoncore.Document - Current []bsoncore.Document Ordered *bool -} -// ClearBatch clears the Current batch. This must be called before AdvanceBatch will advance to the -// next batch. -func (b *Batches) ClearBatch() { b.Current = b.Current[:0] } - -// AdvanceBatch splits the next batch using maxCount and targetBatchSize. This method will do nothing if -// the current batch has not been cleared. We do this so that when this is called during execute we -// can call it without first needing to check if we already have a batch, which makes the code -// simpler and makes retrying easier. -// The maxDocSize parameter is used to check that any one document is not too large. If the first document is bigger -// than targetBatchSize but smaller than maxDocSize, a batch of size 1 containing that document will be created. -func (b *Batches) AdvanceBatch(maxCount, targetBatchSize, maxDocSize int) error { - if len(b.Current) > 0 { - return nil - } + offset int +} - if maxCount <= 0 { - maxCount = 1 +func (b *Batches) AppendBatchSequence(dst []byte, maxCount, maxDocSize, totalSize int) (int, []byte, error) { + if b.End() { + return 0, dst, io.EOF } - - splitAfter := 0 - size := 0 - for i, doc := range b.Documents { - if i == maxCount { + l := len(dst) + var idx int32 + dst = wiremessage.AppendMsgSectionType(dst, wiremessage.DocumentSequence) + idx, dst = bsoncore.ReserveLength(dst) + dst = append(dst, b.Identifier...) + dst = append(dst, 0x00) + size := len(dst) - l + var n int + for i := b.offset; i < len(b.Documents); i++ { + if n == maxCount { break } + doc := b.Documents[i] if len(doc) > maxDocSize { - return ErrDocumentTooLarge + break } - if size+len(doc) > targetBatchSize { + size += len(doc) + if size >= totalSize { break } + dst = append(dst, doc...) + n++ + } + if n == 0 { + return 0, dst[:l], nil + } + dst = bsoncore.UpdateLength(dst, idx, int32(len(dst[idx:]))) + return n, dst, nil +} +func (b *Batches) AppendBatchArray(dst []byte, maxCount, maxDocSize, totalSize int) (int, []byte, error) { + if b.End() { + return 0, dst, io.EOF + } + l := len(dst) + aidx, dst := bsoncore.AppendArrayElementStart(dst, b.Identifier) + size := len(dst) - l + var n int + for i := b.offset; i < len(b.Documents); i++ { + if n == maxCount { + break + } + doc := b.Documents[i] + if len(doc) > maxDocSize { + break + } size += len(doc) - splitAfter++ + if size >= totalSize { + break + } + dst = bsoncore.AppendDocumentElement(dst, strconv.Itoa(n), doc) + n++ } - - // if there are no documents, take the first one. - // this can happen if there is a document that is smaller than maxDocSize but greater than targetBatchSize. - if splitAfter == 0 { - splitAfter = 1 + if n == 0 { + return 0, dst[:l], nil } + var err error + dst, err = bsoncore.AppendArrayEnd(dst, aidx) + if err != nil { + return 0, nil, err + } + return n, dst, nil +} + +func (b *Batches) IsOrdered() *bool { + return b.Ordered +} + +func (b *Batches) AdvanceBatches(n int) { + b.offset += n +} - b.Current, b.Documents = b.Documents[:splitAfter], b.Documents[splitAfter:] - return nil +func (b *Batches) End() bool { + return len(b.Documents) <= b.offset } diff --git a/x/mongo/driver/batches_test.go b/x/mongo/driver/batches_test.go index a72813833a..c4b64eee78 100644 --- a/x/mongo/driver/batches_test.go +++ b/x/mongo/driver/batches_test.go @@ -6,110 +6,105 @@ package driver -import ( - "testing" - - "github.com/google/go-cmp/cmp" - "go.mongodb.org/mongo-driver/internal/assert" - "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" -) - +/* func TestBatches(t *testing.T) { - t.Run("ClearBatch", func(t *testing.T) { - batches := &Batches{Identifier: "documents", Current: make([]bsoncore.Document, 2, 10)} - if len(batches.Current) != 2 { - t.Fatalf("Length of current batch should be 2, but is %d", len(batches.Current)) - } - batches.ClearBatch() - if len(batches.Current) != 0 { - t.Fatalf("Length of current batch should be 0, but is %d", len(batches.Current)) - } - }) - t.Run("AdvanceBatch", func(t *testing.T) { - documents := make([]bsoncore.Document, 0) - for i := 0; i < 5; i++ { - doc := make(bsoncore.Document, 100) - documents = append(documents, doc) - } - - testCases := []struct { - name string - batches *Batches - maxCount int - targetBatchSize int - maxDocSize int - err error - want *Batches - }{ - { - "current batch non-zero", - &Batches{Current: make([]bsoncore.Document, 2, 10)}, - 0, 0, 0, nil, - &Batches{Current: make([]bsoncore.Document, 2, 10)}, - }, - { - // all of the documents in the batch fit in targetBatchSize so the batch is created successfully - "documents fit in targetBatchSize", - &Batches{Documents: documents}, - 10, 600, 1000, nil, - &Batches{Documents: documents[:0], Current: documents[0:]}, - }, - { - // the first doc is bigger than targetBatchSize but smaller than maxDocSize so it is taken alone - "first document larger than targetBatchSize, smaller than maxDocSize", - &Batches{Documents: documents}, - 10, 5, 100, nil, - &Batches{Documents: documents[1:], Current: documents[:1]}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - err := tc.batches.AdvanceBatch(tc.maxCount, tc.targetBatchSize, tc.maxDocSize) - if !cmp.Equal(err, tc.err, cmp.Comparer(compareErrors)) { - t.Errorf("Errors do not match. got %v; want %v", err, tc.err) - } - if !cmp.Equal(tc.batches, tc.want) { - t.Errorf("Batches is not in correct state after AdvanceBatch. got %v; want %v", tc.batches, tc.want) - } - }) - } - - t.Run("middle document larger than targetBatchSize, smaller than maxDocSize", func(t *testing.T) { - // a batch is made but one document is too big, so everything before it is taken. - // on the second call to AdvanceBatch, only the large document is taken + t.Run("ClearBatch", func(t *testing.T) { + batches := &Batches{Identifier: "documents", Current: make([]bsoncore.Document, 2, 10)} + if len(batches.Current) != 2 { + t.Fatalf("Length of current batch should be 2, but is %d", len(batches.Current)) + } + batches.ClearBatch() + if len(batches.Current) != 0 { + t.Fatalf("Length of current batch should be 0, but is %d", len(batches.Current)) + } + }) - middleLargeDoc := make([]bsoncore.Document, 0) + t.Run("AdvanceBatch", func(t *testing.T) { + documents := make([]bsoncore.Document, 0) for i := 0; i < 5; i++ { doc := make(bsoncore.Document, 100) - middleLargeDoc = append(middleLargeDoc, doc) + documents = append(documents, doc) } - largeDoc := make(bsoncore.Document, 900) - middleLargeDoc[2] = largeDoc - batches := &Batches{Documents: middleLargeDoc} - maxCount := 10 - targetSize := 600 - maxDocSize := 1000 - // first batch should take first 2 docs (size 100 each) - err := batches.AdvanceBatch(maxCount, targetSize, maxDocSize) - assert.Nil(t, err, "AdvanceBatch error: %v", err) - want := &Batches{Current: middleLargeDoc[:2], Documents: middleLargeDoc[2:]} - assert.Equal(t, want, batches, "expected batches %v, got %v", want, batches) + testCases := []struct { + name string + batches *Batches + maxCount int + targetBatchSize int + maxDocSize int + err error + want *Batches + }{ + { + "current batch non-zero", + &Batches{Current: make([]bsoncore.Document, 2, 10)}, + 0, 0, 0, nil, + &Batches{Current: make([]bsoncore.Document, 2, 10)}, + }, + { + // all of the documents in the batch fit in targetBatchSize so the batch is created successfully + "documents fit in targetBatchSize", + &Batches{Documents: documents}, + 10, 600, 1000, nil, + &Batches{Documents: documents[:0], Current: documents[0:]}, + }, + { + // the first doc is bigger than targetBatchSize but smaller than maxDocSize so it is taken alone + "first document larger than targetBatchSize, smaller than maxDocSize", + &Batches{Documents: documents}, + 10, 5, 100, nil, + &Batches{Documents: documents[1:], Current: documents[:1]}, + }, + } - // second batch should take single large doc (size 900) - batches.ClearBatch() - err = batches.AdvanceBatch(maxCount, targetSize, maxDocSize) - assert.Nil(t, err, "AdvanceBatch error: %v", err) - want = &Batches{Current: middleLargeDoc[2:3], Documents: middleLargeDoc[3:]} - assert.Equal(t, want, batches, "expected batches %v, got %v", want, batches) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.batches.AdvanceBatch(tc.maxCount, tc.targetBatchSize, tc.maxDocSize) + if !cmp.Equal(err, tc.err, cmp.Comparer(compareErrors)) { + t.Errorf("Errors do not match. got %v; want %v", err, tc.err) + } + if !cmp.Equal(tc.batches, tc.want) { + t.Errorf("Batches is not in correct state after AdvanceBatch. got %v; want %v", tc.batches, tc.want) + } + }) + } - // last batch should take last 2 docs (size 100 each) - batches.ClearBatch() - err = batches.AdvanceBatch(maxCount, targetSize, maxDocSize) - assert.Nil(t, err, "AdvanceBatch error: %v", err) - want = &Batches{Current: middleLargeDoc[3:], Documents: middleLargeDoc[:0]} - assert.Equal(t, want, batches, "expected batches %v, got %v", want, batches) + t.Run("middle document larger than targetBatchSize, smaller than maxDocSize", func(t *testing.T) { + // a batch is made but one document is too big, so everything before it is taken. + // on the second call to AdvanceBatch, only the large document is taken + + middleLargeDoc := make([]bsoncore.Document, 0) + for i := 0; i < 5; i++ { + doc := make(bsoncore.Document, 100) + middleLargeDoc = append(middleLargeDoc, doc) + } + largeDoc := make(bsoncore.Document, 900) + middleLargeDoc[2] = largeDoc + batches := &Batches{Documents: middleLargeDoc} + maxCount := 10 + targetSize := 600 + maxDocSize := 1000 + + // first batch should take first 2 docs (size 100 each) + err := batches.AdvanceBatch(maxCount, targetSize, maxDocSize) + assert.Nil(t, err, "AdvanceBatch error: %v", err) + want := &Batches{Current: middleLargeDoc[:2], Documents: middleLargeDoc[2:]} + assert.Equal(t, want, batches, "expected batches %v, got %v", want, batches) + + // second batch should take single large doc (size 900) + batches.ClearBatch() + err = batches.AdvanceBatch(maxCount, targetSize, maxDocSize) + assert.Nil(t, err, "AdvanceBatch error: %v", err) + want = &Batches{Current: middleLargeDoc[2:3], Documents: middleLargeDoc[3:]} + assert.Equal(t, want, batches, "expected batches %v, got %v", want, batches) + + // last batch should take last 2 docs (size 100 each) + batches.ClearBatch() + err = batches.AdvanceBatch(maxCount, targetSize, maxDocSize) + assert.Nil(t, err, "AdvanceBatch error: %v", err) + want = &Batches{Current: middleLargeDoc[3:], Documents: middleLargeDoc[:0]} + assert.Equal(t, want, batches, "expected batches %v, got %v", want, batches) + }) }) - }) } +*/ diff --git a/x/mongo/driver/operation.go b/x/mongo/driver/operation.go index 4f45a78ce4..0c98eabb60 100644 --- a/x/mongo/driver/operation.go +++ b/x/mongo/driver/operation.go @@ -47,13 +47,16 @@ var ( ErrReplyDocumentMismatch = errors.New("number of documents returned does not match numberReturned field") // ErrNonPrimaryReadPref is returned when a read is attempted in a transaction with a non-primary read preference. ErrNonPrimaryReadPref = errors.New("read preference in a transaction must be primary") + // ErrDocumentTooLarge occurs when a document that is larger than the maximum size accepted by a + // server is passed to an insert command. + ErrDocumentTooLarge = errors.New("an inserted document is too large") // errDatabaseNameEmpty occurs when a database name is not provided. errDatabaseNameEmpty = errors.New("database name cannot be empty") ) const ( // maximum BSON object size when client side encryption is enabled - cryptMaxBsonObjectSize uint32 = 2097152 + cryptMaxBsonObjectSize int = 2097152 // minimum wire version necessary to use automatic encryption cryptMinWireVersion int32 = 8 // minimum wire version necessary to use read snapshots @@ -92,16 +95,17 @@ type opReply struct { // startedInformation keeps track of all of the information necessary for monitoring started events. type startedInformation struct { - cmd bsoncore.Document - requestID int32 - cmdName string - documentSequenceIncluded bool - connID string - driverConnectionID uint64 // TODO(GODRIVER-2824): change type to int64. - serverConnID *int64 - redacted bool - serviceID *primitive.ObjectID - serverAddress address.Address + cmd bsoncore.Document + requestID int32 + cmdName string + documentSequence []byte + processedBatches int + connID string + driverConnectionID uint64 // TODO(GODRIVER-2824): change type to int64. + serverConnID *int64 + redacted bool + serviceID *primitive.ObjectID + serverAddress address.Address } // finishedInformation keeps track of all of the information necessary for monitoring success and failure events. @@ -151,7 +155,6 @@ func (info finishedInformation) success() bool { // ResponseInfo contains the context required to parse a server response. type ResponseInfo struct { - ServerResponse bsoncore.Document Server Server Connection Connection ConnectionDescription description.Server @@ -159,7 +162,7 @@ type ResponseInfo struct { Error error } -func redactStartedInformationCmd(info startedInformation, batches []Batches) bson.Raw { +func redactStartedInformationCmd(info startedInformation) bson.Raw { var cmdCopy bson.Raw // Make a copy of the command. Redact if the command is security @@ -169,13 +172,10 @@ func redactStartedInformationCmd(info startedInformation, batches []Batches) bso cmdCopy = make([]byte, len(info.cmd)) copy(cmdCopy, info.cmd) - if info.documentSequenceIncluded { + if len(info.documentSequence) > 0 { // remove 0 byte at end cmdCopy = cmdCopy[:len(info.cmd)-1] - for i := 0; i < len(batches); i++ { - cmdCopy = addBatchArray(cmdCopy, batches[i].Identifier, batches[i].Current) - } - + cmdCopy = append(cmdCopy, info.documentSequence...) // add back 0 byte and update length cmdCopy, _ = bsoncore.AppendDocumentEnd(cmdCopy, 0) } @@ -221,7 +221,7 @@ type Operation struct { // ProcessResponseFn is called after a response to the command is returned. The server is // provided for types like Cursor that are required to run subsequent commands using the same // server. - ProcessResponseFn func(context.Context, ResponseInfo) error + ProcessResponseFn func(context.Context, bsoncore.Document, ResponseInfo) error // Selector is the server selector that's used during both initial server selection and // subsequent selection for retries. Depending on the Deployment implementation, the @@ -279,7 +279,13 @@ type Operation struct { // has more documents than can fit in a single command. This should only be specified for // commands that are batch compatible. For more information, please refer to the definition of // Batches. - Batches []Batches + Batches interface { + AppendBatchSequence(dst []byte, maxCount int, maxDocSize int, totalSize int) (int, []byte, error) + AppendBatchArray(dst []byte, maxCount int, maxDocSize int, totalSize int) (int, []byte, error) + IsOrdered() *bool + AdvanceBatches(n int) + End() bool + } // Legacy sets the legacy type for this operation. There are only 3 types that require legacy // support: find, getMore, and killCursors. For more information about LegacyOperationKind, @@ -562,7 +568,6 @@ func (op Operation) Execute(ctx context.Context) error { var operationErr WriteCommandError var prevErr error var prevIndefiniteErr error - batching := len(op.Batches) > 0 retrySupported := false first := true currIndex := 0 @@ -714,26 +719,6 @@ func (op Operation) Execute(ctx context.Context) error { desc := description.SelectedServer{Server: conn.Description(), Kind: op.Deployment.Kind()} - if batching { - targetBatchSize := desc.MaxDocumentSize - maxDocSize := desc.MaxDocumentSize - if op.shouldEncrypt() { - // For client-side encryption, we want the batch to be split at 2 MiB instead of 16MiB. - // If there's only one document in the batch, it can be up to 16MiB, so we set target batch size to - // 2MiB but max document size to 16MiB. This will allow the AdvanceBatch call to create a batch - // with a single large document. - targetBatchSize = cryptMaxBsonObjectSize - } - - for i := 0; i < len(op.Batches); i++ { - err = op.Batches[i].AdvanceBatch(int(desc.MaxBatchCount), int(targetBatchSize), int(maxDocSize)) - if err != nil { - // TODO(GODRIVER-982): Should we also be returning operationErr? - return err - } - } - } - var startedInfo startedInformation *wm, startedInfo, err = op.createWireMessage(ctx, maxTimeMS, (*wm)[:0], desc, conn, requestID) @@ -875,20 +860,19 @@ func (op Operation) Execute(ctx context.Context) error { // If the operation isn't being retried, process the response if op.ProcessResponseFn != nil { info := ResponseInfo{ - ServerResponse: res, Server: srvr, Connection: conn, ConnectionDescription: desc.Server, CurrentIndex: currIndex, Error: tt, } - _ = op.ProcessResponseFn(ctx, info) + _ = op.ProcessResponseFn(ctx, res, info) // if perr != nil { // return perr // } } - if batching && len(tt.WriteErrors) > 0 && currIndex > 0 { + if op.Batches != nil && len(tt.WriteErrors) > 0 && currIndex > 0 { for i := range tt.WriteErrors { tt.WriteErrors[i].Index += int64(currIndex) } @@ -896,16 +880,11 @@ func (op Operation) Execute(ctx context.Context) error { // If batching is enabled and either ordered is the default (which is true) or // explicitly set to true and we have write errors, return the errors. - var ordered bool - for i := 0; i < len(op.Batches); i++ { - if op.Batches[i].Ordered == nil || *op.Batches[i].Ordered { - ordered = true - break + if op.Batches != nil && len(tt.WriteErrors) > 0 { + if isOrdered := op.Batches.IsOrdered(); isOrdered == nil || *isOrdered { + return tt } } - if batching && ordered && len(tt.WriteErrors) > 0 { - return tt - } if op.Client != nil && op.Client.Committing && tt.WriteConcernError != nil { // When running commitTransaction we return WriteConcernErrors as an Error. err := Error{ @@ -1005,14 +984,13 @@ func (op Operation) Execute(ctx context.Context) error { // If the operation isn't being retried, process the response if op.ProcessResponseFn != nil { info := ResponseInfo{ - ServerResponse: res, Server: srvr, Connection: conn, ConnectionDescription: desc.Server, CurrentIndex: currIndex, Error: tt, } - _ = op.ProcessResponseFn(ctx, info) + _ = op.ProcessResponseFn(ctx, res, info) // if perr != nil { // return perr // } @@ -1029,14 +1007,13 @@ func (op Operation) Execute(ctx context.Context) error { } if op.ProcessResponseFn != nil { info := ResponseInfo{ - ServerResponse: res, Server: srvr, Connection: conn, ConnectionDescription: desc.Server, CurrentIndex: currIndex, Error: tt, } - perr := op.ProcessResponseFn(ctx, info) + perr := op.ProcessResponseFn(ctx, res, info) if perr != nil { fmt.Println("op", perr) return perr @@ -1045,14 +1022,13 @@ func (op Operation) Execute(ctx context.Context) error { default: if op.ProcessResponseFn != nil { info := ResponseInfo{ - ServerResponse: res, Server: srvr, Connection: conn, ConnectionDescription: desc.Server, CurrentIndex: currIndex, Error: tt, } - _ = op.ProcessResponseFn(ctx, info) + _ = op.ProcessResponseFn(ctx, res, info) } return err } @@ -1060,13 +1036,7 @@ func (op Operation) Execute(ctx context.Context) error { // If we're batching and there are batches remaining, advance to the next batch. This isn't // a retry, so increment the transaction number, reset the retries number, and don't set // server or connection to nil to continue using the same connection. - var advancing []int - for i := 0; i < len(op.Batches); i++ { - if len(op.Batches[i].Documents) > 0 { - advancing = append(advancing, i) - } - } - if batching && len(advancing) > 0 { + if op.Batches != nil { // If retries are supported for the current operation on the current server description, // the session isn't nil, and client retries are enabled, increment the txn number. // Calling IncrementTxnNumber() for server descriptions or topologies that do not @@ -1081,11 +1051,11 @@ func (op Operation) Execute(ctx context.Context) error { retries = 1 } } - for _, i := range advancing { - currIndex += len(op.Batches[i].Current) - op.Batches[i].ClearBatch() + currIndex += startedInfo.processedBatches + op.Batches.AdvanceBatches(startedInfo.processedBatches) + if !op.Batches.End() { + continue } - continue } break } @@ -1241,28 +1211,13 @@ func (Operation) decompressWireMessage(wm []byte) (wiremessage.OpCode, []byte, e return opcode, uncompressed, nil } -func addBatchArray(dst []byte, identifier string, docs []bsoncore.Document) []byte { - if len(docs) == 0 { - return dst - } - aidx, dst := bsoncore.AppendArrayElementStart(dst, identifier) - for i, doc := range docs { - dst = bsoncore.AppendDocumentElement(dst, strconv.Itoa(i), doc) - } - dst, _ = bsoncore.AppendArrayEnd(dst, aidx) - return dst -} - func (op Operation) createLegacyHandshakeWireMessage( maxTimeMS uint64, dst []byte, desc description.SelectedServer, -) ([]byte, startedInformation, error) { - var info startedInformation + cmdFn func([]byte, description.SelectedServer) ([]byte, error), +) ([]byte, []byte, error) { flags := op.secondaryOK(desc) - var wmindex int32 - info.requestID = wiremessage.NextRequestID() - wmindex, dst = wiremessage.AppendHeaderStart(dst, info.requestID, 0, wiremessage.OpQuery) dst = wiremessage.AppendQueryFlags(dst, flags) dollarCmd := [...]byte{'.', '$', 'c', 'm', 'd'} @@ -1277,35 +1232,31 @@ func (op Operation) createLegacyHandshakeWireMessage( wrapper := int32(-1) rp, err := op.createReadPref(desc, true) if err != nil { - return dst, info, err + return dst, nil, err } if len(rp) > 0 { wrapper, dst = bsoncore.AppendDocumentStart(dst) dst = bsoncore.AppendHeader(dst, bsontype.EmbeddedDocument, "$query") } idx, dst := bsoncore.AppendDocumentStart(dst) - dst, err = op.CommandFn(dst, desc) + dst, err = cmdFn(dst, desc) if err != nil { - return dst, info, err - } - - for i := 0; i < len(op.Batches); i++ { - dst = addBatchArray(dst, op.Batches[i].Identifier, op.Batches[i].Current) + return dst, nil, err } dst, err = op.addReadConcern(dst, desc) if err != nil { - return dst, info, err + return dst, nil, err } dst, err = op.addWriteConcern(dst, desc) if err != nil { - return dst, info, err + return dst, nil, err } dst, err = op.addSession(dst, desc) if err != nil { - return dst, info, err + return dst, nil, err } dst = op.addClusterTime(dst, desc) @@ -1317,40 +1268,32 @@ func (op Operation) createLegacyHandshakeWireMessage( } dst, _ = bsoncore.AppendDocumentEnd(dst, idx) - // Command monitoring only reports the document inside $query - info.cmd = dst[idx:] if len(rp) > 0 { var err error dst = bsoncore.AppendDocumentElement(dst, "$readPreference", rp) dst, err = bsoncore.AppendDocumentEnd(dst, wrapper) if err != nil { - return dst, info, err + return dst, nil, err } } - return bsoncore.UpdateLength(dst, wmindex, int32(len(dst[wmindex:]))), info, nil + return dst, dst[idx:], nil } func (op Operation) createMsgWireMessage( - ctx context.Context, maxTimeMS uint64, dst []byte, desc description.SelectedServer, conn Connection, - requestID int32, -) ([]byte, startedInformation, error) { - var info startedInformation + cmdFn func([]byte, description.SelectedServer) ([]byte, error), +) ([]byte, []byte, error) { var flags wiremessage.MsgFlag - var wmindex int32 // We set the MoreToCome bit if we have a write concern, it's unacknowledged, and we either // aren't batching or we are encoding the last batch. var batching bool - for i := 0; i < len(op.Batches); i++ { - if len(op.Batches[i].Documents) > 0 { - batching = true - break - } + if op.Batches != nil && !op.Batches.End() { + batching = true } if op.WriteConcern != nil && !writeconcern.AckWrite(op.WriteConcern) && !batching { flags = wiremessage.MoreToCome @@ -1361,29 +1304,28 @@ func (op Operation) createMsgWireMessage( flags |= wiremessage.ExhaustAllowed } - info.requestID = requestID - wmindex, dst = wiremessage.AppendHeaderStart(dst, info.requestID, 0, wiremessage.OpMsg) dst = wiremessage.AppendMsgFlags(dst, flags) // Body dst = wiremessage.AppendMsgSectionType(dst, wiremessage.SingleDocument) idx, dst := bsoncore.AppendDocumentStart(dst) - dst, err := op.addCommandFields(ctx, dst, desc) + var err error + dst, err = cmdFn(dst, desc) if err != nil { - return dst, info, err + return dst, nil, err } dst, err = op.addReadConcern(dst, desc) if err != nil { - return dst, info, err + return dst, nil, err } dst, err = op.addWriteConcern(dst, desc) if err != nil { - return dst, info, err + return dst, nil, err } dst, err = op.addSession(dst, desc) if err != nil { - return dst, info, err + return dst, nil, err } dst = op.addClusterTime(dst, desc) @@ -1397,39 +1339,15 @@ func (op Operation) createMsgWireMessage( dst = bsoncore.AppendStringElement(dst, "$db", op.Database) rp, err := op.createReadPref(desc, false) if err != nil { - return dst, info, err + return dst, nil, err } if len(rp) > 0 { dst = bsoncore.AppendDocumentElement(dst, "$readPreference", rp) } dst, _ = bsoncore.AppendDocumentEnd(dst, idx) - // The command document for monitoring shouldn't include the type 1 payload as a document sequence - info.cmd = dst[idx:] - - // add batch as a document sequence if auto encryption is not enabled - // if auto encryption is enabled, the batch will already be an array in the command document - if !op.shouldEncrypt() { - for i := 0; i < len(op.Batches); i++ { - if len(op.Batches[i].Current) == 0 { - continue - } - info.documentSequenceIncluded = true - dst = wiremessage.AppendMsgSectionType(dst, wiremessage.DocumentSequence) - idx, dst = bsoncore.ReserveLength(dst) - - dst = append(dst, op.Batches[i].Identifier...) - dst = append(dst, 0x00) - - for _, doc := range op.Batches[i].Current { - dst = append(dst, doc...) - } - dst = bsoncore.UpdateLength(dst, idx, int32(len(dst[idx:]))) - } - } - - return bsoncore.UpdateLength(dst, wmindex, int32(len(dst[wmindex:]))), info, nil + return dst, dst[idx:], nil } // isLegacyHandshake returns True if the operation is the first message of @@ -1448,45 +1366,121 @@ func (op Operation) createWireMessage( conn Connection, requestID int32, ) ([]byte, startedInformation, error) { - if isLegacyHandshake(op, desc) { - return op.createLegacyHandshakeWireMessage(maxTimeMS, dst, desc) - } + var info startedInformation + var wmindex int32 + var err error - return op.createMsgWireMessage(ctx, maxTimeMS, dst, desc, conn, requestID) + isLegacy := isLegacyHandshake(op, desc) + shouldEncrypt := op.shouldEncrypt() + if !isLegacy && !shouldEncrypt { + wmindex, dst = wiremessage.AppendHeaderStart(dst, requestID, 0, wiremessage.OpMsg) + dst, info.cmd, err = op.createMsgWireMessage(maxTimeMS, dst, desc, conn, op.CommandFn) + if err == nil && op.Batches != nil { + var processedBatches int + dsOffset := len(dst) + processedBatches, dst, err = op.Batches.AppendBatchSequence(dst, int(desc.MaxBatchCount), int(desc.MaxDocumentSize), int(desc.MaxMessageSize)) + if err == nil { + info.processedBatches = processedBatches + info.documentSequence = make([]byte, 0) + for b := dst[dsOffset:]; len(b) > 0; /* nothing */ { + var seq []byte + var ok bool + seq, b, ok = wiremessage.DocumentSequenceToArray(b) + if !ok { + break + } + info.documentSequence = append(info.documentSequence, seq...) + } + } + } + } else if shouldEncrypt { + if desc.WireVersion.Max < cryptMinWireVersion { + return dst, info, errors.New("auto-encryption requires a MongoDB version of 4.2") + } + cmdFn := func(dst []byte, desc description.SelectedServer) ([]byte, error) { + // create temporary command document + var cmdDst []byte + info.processedBatches, cmdDst, err = op.addEncryptCommandFields(nil, desc) + if err != nil { + return nil, err + } + // encrypt the command + encrypted, err := op.Crypt.Encrypt(ctx, op.Database, cmdDst) + if err != nil { + return nil, err + } + // append encrypted command to original destination, removing the first 4 bytes (length) and final byte (terminator) + dst = append(dst, encrypted[4:len(encrypted)-1]...) + return dst, nil + } + wmindex, dst = wiremessage.AppendHeaderStart(dst, requestID, 0, wiremessage.OpMsg) + dst, info.cmd, err = op.createMsgWireMessage(maxTimeMS, dst, desc, conn, cmdFn) + } else { // isLegacy + cmdFn := func(dst []byte, desc description.SelectedServer) ([]byte, error) { + info.processedBatches, dst, err = op.addLegacyCommandFields(dst, desc) + return dst, err + } + requestID := wiremessage.NextRequestID() + wmindex, dst = wiremessage.AppendHeaderStart(dst, requestID, 0, wiremessage.OpQuery) + dst, info.cmd, err = op.createLegacyHandshakeWireMessage(maxTimeMS, dst, desc, cmdFn) + } + if err != nil { + return nil, info, err + } + info.requestID = requestID + return bsoncore.UpdateLength(dst, wmindex, int32(len(dst[wmindex:]))), info, nil } -// addCommandFields adds the fields for a command to the wire message in dst. This assumes that the start of the document -// has already been added and does not add the final 0 byte. -func (op Operation) addCommandFields(ctx context.Context, dst []byte, desc description.SelectedServer) ([]byte, error) { - if !op.shouldEncrypt() { - return op.CommandFn(dst, desc) +func (op Operation) addEncryptCommandFields(dst []byte, desc description.SelectedServer) (int, []byte, error) { + var idx int32 + idx, dst = bsoncore.AppendDocumentStart(dst) + var err error + dst, err = op.CommandFn(dst, desc) + if err != nil { + return 0, nil, err } - - if desc.WireVersion.Max < cryptMinWireVersion { - return dst, errors.New("auto-encryption requires a MongoDB version of 4.2") + var n int + if op.Batches != nil { + maxBatchCount := int(desc.MaxBatchCount) + maxDocumentSize := int(desc.MaxDocumentSize) + if maxBatchCount > 1 { + n, dst, err = op.Batches.AppendBatchArray(dst, maxBatchCount, cryptMaxBsonObjectSize, maxDocumentSize) + if err != nil { + return 0, nil, err + } + } + if n == 0 { + n, dst, err = op.Batches.AppendBatchArray(dst, 1, maxDocumentSize, maxDocumentSize) + if err != nil { + return 0, nil, err + } + if n == 0 { + return 0, nil, ErrDocumentTooLarge + } + } + } + dst, err = bsoncore.AppendDocumentEnd(dst, idx) + if err != nil { + return 0, nil, err } + return n, dst, nil +} - // create temporary command document - cidx, cmdDst := bsoncore.AppendDocumentStart(nil) +func (op Operation) addLegacyCommandFields(dst []byte, desc description.SelectedServer) (int, []byte, error) { var err error - cmdDst, err = op.CommandFn(cmdDst, desc) + dst, err = op.CommandFn(dst, desc) if err != nil { - return dst, err + return 0, nil, err } - // use a BSON array instead of a type 1 payload because mongocryptd will convert to arrays regardless - for i := 0; i < len(op.Batches); i++ { - cmdDst = addBatchArray(cmdDst, op.Batches[i].Identifier, op.Batches[i].Current) + if op.Batches == nil { + return 0, dst, nil } - cmdDst, _ = bsoncore.AppendDocumentEnd(cmdDst, cidx) - - // encrypt the command - encrypted, err := op.Crypt.Encrypt(ctx, op.Database, cmdDst) + var n int + n, dst, err = op.Batches.AppendBatchArray(dst, int(desc.MaxBatchCount), int(desc.MaxDocumentSize), int(desc.MaxDocumentSize)) if err != nil { - return dst, err + return 0, nil, err } - // append encrypted command to original destination, removing the first 4 bytes (length) and final byte (terminator) - dst = append(dst, encrypted[4:len(encrypted)-1]...) - return dst, nil + return n, dst, nil } // addServerAPI adds the relevant fields for server API specification to the wire message in dst. @@ -2022,7 +2016,7 @@ func (op Operation) publishStartedEvent(ctx context.Context, info startedInforma if op.canLogCommandMessage() { host, port, _ := net.SplitHostPort(info.serverAddress.String()) - redactedCmd := redactStartedInformationCmd(info, op.Batches).String() + redactedCmd := redactStartedInformationCmd(info).String() formattedCmd := logger.FormatMessage(redactedCmd, op.Logger.MaxDocumentLength) op.Logger.Print(logger.LevelDebug, @@ -2045,7 +2039,7 @@ func (op Operation) publishStartedEvent(ctx context.Context, info startedInforma if op.canPublishStartedEvent() { started := &event.CommandStartedEvent{ - Command: redactStartedInformationCmd(info, op.Batches), + Command: redactStartedInformationCmd(info), DatabaseName: op.Database, CommandName: info.cmdName, RequestID: int64(info.requestID), diff --git a/x/mongo/driver/operation/abort_transaction.go b/x/mongo/driver/operation/abort_transaction.go index 3155233e42..c3104cdb90 100644 --- a/x/mongo/driver/operation/abort_transaction.go +++ b/x/mongo/driver/operation/abort_transaction.go @@ -41,9 +41,8 @@ func NewAbortTransaction() *AbortTransaction { return &AbortTransaction{} } -func (at *AbortTransaction) processResponse(context.Context, driver.ResponseInfo) error { - var err error - return err +func (at *AbortTransaction) processResponse(context.Context, bsoncore.Document, driver.ResponseInfo) error { + return nil } // Execute runs this operations and returns an error if the operation did not execute successfully. diff --git a/x/mongo/driver/operation/aggregate.go b/x/mongo/driver/operation/aggregate.go index 17b0c6a70e..b95a8205af 100644 --- a/x/mongo/driver/operation/aggregate.go +++ b/x/mongo/driver/operation/aggregate.go @@ -79,10 +79,12 @@ func (a *Aggregate) ResultCursorResponse() driver.CursorResponse { return a.result } -func (a *Aggregate) processResponse(_ context.Context, info driver.ResponseInfo) error { - var err error - - a.result, err = driver.NewCursorResponse(info) +func (a *Aggregate) processResponse(_ context.Context, resp bsoncore.Document, info driver.ResponseInfo) error { + curDoc, err := driver.ExtractCursorDocument(resp) + if err != nil { + return err + } + a.result, err = driver.NewCursorResponse(curDoc, info) return err } diff --git a/x/mongo/driver/operation/command.go b/x/mongo/driver/operation/command.go index d099067392..42de03a49e 100644 --- a/x/mongo/driver/operation/command.go +++ b/x/mongo/driver/operation/command.go @@ -82,11 +82,15 @@ func (c *Command) Execute(ctx context.Context) error { CommandFn: func(dst []byte, _ description.SelectedServer) ([]byte, error) { return append(dst, c.command[4:len(c.command)-1]...), nil }, - ProcessResponseFn: func(_ context.Context, info driver.ResponseInfo) error { - c.resultResponse = info.ServerResponse + ProcessResponseFn: func(_ context.Context, resp bsoncore.Document, info driver.ResponseInfo) error { + c.resultResponse = resp if c.createCursor { - cursorRes, err := driver.NewCursorResponse(info) + curDoc, err := driver.ExtractCursorDocument(resp) + if err != nil { + return err + } + cursorRes, err := driver.NewCursorResponse(curDoc, info) if err != nil { return err } diff --git a/x/mongo/driver/operation/commit_transaction.go b/x/mongo/driver/operation/commit_transaction.go index cf20d9abec..fda31da542 100644 --- a/x/mongo/driver/operation/commit_transaction.go +++ b/x/mongo/driver/operation/commit_transaction.go @@ -42,9 +42,8 @@ func NewCommitTransaction() *CommitTransaction { return &CommitTransaction{} } -func (ct *CommitTransaction) processResponse(context.Context, driver.ResponseInfo) error { - var err error - return err +func (ct *CommitTransaction) processResponse(context.Context, bsoncore.Document, driver.ResponseInfo) error { + return nil } // Execute runs this operations and returns an error if the operation did not execute successfully. diff --git a/x/mongo/driver/operation/count.go b/x/mongo/driver/operation/count.go index 8c0e30c031..7ef3f549e3 100644 --- a/x/mongo/driver/operation/count.go +++ b/x/mongo/driver/operation/count.go @@ -99,9 +99,9 @@ func NewCount() *Count { // Result returns the result of executing this operation. func (c *Count) Result() CountResult { return c.result } -func (c *Count) processResponse(_ context.Context, info driver.ResponseInfo) error { +func (c *Count) processResponse(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error { var err error - c.result, err = buildCountResult(info.ServerResponse) + c.result, err = buildCountResult(resp) return err } diff --git a/x/mongo/driver/operation/create.go b/x/mongo/driver/operation/create.go index 6a6301d6b1..73a02feb4d 100644 --- a/x/mongo/driver/operation/create.go +++ b/x/mongo/driver/operation/create.go @@ -56,7 +56,7 @@ func NewCreate(collectionName string) *Create { } } -func (c *Create) processResponse(context.Context, driver.ResponseInfo) error { +func (c *Create) processResponse(context.Context, bsoncore.Document, driver.ResponseInfo) error { return nil } diff --git a/x/mongo/driver/operation/create_indexes.go b/x/mongo/driver/operation/create_indexes.go index 64bd90940e..ae73d49e5e 100644 --- a/x/mongo/driver/operation/create_indexes.go +++ b/x/mongo/driver/operation/create_indexes.go @@ -93,9 +93,9 @@ func NewCreateIndexes(indexes bsoncore.Document) *CreateIndexes { // Result returns the result of executing this operation. func (ci *CreateIndexes) Result() CreateIndexesResult { return ci.result } -func (ci *CreateIndexes) processResponse(_ context.Context, info driver.ResponseInfo) error { +func (ci *CreateIndexes) processResponse(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error { var err error - ci.result, err = buildCreateIndexesResult(info.ServerResponse) + ci.result, err = buildCreateIndexesResult(resp) return err } diff --git a/x/mongo/driver/operation/create_search_indexes.go b/x/mongo/driver/operation/create_search_indexes.go index 90495231c7..180858b3ec 100644 --- a/x/mongo/driver/operation/create_search_indexes.go +++ b/x/mongo/driver/operation/create_search_indexes.go @@ -93,9 +93,9 @@ func NewCreateSearchIndexes(indexes bsoncore.Document) *CreateSearchIndexes { // Result returns the result of executing this operation. func (csi *CreateSearchIndexes) Result() CreateSearchIndexesResult { return csi.result } -func (csi *CreateSearchIndexes) processResponse(_ context.Context, info driver.ResponseInfo) error { +func (csi *CreateSearchIndexes) processResponse(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error { var err error - csi.result, err = buildCreateSearchIndexesResult(info.ServerResponse) + csi.result, err = buildCreateSearchIndexesResult(resp) return err } diff --git a/x/mongo/driver/operation/delete.go b/x/mongo/driver/operation/delete.go index e03047a9cf..04308cc239 100644 --- a/x/mongo/driver/operation/delete.go +++ b/x/mongo/driver/operation/delete.go @@ -81,8 +81,8 @@ func NewDelete(deletes ...bsoncore.Document) *Delete { // Result returns the result of executing this operation. func (d *Delete) Result() DeleteResult { return d.result } -func (d *Delete) processResponse(_ context.Context, info driver.ResponseInfo) error { - dr, err := buildDeleteResult(info.ServerResponse) +func (d *Delete) processResponse(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error { + dr, err := buildDeleteResult(resp) d.result.N += dr.N return err } @@ -96,12 +96,10 @@ func (d *Delete) Execute(ctx context.Context) error { return driver.Operation{ CommandFn: d.command, ProcessResponseFn: d.processResponse, - Batches: []driver.Batches{ - { - Identifier: "deletes", - Documents: d.deletes, - Ordered: d.ordered, - }, + Batches: &driver.Batches{ + Identifier: "deletes", + Documents: d.deletes, + Ordered: d.ordered, }, RetryMode: d.retry, Type: driver.Write, diff --git a/x/mongo/driver/operation/distinct.go b/x/mongo/driver/operation/distinct.go index 946aa8aa4c..882b4c558e 100644 --- a/x/mongo/driver/operation/distinct.go +++ b/x/mongo/driver/operation/distinct.go @@ -77,9 +77,9 @@ func NewDistinct(key string, query bsoncore.Document) *Distinct { // Result returns the result of executing this operation. func (d *Distinct) Result() DistinctResult { return d.result } -func (d *Distinct) processResponse(_ context.Context, info driver.ResponseInfo) error { +func (d *Distinct) processResponse(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error { var err error - d.result, err = buildDistinctResult(info.ServerResponse) + d.result, err = buildDistinctResult(resp) return err } diff --git a/x/mongo/driver/operation/drop_collection.go b/x/mongo/driver/operation/drop_collection.go index 11f4b74f82..b1c18d4083 100644 --- a/x/mongo/driver/operation/drop_collection.go +++ b/x/mongo/driver/operation/drop_collection.go @@ -79,9 +79,9 @@ func NewDropCollection() *DropCollection { // Result returns the result of executing this operation. func (dc *DropCollection) Result() DropCollectionResult { return dc.result } -func (dc *DropCollection) processResponse(_ context.Context, info driver.ResponseInfo) error { +func (dc *DropCollection) processResponse(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error { var err error - dc.result, err = buildDropCollectionResult(info.ServerResponse) + dc.result, err = buildDropCollectionResult(resp) return err } diff --git a/x/mongo/driver/operation/drop_indexes.go b/x/mongo/driver/operation/drop_indexes.go index 70932ed0fb..1171d81937 100644 --- a/x/mongo/driver/operation/drop_indexes.go +++ b/x/mongo/driver/operation/drop_indexes.go @@ -74,9 +74,9 @@ func NewDropIndexes(index any) *DropIndexes { // Result returns the result of executing this operation. func (di *DropIndexes) Result() DropIndexesResult { return di.result } -func (di *DropIndexes) processResponse(_ context.Context, info driver.ResponseInfo) error { +func (di *DropIndexes) processResponse(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error { var err error - di.result, err = buildDropIndexesResult(info.ServerResponse) + di.result, err = buildDropIndexesResult(resp) return err } diff --git a/x/mongo/driver/operation/drop_search_index.go b/x/mongo/driver/operation/drop_search_index.go index 688a6e2280..55d675a84f 100644 --- a/x/mongo/driver/operation/drop_search_index.go +++ b/x/mongo/driver/operation/drop_search_index.go @@ -69,9 +69,9 @@ func NewDropSearchIndex(index string) *DropSearchIndex { // Result returns the result of executing this operation. func (dsi *DropSearchIndex) Result() DropSearchIndexResult { return dsi.result } -func (dsi *DropSearchIndex) processResponse(_ context.Context, info driver.ResponseInfo) error { +func (dsi *DropSearchIndex) processResponse(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error { var err error - dsi.result, err = buildDropSearchIndexResult(info.ServerResponse) + dsi.result, err = buildDropSearchIndexResult(resp) return err } diff --git a/x/mongo/driver/operation/end_sessions.go b/x/mongo/driver/operation/end_sessions.go index 4ddf781683..eaf03d2ced 100644 --- a/x/mongo/driver/operation/end_sessions.go +++ b/x/mongo/driver/operation/end_sessions.go @@ -39,9 +39,8 @@ func NewEndSessions(sessionIDs bsoncore.Document) *EndSessions { } } -func (es *EndSessions) processResponse(context.Context, driver.ResponseInfo) error { - var err error - return err +func (es *EndSessions) processResponse(context.Context, bsoncore.Document, driver.ResponseInfo) error { + return nil } // Execute runs this operations and returns an error if the operation did not execute successfully. diff --git a/x/mongo/driver/operation/find.go b/x/mongo/driver/operation/find.go index fd611ebde8..e8f7640e94 100644 --- a/x/mongo/driver/operation/find.go +++ b/x/mongo/driver/operation/find.go @@ -80,9 +80,12 @@ func (f *Find) Result(opts driver.CursorOptions) (*driver.BatchCursor, error) { return driver.NewBatchCursor(f.result, f.session, f.clock, opts) } -func (f *Find) processResponse(_ context.Context, info driver.ResponseInfo) error { - var err error - f.result, err = driver.NewCursorResponse(info) +func (f *Find) processResponse(_ context.Context, resp bsoncore.Document, info driver.ResponseInfo) error { + curDoc, err := driver.ExtractCursorDocument(resp) + if err != nil { + return err + } + f.result, err = driver.NewCursorResponse(curDoc, info) return err } diff --git a/x/mongo/driver/operation/find_and_modify.go b/x/mongo/driver/operation/find_and_modify.go index 1ec799be7c..76f34b9255 100644 --- a/x/mongo/driver/operation/find_and_modify.go +++ b/x/mongo/driver/operation/find_and_modify.go @@ -114,10 +114,10 @@ func NewFindAndModify(query bsoncore.Document) *FindAndModify { // Result returns the result of executing this operation. func (fam *FindAndModify) Result() FindAndModifyResult { return fam.result } -func (fam *FindAndModify) processResponse(_ context.Context, info driver.ResponseInfo) error { +func (fam *FindAndModify) processResponse(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error { var err error - fam.result, err = buildFindAndModifyResult(info.ServerResponse) + fam.result, err = buildFindAndModifyResult(resp) return err } diff --git a/x/mongo/driver/operation/hello.go b/x/mongo/driver/operation/hello.go index 8e38dd0473..77086f8dc6 100644 --- a/x/mongo/driver/operation/hello.go +++ b/x/mongo/driver/operation/hello.go @@ -586,8 +586,8 @@ func (h *Hello) createOperation() driver.Operation { CommandFn: h.command, Database: "admin", Deployment: h.d, - ProcessResponseFn: func(_ context.Context, info driver.ResponseInfo) error { - h.res = info.ServerResponse + ProcessResponseFn: func(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error { + h.res = resp return nil }, ServerAPI: h.serverAPI, @@ -610,8 +610,8 @@ func (h *Hello) GetHandshakeInformation(ctx context.Context, _ address.Address, CommandFn: h.handshakeCommand, Deployment: deployment, Database: "admin", - ProcessResponseFn: func(_ context.Context, info driver.ResponseInfo) error { - h.res = info.ServerResponse + ProcessResponseFn: func(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error { + h.res = resp return nil }, ServerAPI: h.serverAPI, diff --git a/x/mongo/driver/operation/insert.go b/x/mongo/driver/operation/insert.go index 71817a9c42..7f7c6a5453 100644 --- a/x/mongo/driver/operation/insert.go +++ b/x/mongo/driver/operation/insert.go @@ -80,8 +80,8 @@ func NewInsert(documents ...bsoncore.Document) *Insert { // Result returns the result of executing this operation. func (i *Insert) Result() InsertResult { return i.result } -func (i *Insert) processResponse(_ context.Context, info driver.ResponseInfo) error { - ir, err := buildInsertResult(info.ServerResponse) +func (i *Insert) processResponse(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error { + ir, err := buildInsertResult(resp) i.result.N += ir.N return err } @@ -95,12 +95,10 @@ func (i *Insert) Execute(ctx context.Context) error { return driver.Operation{ CommandFn: i.command, ProcessResponseFn: i.processResponse, - Batches: []driver.Batches{ - { - Identifier: "documents", - Documents: i.documents, - Ordered: i.ordered, - }, + Batches: &driver.Batches{ + Identifier: "documents", + Documents: i.documents, + Ordered: i.ordered, }, RetryMode: i.retry, Type: driver.Write, diff --git a/x/mongo/driver/operation/list_collections.go b/x/mongo/driver/operation/list_collections.go index 927f82be48..701f7ea01e 100644 --- a/x/mongo/driver/operation/list_collections.go +++ b/x/mongo/driver/operation/list_collections.go @@ -55,9 +55,12 @@ func (lc *ListCollections) Result(opts driver.CursorOptions) (*driver.BatchCurso return driver.NewBatchCursor(lc.result, lc.session, lc.clock, opts) } -func (lc *ListCollections) processResponse(_ context.Context, info driver.ResponseInfo) error { - var err error - lc.result, err = driver.NewCursorResponse(info) +func (lc *ListCollections) processResponse(_ context.Context, resp bsoncore.Document, info driver.ResponseInfo) error { + curDoc, err := driver.ExtractCursorDocument(resp) + if err != nil { + return err + } + lc.result, err = driver.NewCursorResponse(curDoc, info) return err } diff --git a/x/mongo/driver/operation/listDatabases.go b/x/mongo/driver/operation/list_databases.go similarity index 98% rename from x/mongo/driver/operation/listDatabases.go rename to x/mongo/driver/operation/list_databases.go index 1f8719e9fc..37371d49cb 100644 --- a/x/mongo/driver/operation/listDatabases.go +++ b/x/mongo/driver/operation/list_databases.go @@ -135,10 +135,10 @@ func NewListDatabases(filter bsoncore.Document) *ListDatabases { // Result returns the result of executing this operation. func (ld *ListDatabases) Result() ListDatabasesResult { return ld.result } -func (ld *ListDatabases) processResponse(_ context.Context, info driver.ResponseInfo) error { +func (ld *ListDatabases) processResponse(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error { var err error - ld.result, err = buildListDatabasesResult(info.ServerResponse) + ld.result, err = buildListDatabasesResult(resp) return err } diff --git a/x/mongo/driver/operation/list_indexes.go b/x/mongo/driver/operation/list_indexes.go index 93cd288842..a9cf200779 100644 --- a/x/mongo/driver/operation/list_indexes.go +++ b/x/mongo/driver/operation/list_indexes.go @@ -54,10 +54,12 @@ func (li *ListIndexes) Result(opts driver.CursorOptions) (*driver.BatchCursor, e return driver.NewBatchCursor(li.result, clientSession, clock, opts) } -func (li *ListIndexes) processResponse(_ context.Context, info driver.ResponseInfo) error { - var err error - - li.result, err = driver.NewCursorResponse(info) +func (li *ListIndexes) processResponse(_ context.Context, resp bsoncore.Document, info driver.ResponseInfo) error { + curDoc, err := driver.ExtractCursorDocument(resp) + if err != nil { + return err + } + li.result, err = driver.NewCursorResponse(curDoc, info) return err } diff --git a/x/mongo/driver/operation/update.go b/x/mongo/driver/operation/update.go index 2b63e750a2..2c9c9cba28 100644 --- a/x/mongo/driver/operation/update.go +++ b/x/mongo/driver/operation/update.go @@ -124,8 +124,8 @@ func NewUpdate(updates ...bsoncore.Document) *Update { // Result returns the result of executing this operation. func (u *Update) Result() UpdateResult { return u.result } -func (u *Update) processResponse(_ context.Context, info driver.ResponseInfo) error { - ur, err := buildUpdateResult(info.ServerResponse) +func (u *Update) processResponse(_ context.Context, resp bsoncore.Document, info driver.ResponseInfo) error { + ur, err := buildUpdateResult(resp) u.result.N += ur.N u.result.NModified += ur.NModified @@ -148,12 +148,10 @@ func (u *Update) Execute(ctx context.Context) error { return driver.Operation{ CommandFn: u.command, ProcessResponseFn: u.processResponse, - Batches: []driver.Batches{ - { - Identifier: "updates", - Documents: u.updates, - Ordered: u.ordered, - }, + Batches: &driver.Batches{ + Identifier: "updates", + Documents: u.updates, + Ordered: u.ordered, }, RetryMode: u.retry, Type: driver.Write, diff --git a/x/mongo/driver/operation/update_search_index.go b/x/mongo/driver/operation/update_search_index.go index 6df4874790..60fc0d4c04 100644 --- a/x/mongo/driver/operation/update_search_index.go +++ b/x/mongo/driver/operation/update_search_index.go @@ -71,9 +71,9 @@ func NewUpdateSearchIndex(index string, definition bsoncore.Document) *UpdateSea // Result returns the result of executing this operation. func (usi *UpdateSearchIndex) Result() UpdateSearchIndexResult { return usi.result } -func (usi *UpdateSearchIndex) processResponse(_ context.Context, info driver.ResponseInfo) error { +func (usi *UpdateSearchIndex) processResponse(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error { var err error - usi.result, err = buildUpdateSearchIndexResult(info.ServerResponse) + usi.result, err = buildUpdateSearchIndexResult(resp) return err } diff --git a/x/mongo/driver/operation_exhaust.go b/x/mongo/driver/operation_exhaust.go index 1836ec4657..db1bd881e9 100644 --- a/x/mongo/driver/operation_exhaust.go +++ b/x/mongo/driver/operation_exhaust.go @@ -25,10 +25,9 @@ func (op Operation) ExecuteExhaust(ctx context.Context, conn StreamerConnection) if op.ProcessResponseFn != nil { // Server, ConnectionDescription, and CurrentIndex are unused in this mode. info := ResponseInfo{ - ServerResponse: res, - Connection: conn, + Connection: conn, } - if err = op.ProcessResponseFn(ctx, info); err != nil { + if err = op.ProcessResponseFn(ctx, res, info); err != nil { return err } } diff --git a/x/mongo/driver/wiremessage/wiremessage.go b/x/mongo/driver/wiremessage/wiremessage.go index 987ae16c08..0394272891 100644 --- a/x/mongo/driver/wiremessage/wiremessage.go +++ b/x/mongo/driver/wiremessage/wiremessage.go @@ -16,6 +16,7 @@ package wiremessage import ( "bytes" "encoding/binary" + "strconv" "strings" "sync/atomic" @@ -422,6 +423,38 @@ func ReadMsgSectionRawDocumentSequence(src []byte) (identifier string, data []by return identifier, rem, rest, true } +func DocumentSequenceToArray(src []byte) (data bsoncore.Array, rem []byte, ok bool) { + stype, rem, ok := ReadMsgSectionType(src) + if !ok || stype != DocumentSequence { + return nil, src, false + } + var identifier string + var ret []byte + identifier, rem, ret, ok = ReadMsgSectionRawDocumentSequence(rem) + if !ok { + return nil, src, false + } + + aidx, dst := bsoncore.AppendArrayElementStart(nil, identifier) + i := 0 + for { + var doc bsoncore.Document + doc, rem, ok = bsoncore.ReadDocument(rem) + if !ok { + break + } + dst = bsoncore.AppendDocumentElement(dst, strconv.Itoa(i), doc) + i++ + } + if len(rem) > 0 { + return nil, src, false + } + + dst, _ = bsoncore.AppendArrayEnd(dst, aidx) + + return dst, ret, true +} + // ReadMsgChecksum reads a checksum from src. func ReadMsgChecksum(src []byte) (checksum uint32, rem []byte, ok bool) { i32, rem, ok := readi32(src) From 45d22f8b004e6b915711172278a0d9389e1f381c Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Sun, 13 Oct 2024 18:26:48 -0400 Subject: [PATCH 09/14] WIP --- mongo/client_bulk_write.go | 29 ++-- .../client_side_encryption_test.go | 2 +- mongo/integration/crud_prose_test.go | 26 ++-- .../unified/client_operation_execution.go | 6 +- x/mongo/driver/batch_cursor.go | 3 +- x/mongo/driver/batches.go | 35 +++-- x/mongo/driver/operation.go | 137 ++++++++---------- x/mongo/driver/wiremessage/wiremessage.go | 1 + 8 files changed, 129 insertions(+), 110 deletions(-) diff --git a/mongo/client_bulk_write.go b/mongo/client_bulk_write.go index 962b00efd3..b07da1701d 100644 --- a/mongo/client_bulk_write.go +++ b/mongo/client_bulk_write.go @@ -161,10 +161,16 @@ func (mb *modelBatches) IsOrdered() *bool { func (mb *modelBatches) AdvanceBatches(n int) { mb.offset += n + if mb.offset > len(mb.models) { + mb.offset = len(mb.models) + } } -func (mb *modelBatches) End() bool { - return len(mb.models) <= mb.offset +func (mb *modelBatches) Size() int { + if mb.offset > len(mb.models) { + return 0 + } + return len(mb.models) - mb.offset } func (mb *modelBatches) AppendBatchSequence(dst []byte, maxCount, maxDocSize, totalSize int) (int, []byte, error) { @@ -181,7 +187,7 @@ func (mb *modelBatches) AppendBatchSequence(dst []byte, maxCount, maxDocSize, to dst = append(dst, doc...) return dst }, - appendEnd: func(dst []byte, idx, length int32) []byte { + updateLength: func(dst []byte, idx, length int32) []byte { dst = bsoncore.UpdateLength(dst, idx, length) return dst }, @@ -193,7 +199,7 @@ func (mb *modelBatches) AppendBatchArray(dst []byte, maxCount, maxDocSize, total fn := functionSet{ appendStart: bsoncore.AppendArrayElementStart, appendDocument: bsoncore.AppendDocumentElement, - appendEnd: func(dst []byte, idx, _ int32) []byte { + updateLength: func(dst []byte, idx, _ int32) []byte { dst, _ = bsoncore.AppendArrayEnd(dst, idx) return dst }, @@ -204,11 +210,11 @@ func (mb *modelBatches) AppendBatchArray(dst []byte, maxCount, maxDocSize, total type functionSet struct { appendStart func([]byte, string) (int32, []byte) appendDocument func([]byte, string, []byte) []byte - appendEnd func([]byte, int32, int32) []byte + updateLength func([]byte, int32, int32) []byte } func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxDocSize, totalSize int) (int, []byte, error) { - if mb.End() { + if mb.Size() == 0 { return 0, dst, io.EOF } @@ -344,8 +350,8 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD return 0, dst[:l], nil } - dst = fn.appendEnd(dst, opsIdx, int32(len(dst[opsIdx:]))) - nsDst = fn.appendEnd(nsDst, nsIdx, int32(len(nsDst[nsIdx:]))) + dst = fn.updateLength(dst, opsIdx, int32(len(dst[opsIdx:]))) + nsDst = fn.updateLength(nsDst, nsIdx, int32(len(nsDst[nsIdx:]))) dst = append(dst, nsDst...) mb.retryMode = driver.RetryNone @@ -417,7 +423,10 @@ func (mb *modelBatches) processResponse(ctx context.Context, resp bsoncore.Docum ok := true for cursor.Next(ctx) { var cur cursorInfo - cursor.Decode(&cur) + err = cursor.Decode(&cur) + if err != nil { + return err + } if int(cur.Idx) >= len(mb.cursorHandlers) { continue } @@ -483,7 +492,7 @@ func (mb *modelBatches) appendUpdateResult(cur *cursorInfo, raw bson.Raw) bool { result.ModifiedCount = int64(*cur.NModified) } if cur.Upserted != nil { - result.UpsertedID = (*cur.Upserted).ID + result.UpsertedID = cur.Upserted.ID } mb.result.UpdateResults[int(cur.Idx)] = result if err := cur.extractError(); err != nil { diff --git a/mongo/integration/client_side_encryption_test.go b/mongo/integration/client_side_encryption_test.go index abff32ff62..ff4281a970 100644 --- a/mongo/integration/client_side_encryption_test.go +++ b/mongo/integration/client_side_encryption_test.go @@ -396,7 +396,7 @@ func TestClientSideEncryptionCustomCrypt(t *testing.T) { "expected 0 calls to DecryptExplicit, got %v", cc.numDecryptExplicitCalls) assert.Equal(mt, cc.numCloseCalls, 0, "expected 0 calls to Close, got %v", cc.numCloseCalls) - assert.Equal(mt, cc.numBypassAutoEncryptionCalls, 2, + assert.Equal(mt, cc.numBypassAutoEncryptionCalls, 1, "expected 2 calls to BypassAutoEncryption, got %v", cc.numBypassAutoEncryptionCalls) }) } diff --git a/mongo/integration/crud_prose_test.go b/mongo/integration/crud_prose_test.go index c3b3120d71..2c61c73e4c 100644 --- a/mongo/integration/crud_prose_test.go +++ b/mongo/integration/crud_prose_test.go @@ -420,7 +420,7 @@ func TestClientBulkWrite(t *testing.T) { mt.Run("input with greater than maxWriteBatchSize", func(mt *mtest.T) { var opsCnt []int monitor := &event.CommandMonitor{ - Started: func(ctx context.Context, e *event.CommandStartedEvent) { + Started: func(_ context.Context, e *event.CommandStartedEvent) { if e.CommandName == "bulkWrite" { v := e.Command.Lookup("ops") elems, err := v.Array().Elements() @@ -453,7 +453,7 @@ func TestClientBulkWrite(t *testing.T) { mt.Run("input with greater than maxMessageSizeBytes", func(mt *mtest.T) { var opsCnt []int monitor := &event.CommandMonitor{ - Started: func(ctx context.Context, e *event.CommandStartedEvent) { + Started: func(_ context.Context, e *event.CommandStartedEvent) { if e.CommandName == "bulkWrite" { v := e.Command.Lookup("ops") elems, err := v.Array().Elements() @@ -533,7 +533,7 @@ func TestClientBulkWrite(t *testing.T) { var eventCnt int monitor := &event.CommandMonitor{ - Started: func(ctx context.Context, e *event.CommandStartedEvent) { + Started: func(_ context.Context, e *event.CommandStartedEvent) { if e.CommandName == "bulkWrite" { eventCnt++ } @@ -578,11 +578,12 @@ func TestClientBulkWrite(t *testing.T) { mt.Run("bulkWrite handles a cursor requiring a getMore", func(mt *mtest.T) { coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, true) - coll.Drop(context.Background()) + err := coll.Drop(context.Background()) + require.NoError(mt, err, "Drop error") var getMoreCalled bool monitor := &event.CommandMonitor{ - Started: func(ctx context.Context, e *event.CommandStartedEvent) { + Started: func(_ context.Context, e *event.CommandStartedEvent) { if e.CommandName == "getMore" { getMoreCalled = true } @@ -592,7 +593,8 @@ func TestClientBulkWrite(t *testing.T) { var hello struct { MaxBsonObjectSize int } - require.NoError(mt, mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello), "Hello error") + err = mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello) + require.NoError(mt, err, "Hello error") upsert := true models := (&mongo.ClientWriteModels{}). AppendUpdateOne(&mongo.ClientUpdateOneModel{ @@ -616,11 +618,12 @@ func TestClientBulkWrite(t *testing.T) { mt.Run("bulkWrite handles a cursor requiring a getMore within a transaction", func(mt *mtest.T) { coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, true) - coll.Drop(context.Background()) + err := coll.Drop(context.Background()) + require.NoError(mt, err, "Drop error") var getMoreCalled bool monitor := &event.CommandMonitor{ - Started: func(ctx context.Context, e *event.CommandStartedEvent) { + Started: func(_ context.Context, e *event.CommandStartedEvent) { if e.CommandName == "getMore" { getMoreCalled = true } @@ -630,7 +633,8 @@ func TestClientBulkWrite(t *testing.T) { var hello struct { MaxBsonObjectSize int } - require.NoError(mt, mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello), "Hello error") + err = mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello) + require.NoError(mt, err, "Hello error") session, err := mt.Client.StartSession() require.NoError(mt, err, "StartSession error") defer session.EndSession(context.Background()) @@ -648,7 +652,7 @@ func TestClientBulkWrite(t *testing.T) { Update: bson.D{{"$set", bson.D{{"x", 1}}}}, Upsert: &upsert, }) - result, err := session.WithTransaction(context.TODO(), func(ctx mongo.SessionContext) (interface{}, error) { + result, err := session.WithTransaction(context.Background(), func(mongo.SessionContext) (interface{}, error) { return mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetVerboseResults(true)) }) require.NoError(mt, err, "BulkWrite error") @@ -659,7 +663,7 @@ func TestClientBulkWrite(t *testing.T) { assert.True(mt, getMoreCalled, "the getMore was not called") }) - mt.Run("bulkWrite handles a getMore error", func(mt *mtest.T) { + mt.Run("bulkWrite handles a getMore error", func(_ *mtest.T) { }) mt.Run("bulkWrite returns error for unacknowledged too-large insert", func(mt *mtest.T) { diff --git a/mongo/integration/unified/client_operation_execution.go b/mongo/integration/unified/client_operation_execution.go index 80fc1374b4..17cb284315 100644 --- a/mongo/integration/unified/client_operation_execution.go +++ b/mongo/integration/unified/client_operation_execution.go @@ -240,7 +240,7 @@ func executeClientBulkWrite(ctx context.Context, operation *operation) (*operati resBuilder = bsoncore.NewDocumentBuilder() for k, v := range res.DeleteResults { - resBuilder.AppendDocument(strconv.Itoa(int(k)), + resBuilder.AppendDocument(strconv.Itoa(k), bsoncore.NewDocumentBuilder(). AppendInt64("deletedCount", v.DeletedCount). Build(), @@ -254,7 +254,7 @@ func executeClientBulkWrite(ctx context.Context, operation *operation) (*operati if err != nil { return nil, err } - resBuilder.AppendDocument(strconv.Itoa(int(k)), + resBuilder.AppendDocument(strconv.Itoa(k), bsoncore.NewDocumentBuilder(). AppendValue("insertedId", bsoncore.Value{Type: t, Data: d}). Build(), @@ -274,7 +274,7 @@ func executeClientBulkWrite(ctx context.Context, operation *operation) (*operati } b.AppendValue("upsertedId", bsoncore.Value{Type: t, Data: d}) } - resBuilder.AppendDocument(strconv.Itoa(int(k)), b.Build()) + resBuilder.AppendDocument(strconv.Itoa(k), b.Build()) } rawBuilder.AppendDocument("updateResults", resBuilder.Build()) diff --git a/x/mongo/driver/batch_cursor.go b/x/mongo/driver/batch_cursor.go index fcc3895589..656aeeddb2 100644 --- a/x/mongo/driver/batch_cursor.go +++ b/x/mongo/driver/batch_cursor.go @@ -399,8 +399,7 @@ func (bc *BatchCursor) getMore(ctx context.Context) { }, Database: bc.database, Deployment: bc.getOperationDeployment(), - ProcessResponseFn: func(_ context.Context, response bsoncore.Document, info ResponseInfo) error { - // response := info.ServerResponse + ProcessResponseFn: func(_ context.Context, response bsoncore.Document, _ ResponseInfo) error { id, ok := response.Lookup("cursor", "id").Int64OK() if !ok { return fmt.Errorf("cursor.id should be an int64 but is a BSON %s", response.Lookup("cursor", "id").Type) diff --git a/x/mongo/driver/batches.go b/x/mongo/driver/batches.go index fbcd169a9f..0fd0a218a5 100644 --- a/x/mongo/driver/batches.go +++ b/x/mongo/driver/batches.go @@ -24,8 +24,11 @@ type Batches struct { offset int } -func (b *Batches) AppendBatchSequence(dst []byte, maxCount, maxDocSize, totalSize int) (int, []byte, error) { - if b.End() { +// AppendBatchSequence appends dst with document sequence of batches as long as the limits of max count, max +// document size, or total size allows. It returns the number of batches appended, the new appended slice, and +// any error raised. It returns the origenal input slice if nothing can be appends within the limits. +func (b *Batches) AppendBatchSequence(dst []byte, maxCount, maxDocSize, _ int) (int, []byte, error) { + if b.Size() == 0 { return 0, dst, io.EOF } l := len(dst) @@ -34,7 +37,7 @@ func (b *Batches) AppendBatchSequence(dst []byte, maxCount, maxDocSize, totalSiz idx, dst = bsoncore.ReserveLength(dst) dst = append(dst, b.Identifier...) dst = append(dst, 0x00) - size := len(dst) - l + var size int var n int for i := b.offset; i < len(b.Documents); i++ { if n == maxCount { @@ -45,7 +48,7 @@ func (b *Batches) AppendBatchSequence(dst []byte, maxCount, maxDocSize, totalSiz break } size += len(doc) - if size >= totalSize { + if size > maxDocSize { break } dst = append(dst, doc...) @@ -58,13 +61,16 @@ func (b *Batches) AppendBatchSequence(dst []byte, maxCount, maxDocSize, totalSiz return n, dst, nil } -func (b *Batches) AppendBatchArray(dst []byte, maxCount, maxDocSize, totalSize int) (int, []byte, error) { - if b.End() { +// AppendBatchArray appends dst with array of batches as long as the limits of max count, max document size, or +// total size allows. It returns the number of batches appended, the new appended slice, and any error raised. It +// returns the origenal input slice if nothing can be appends within the limits. +func (b *Batches) AppendBatchArray(dst []byte, maxCount, maxDocSize, _ int) (int, []byte, error) { + if b.Size() == 0 { return 0, dst, io.EOF } l := len(dst) aidx, dst := bsoncore.AppendArrayElementStart(dst, b.Identifier) - size := len(dst) - l + var size int var n int for i := b.offset; i < len(b.Documents); i++ { if n == maxCount { @@ -75,7 +81,7 @@ func (b *Batches) AppendBatchArray(dst []byte, maxCount, maxDocSize, totalSize i break } size += len(doc) - if size >= totalSize { + if size > maxDocSize { break } dst = bsoncore.AppendDocumentElement(dst, strconv.Itoa(n), doc) @@ -92,14 +98,23 @@ func (b *Batches) AppendBatchArray(dst []byte, maxCount, maxDocSize, totalSize i return n, dst, nil } +// IsOrdered indicates if the batches are ordered. func (b *Batches) IsOrdered() *bool { return b.Ordered } +// AdvanceBatches advances the batches with the given input. func (b *Batches) AdvanceBatches(n int) { b.offset += n + if b.offset > len(b.Documents) { + b.offset = len(b.Documents) + } } -func (b *Batches) End() bool { - return len(b.Documents) <= b.offset +// Size returns the size of batches remained. +func (b *Batches) Size() int { + if b.offset > len(b.Documents) { + return 0 + } + return len(b.Documents) - b.offset } diff --git a/x/mongo/driver/operation.go b/x/mongo/driver/operation.go index 0c98eabb60..f16afe1da6 100644 --- a/x/mongo/driver/operation.go +++ b/x/mongo/driver/operation.go @@ -169,8 +169,8 @@ func redactStartedInformationCmd(info startedInformation) bson.Raw { // sensitive and cannot be monitored. If there was a type 1 payload for // the current batch, convert it to a BSON array if !info.redacted { - cmdCopy = make([]byte, len(info.cmd)) - copy(cmdCopy, info.cmd) + cmdCopy = make([]byte, 0, len(info.cmd)) + cmdCopy = append(cmdCopy, info.cmd...) if len(info.documentSequence) > 0 { // remove 0 byte at end @@ -284,7 +284,7 @@ type Operation struct { AppendBatchArray(dst []byte, maxCount int, maxDocSize int, totalSize int) (int, []byte, error) IsOrdered() *bool AdvanceBatches(n int) - End() bool + Size() int } // Legacy sets the legacy type for this operation. There are only 3 types that require legacy @@ -719,8 +719,9 @@ func (op Operation) Execute(ctx context.Context) error { desc := description.SelectedServer{Server: conn.Description(), Kind: op.Deployment.Kind()} + var moreToCome bool var startedInfo startedInformation - *wm, startedInfo, err = op.createWireMessage(ctx, maxTimeMS, (*wm)[:0], desc, conn, requestID) + *wm, moreToCome, startedInfo, err = op.createWireMessage(ctx, maxTimeMS, (*wm)[:0], desc, conn, requestID) if err != nil { return err @@ -746,9 +747,6 @@ func (op Operation) Execute(ctx context.Context) error { op.publishStartedEvent(ctx, startedInfo) - // get the moreToCome flag information before we compress - moreToCome := wiremessage.IsMsgMoreToCome(*wm) - // compress wiremessage if allowed if compressor, ok := conn.(Compressor); ok && op.canCompress(startedInfo.cmdName) { b := memoryPool.Get().(*[]byte) @@ -872,15 +870,14 @@ func (op Operation) Execute(ctx context.Context) error { // } } - if op.Batches != nil && len(tt.WriteErrors) > 0 && currIndex > 0 { - for i := range tt.WriteErrors { - tt.WriteErrors[i].Index += int64(currIndex) - } - } - // If batching is enabled and either ordered is the default (which is true) or // explicitly set to true and we have write errors, return the errors. if op.Batches != nil && len(tt.WriteErrors) > 0 { + if currIndex > 0 { + for i := range tt.WriteErrors { + tt.WriteErrors[i].Index += int64(currIndex) + } + } if isOrdered := op.Batches.IsOrdered(); isOrdered == nil || *isOrdered { return tt } @@ -1015,7 +1012,6 @@ func (op Operation) Execute(ctx context.Context) error { } perr := op.ProcessResponseFn(ctx, res, info) if perr != nil { - fmt.Println("op", perr) return perr } } @@ -1036,7 +1032,7 @@ func (op Operation) Execute(ctx context.Context) error { // If we're batching and there are batches remaining, advance to the next batch. This isn't // a retry, so increment the transaction number, reset the retries number, and don't set // server or connection to nil to continue using the same connection. - if op.Batches != nil { + if op.Batches != nil && op.Batches.Size() > startedInfo.processedBatches { // If retries are supported for the current operation on the current server description, // the session isn't nil, and client retries are enabled, increment the txn number. // Calling IncrementTxnNumber() for server descriptions or topologies that do not @@ -1053,7 +1049,7 @@ func (op Operation) Execute(ctx context.Context) error { } currIndex += startedInfo.processedBatches op.Batches.AdvanceBatches(startedInfo.processedBatches) - if !op.Batches.End() { + if op.Batches.Size() > 0 { continue } } @@ -1289,21 +1285,11 @@ func (op Operation) createMsgWireMessage( cmdFn func([]byte, description.SelectedServer) ([]byte, error), ) ([]byte, []byte, error) { var flags wiremessage.MsgFlag - // We set the MoreToCome bit if we have a write concern, it's unacknowledged, and we either - // aren't batching or we are encoding the last batch. - var batching bool - if op.Batches != nil && !op.Batches.End() { - batching = true - } - if op.WriteConcern != nil && !writeconcern.AckWrite(op.WriteConcern) && !batching { - flags = wiremessage.MoreToCome - } // Set the ExhaustAllowed flag if the connection supports streaming. This will tell the server that it can // respond with the MoreToCome flag and then stream responses over this connection. if streamer, ok := conn.(StreamerConnection); ok && streamer.SupportsStreaming() { - flags |= wiremessage.ExhaustAllowed + flags = wiremessage.ExhaustAllowed } - dst = wiremessage.AppendMsgFlags(dst, flags) // Body dst = wiremessage.AppendMsgSectionType(dst, wiremessage.SingleDocument) @@ -1365,14 +1351,32 @@ func (op Operation) createWireMessage( desc description.SelectedServer, conn Connection, requestID int32, -) ([]byte, startedInformation, error) { +) ([]byte, bool, startedInformation, error) { var info startedInformation var wmindex int32 var err error + fIdx := len(dst) isLegacy := isLegacyHandshake(op, desc) - shouldEncrypt := op.shouldEncrypt() - if !isLegacy && !shouldEncrypt { + if isLegacy { + cmdFn := func(dst []byte, desc description.SelectedServer) ([]byte, error) { + info.processedBatches, dst, err = op.addLegacyCommandFields(dst, desc) + return dst, err + } + requestID := wiremessage.NextRequestID() + wmindex, dst = wiremessage.AppendHeaderStart(dst, requestID, 0, wiremessage.OpQuery) + dst, info.cmd, err = op.createLegacyHandshakeWireMessage(maxTimeMS, dst, desc, cmdFn) + } else if op.shouldEncrypt() { + if desc.WireVersion.Max < cryptMinWireVersion { + return dst, false, info, errors.New("auto-encryption requires a MongoDB version of 4.2") + } + cmdFn := func(dst []byte, desc description.SelectedServer) ([]byte, error) { + info.processedBatches, dst, err = op.addEncryptCommandFields(ctx, dst, desc) + return dst, err + } + wmindex, dst = wiremessage.AppendHeaderStart(dst, requestID, 0, wiremessage.OpMsg) + dst, info.cmd, err = op.createMsgWireMessage(maxTimeMS, dst, desc, conn, cmdFn) + } else { wmindex, dst = wiremessage.AppendHeaderStart(dst, requestID, 0, wiremessage.OpMsg) dst, info.cmd, err = op.createMsgWireMessage(maxTimeMS, dst, desc, conn, op.CommandFn) if err == nil && op.Batches != nil { @@ -1393,64 +1397,43 @@ func (op Operation) createWireMessage( } } } - } else if shouldEncrypt { - if desc.WireVersion.Max < cryptMinWireVersion { - return dst, info, errors.New("auto-encryption requires a MongoDB version of 4.2") - } - cmdFn := func(dst []byte, desc description.SelectedServer) ([]byte, error) { - // create temporary command document - var cmdDst []byte - info.processedBatches, cmdDst, err = op.addEncryptCommandFields(nil, desc) - if err != nil { - return nil, err - } - // encrypt the command - encrypted, err := op.Crypt.Encrypt(ctx, op.Database, cmdDst) - if err != nil { - return nil, err - } - // append encrypted command to original destination, removing the first 4 bytes (length) and final byte (terminator) - dst = append(dst, encrypted[4:len(encrypted)-1]...) - return dst, nil - } - wmindex, dst = wiremessage.AppendHeaderStart(dst, requestID, 0, wiremessage.OpMsg) - dst, info.cmd, err = op.createMsgWireMessage(maxTimeMS, dst, desc, conn, cmdFn) - } else { // isLegacy - cmdFn := func(dst []byte, desc description.SelectedServer) ([]byte, error) { - info.processedBatches, dst, err = op.addLegacyCommandFields(dst, desc) - return dst, err - } - requestID := wiremessage.NextRequestID() - wmindex, dst = wiremessage.AppendHeaderStart(dst, requestID, 0, wiremessage.OpQuery) - dst, info.cmd, err = op.createLegacyHandshakeWireMessage(maxTimeMS, dst, desc, cmdFn) } if err != nil { - return nil, info, err + return nil, false, info, err + } + + var moreToCome bool + // We set the MoreToCome bit if we have a write concern, it's unacknowledged, and we either + // aren't batching or we are encoding the last batch. + unacknowledged := op.WriteConcern != nil && !writeconcern.AckWrite(op.WriteConcern) + batching := op.Batches != nil && op.Batches.Size() > info.processedBatches + if !isLegacy && unacknowledged && !batching { + dst[fIdx] |= byte(wiremessage.MoreToCome) + moreToCome = true } info.requestID = requestID - return bsoncore.UpdateLength(dst, wmindex, int32(len(dst[wmindex:]))), info, nil + return bsoncore.UpdateLength(dst, wmindex, int32(len(dst[wmindex:]))), moreToCome, info, nil } -func (op Operation) addEncryptCommandFields(dst []byte, desc description.SelectedServer) (int, []byte, error) { - var idx int32 - idx, dst = bsoncore.AppendDocumentStart(dst) +func (op Operation) addEncryptCommandFields(ctx context.Context, dst []byte, desc description.SelectedServer) (int, []byte, error) { + idx, cmdDst := bsoncore.AppendDocumentStart(nil) var err error - dst, err = op.CommandFn(dst, desc) + // create temporary command document + cmdDst, err = op.CommandFn(cmdDst, desc) if err != nil { return 0, nil, err } var n int if op.Batches != nil { - maxBatchCount := int(desc.MaxBatchCount) - maxDocumentSize := int(desc.MaxDocumentSize) - if maxBatchCount > 1 { - n, dst, err = op.Batches.AppendBatchArray(dst, maxBatchCount, cryptMaxBsonObjectSize, maxDocumentSize) + if maxBatchCount := int(desc.MaxBatchCount); maxBatchCount > 1 { + n, cmdDst, err = op.Batches.AppendBatchArray(cmdDst, maxBatchCount, cryptMaxBsonObjectSize, cryptMaxBsonObjectSize) if err != nil { return 0, nil, err } } if n == 0 { - n, dst, err = op.Batches.AppendBatchArray(dst, 1, maxDocumentSize, maxDocumentSize) + maxDocumentSize := int(desc.MaxDocumentSize) + n, cmdDst, err = op.Batches.AppendBatchArray(cmdDst, 1, maxDocumentSize, maxDocumentSize) if err != nil { return 0, nil, err } @@ -1459,10 +1442,17 @@ func (op Operation) addEncryptCommandFields(dst []byte, desc description.Selecte } } } - dst, err = bsoncore.AppendDocumentEnd(dst, idx) + cmdDst, err = bsoncore.AppendDocumentEnd(cmdDst, idx) + if err != nil { + return 0, nil, err + } + // encrypt the command + encrypted, err := op.Crypt.Encrypt(ctx, op.Database, cmdDst) if err != nil { return 0, nil, err } + // append encrypted command to original destination, removing the first 4 bytes (length) and final byte (terminator) + dst = append(dst, encrypted[4:len(encrypted)-1]...) return n, dst, nil } @@ -1476,7 +1466,8 @@ func (op Operation) addLegacyCommandFields(dst []byte, desc description.Selected return 0, dst, nil } var n int - n, dst, err = op.Batches.AppendBatchArray(dst, int(desc.MaxBatchCount), int(desc.MaxDocumentSize), int(desc.MaxDocumentSize)) + maxDocumentSize := int(desc.MaxDocumentSize) + n, dst, err = op.Batches.AppendBatchArray(dst, int(desc.MaxBatchCount), maxDocumentSize, maxDocumentSize) if err != nil { return 0, nil, err } diff --git a/x/mongo/driver/wiremessage/wiremessage.go b/x/mongo/driver/wiremessage/wiremessage.go index 0394272891..4eba32619a 100644 --- a/x/mongo/driver/wiremessage/wiremessage.go +++ b/x/mongo/driver/wiremessage/wiremessage.go @@ -423,6 +423,7 @@ func ReadMsgSectionRawDocumentSequence(src []byte) (identifier string, data []by return identifier, rem, rest, true } +// DocumentSequenceToArray converts a document sequence in byte slice to a bsoncore.Array. func DocumentSequenceToArray(src []byte) (data bsoncore.Array, rem []byte, ok bool) { stype, rem, ok := ReadMsgSectionType(src) if !ok || stype != DocumentSequence { From 00cc83a961f79254c891ab0569aaa988d7b70875 Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Thu, 17 Oct 2024 00:28:37 -0400 Subject: [PATCH 10/14] WIP --- mongo/client.go | 2 - mongo/client_bulk_write.go | 92 ++++++++------ mongo/client_bulk_write_models.go | 89 ++++++-------- mongo/integration/crud_prose_test.go | 61 ++++------ .../unified/client_operation_execution.go | 85 ++++++------- mongo/integration/unified/error.go | 6 +- mongo/integration/unified/operation.go | 4 +- .../unacknowledgedBulkWrite.json | 5 +- .../unacknowledgedBulkWrite.yml | 3 +- x/mongo/driver/operation.go | 113 +++++++++++------- x/mongo/driver/session/client_session.go | 1 - 11 files changed, 239 insertions(+), 222 deletions(-) diff --git a/mongo/client.go b/mongo/client.go index edc6cd487f..5d8e7ece67 100644 --- a/mongo/client.go +++ b/mongo/client.go @@ -426,8 +426,6 @@ func (c *Client) StartSession(opts ...*options.SessionOptions) (Session, error) return nil, replaceErrors(err) } - // Writes are not retryable on standalones, so let operation determine whether to retry - sess.RetryWrite = false sess.RetryRead = c.retryReads return &sessionImpl{ diff --git a/mongo/client_bulk_write.go b/mongo/client_bulk_write.go index b07da1701d..19d43684b8 100644 --- a/mongo/client_bulk_write.go +++ b/mongo/client_bulk_write.go @@ -27,7 +27,7 @@ import ( // bulkWrite performs a bulkwrite operation type clientBulkWrite struct { - models []interface{} + models []clientWriteModel errorsOnly bool ordered *bool bypassDocumentValidation *bool @@ -46,11 +46,12 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { return errors.New("empty write models") } batches := &modelBatches{ - session: bw.session, - client: bw.client, - ordered: bw.ordered, - models: bw.models, - result: &bw.result, + session: bw.session, + client: bw.client, + ordered: bw.ordered, + models: bw.models, + result: &bw.result, + retryMode: driver.RetryOnce, } err := driver.Operation{ CommandFn: bw.newCommand(), @@ -142,7 +143,7 @@ type modelBatches struct { client *Client ordered *bool - models []interface{} + models []clientWriteModel offset int @@ -222,17 +223,14 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD mb.newIDMap = make(map[int]interface{}) nsMap := make(map[string]int) - getNsIndex := func(namespace string) (int, bsoncore.Document) { - idx, doc := bsoncore.AppendDocumentStart(nil) - doc = bsoncore.AppendStringElement(doc, "ns", namespace) - doc, _ = bsoncore.AppendDocumentEnd(doc, idx) - - if v, ok := nsMap[namespace]; ok { - return v, doc + getNsIndex := func(namespace string) (int, bool) { + v, ok := nsMap[namespace] + if ok { + return v, ok } nsIdx := len(nsMap) nsMap[namespace] = nsIdx - return nsIdx, doc + return nsIdx, ok } canRetry := true @@ -249,12 +247,13 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD break } - var nsIdx int - var ns, doc bsoncore.Document + ns := mb.models[i].namespace + nsIdx, exists := getNsIndex(ns) + + var doc bsoncore.Document var err error - switch model := mb.models[i].(type) { + switch model := mb.models[i].model.(type) { case *ClientInsertOneModel: - nsIdx, ns = getNsIndex(model.Namespace) mb.cursorHandlers[i] = mb.appendInsertResult var id interface{} id, doc, err = (&clientInsertDoc{ @@ -266,7 +265,6 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD } mb.newIDMap[i] = id case *ClientUpdateOneModel: - nsIdx, ns = getNsIndex(model.Namespace) mb.cursorHandlers[i] = mb.appendUpdateResult doc, err = (&clientUpdateDoc{ namespace: nsIdx, @@ -281,7 +279,6 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD }).marshal(mb.client.bsonOpts, mb.client.registry) case *ClientUpdateManyModel: canRetry = false - nsIdx, ns = getNsIndex(model.Namespace) mb.cursorHandlers[i] = mb.appendUpdateResult doc, err = (&clientUpdateDoc{ namespace: nsIdx, @@ -295,7 +292,6 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD checkDollarKey: true, }).marshal(mb.client.bsonOpts, mb.client.registry) case *ClientReplaceOneModel: - nsIdx, ns = getNsIndex(model.Namespace) mb.cursorHandlers[i] = mb.appendUpdateResult doc, err = (&clientUpdateDoc{ namespace: nsIdx, @@ -309,7 +305,6 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD checkDollarKey: false, }).marshal(mb.client.bsonOpts, mb.client.registry) case *ClientDeleteOneModel: - nsIdx, ns = getNsIndex(model.Namespace) mb.cursorHandlers[i] = mb.appendDeleteResult doc, err = (&clientDeleteDoc{ namespace: nsIdx, @@ -320,7 +315,6 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD }).marshal(mb.client.bsonOpts, mb.client.registry) case *ClientDeleteManyModel: canRetry = false - nsIdx, ns = getNsIndex(model.Namespace) mb.cursorHandlers[i] = mb.appendDeleteResult doc, err = (&clientDeleteDoc{ namespace: nsIdx, @@ -343,7 +337,12 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD } dst = fn.appendDocument(dst, strconv.Itoa(n), doc) - nsDst = fn.appendDocument(nsDst, strconv.Itoa(n), ns) + if !exists { + idx, doc := bsoncore.AppendDocumentStart(nil) + doc = bsoncore.AppendStringElement(doc, "ns", ns) + doc, _ = bsoncore.AppendDocumentEnd(doc, idx) + nsDst = fn.appendDocument(nsDst, strconv.Itoa(n), doc) + } n++ } if n == 0 { @@ -356,7 +355,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD mb.retryMode = driver.RetryNone if mb.client.retryWrites && canRetry { - mb.retryMode = driver.RetryOncePerCommand + mb.retryMode = driver.RetryOnce } return n, dst, nil } @@ -414,7 +413,7 @@ func (mb *modelBatches) processResponse(ctx context.Context, resp bsoncore.Docum return err } var cursor *Cursor - cursor, err = newCursorWithSession(bCursor, mb.client.bsonOpts, mb.client.registry, mb.session) + cursor, err = newCursor(bCursor, mb.client.bsonOpts, mb.client.registry) if err != nil { return err } @@ -430,7 +429,7 @@ func (mb *modelBatches) processResponse(ctx context.Context, resp bsoncore.Docum if int(cur.Idx) >= len(mb.cursorHandlers) { continue } - ok = ok && mb.cursorHandlers[int(cur.Idx)](&cur, cursor.Current) + ok = mb.cursorHandlers[int(cur.Idx)](&cur, cursor.Current) && ok } err = cursor.Err() if err != nil { @@ -456,32 +455,51 @@ func (mb *modelBatches) processResponse(ctx context.Context, resp bsoncore.Docum } func (mb *modelBatches) appendDeleteResult(cur *cursorInfo, raw bson.Raw) bool { + if err := cur.extractError(); err != nil { + err.Raw = raw + if mb.writeErrors == nil { + mb.writeErrors = make(map[int]WriteError) + } + mb.writeErrors[int(cur.Idx)] = *err + return false + } + if mb.result.DeleteResults == nil { mb.result.DeleteResults = make(map[int]ClientDeleteResult) } mb.result.DeleteResults[int(cur.Idx)] = ClientDeleteResult{int64(cur.N)} + + return true +} + +func (mb *modelBatches) appendInsertResult(cur *cursorInfo, raw bson.Raw) bool { if err := cur.extractError(); err != nil { err.Raw = raw + if mb.writeErrors == nil { + mb.writeErrors = make(map[int]WriteError) + } mb.writeErrors[int(cur.Idx)] = *err return false } - return true -} -func (mb *modelBatches) appendInsertResult(cur *cursorInfo, raw bson.Raw) bool { if mb.result.InsertResults == nil { mb.result.InsertResults = make(map[int]ClientInsertResult) } mb.result.InsertResults[int(cur.Idx)] = ClientInsertResult{mb.newIDMap[int(cur.Idx)]} + + return true +} + +func (mb *modelBatches) appendUpdateResult(cur *cursorInfo, raw bson.Raw) bool { if err := cur.extractError(); err != nil { err.Raw = raw + if mb.writeErrors == nil { + mb.writeErrors = make(map[int]WriteError) + } mb.writeErrors[int(cur.Idx)] = *err return false } - return true -} -func (mb *modelBatches) appendUpdateResult(cur *cursorInfo, raw bson.Raw) bool { if mb.result.UpdateResults == nil { mb.result.UpdateResults = make(map[int]ClientUpdateResult) } @@ -495,11 +513,7 @@ func (mb *modelBatches) appendUpdateResult(cur *cursorInfo, raw bson.Raw) bool { result.UpsertedID = cur.Upserted.ID } mb.result.UpdateResults[int(cur.Idx)] = result - if err := cur.extractError(); err != nil { - err.Raw = raw - mb.writeErrors[int(cur.Idx)] = *err - return false - } + return true } diff --git a/mongo/client_bulk_write_models.go b/mongo/client_bulk_write_models.go index dfd090715e..4a2259a5c9 100644 --- a/mongo/client_bulk_write_models.go +++ b/mongo/client_bulk_write_models.go @@ -7,89 +7,108 @@ package mongo import ( + "fmt" + "go.mongodb.org/mongo-driver/mongo/options" ) // ClientWriteModels is a struct that can be used in a client-level BulkWrite operation. type ClientWriteModels struct { - models []interface{} + models []clientWriteModel +} + +type clientWriteModel struct { + namespace string + model interface{} } // AppendInsertOne appends ClientInsertOneModels. -func (m *ClientWriteModels) AppendInsertOne(models ...*ClientInsertOneModel) *ClientWriteModels { +func (m *ClientWriteModels) AppendInsertOne(database, collection string, models ...*ClientInsertOneModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} } for _, model := range models { - m.models = append(m.models, model) + m.models = append(m.models, clientWriteModel{ + namespace: fmt.Sprintf("%s.%s", database, collection), + model: model, + }) } return m } // AppendUpdateOne appends ClientUpdateOneModels. -func (m *ClientWriteModels) AppendUpdateOne(models ...*ClientUpdateOneModel) *ClientWriteModels { +func (m *ClientWriteModels) AppendUpdateOne(database, collection string, models ...*ClientUpdateOneModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} } for _, model := range models { - m.models = append(m.models, model) + m.models = append(m.models, clientWriteModel{ + namespace: fmt.Sprintf("%s.%s", database, collection), + model: model, + }) } return m } // AppendUpdateMany appends ClientUpdateManyModels. -func (m *ClientWriteModels) AppendUpdateMany(models ...*ClientUpdateManyModel) *ClientWriteModels { +func (m *ClientWriteModels) AppendUpdateMany(database, collection string, models ...*ClientUpdateManyModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} } for _, model := range models { - m.models = append(m.models, model) + m.models = append(m.models, clientWriteModel{ + namespace: fmt.Sprintf("%s.%s", database, collection), + model: model, + }) } return m } // AppendReplaceOne appends ClientReplaceOneModels. -func (m *ClientWriteModels) AppendReplaceOne(models ...*ClientReplaceOneModel) *ClientWriteModels { +func (m *ClientWriteModels) AppendReplaceOne(database, collection string, models ...*ClientReplaceOneModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} } for _, model := range models { - m.models = append(m.models, model) + m.models = append(m.models, clientWriteModel{ + namespace: fmt.Sprintf("%s.%s", database, collection), + model: model, + }) } return m } // AppendDeleteOne appends ClientDeleteOneModels. -func (m *ClientWriteModels) AppendDeleteOne(models ...*ClientDeleteOneModel) *ClientWriteModels { +func (m *ClientWriteModels) AppendDeleteOne(database, collection string, models ...*ClientDeleteOneModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} } for _, model := range models { - m.models = append(m.models, model) + m.models = append(m.models, clientWriteModel{ + namespace: fmt.Sprintf("%s.%s", database, collection), + model: model, + }) } return m } // AppendDeleteMany appends ClientDeleteManyModels. -func (m *ClientWriteModels) AppendDeleteMany(models ...*ClientDeleteManyModel) *ClientWriteModels { +func (m *ClientWriteModels) AppendDeleteMany(database, collection string, models ...*ClientDeleteManyModel) *ClientWriteModels { if m == nil { m = &ClientWriteModels{} } for _, model := range models { - m.models = append(m.models, model) + m.models = append(m.models, clientWriteModel{ + namespace: fmt.Sprintf("%s.%s", database, collection), + model: model, + }) } return m } // ClientInsertOneModel is used to insert a single document in a BulkWrite operation. type ClientInsertOneModel struct { - Namespace string - Document interface{} -} - -// NewClientInsertOneModel creates a new ClientInsertOneModel. -func NewClientInsertOneModel(namespace string) *ClientInsertOneModel { - return &ClientInsertOneModel{Namespace: namespace} + Document interface{} } // SetDocument specifies the document to be inserted. The document cannot be nil. If it does not have an _id field when @@ -102,7 +121,6 @@ func (iom *ClientInsertOneModel) SetDocument(doc interface{}) *ClientInsertOneMo // ClientUpdateOneModel is used to update at most one document in a client-level BulkWrite operation. type ClientUpdateOneModel struct { - Namespace string Collation *options.Collation Upsert *bool Filter interface{} @@ -111,11 +129,6 @@ type ClientUpdateOneModel struct { Hint interface{} } -// ClientNewUpdateOneModel creates a new ClientUpdateOneModel. -func ClientNewUpdateOneModel(namespace string) *ClientUpdateOneModel { - return &ClientUpdateOneModel{Namespace: namespace} -} - // SetHint specifies the index to use for the operation. This should either be the index name as a string or the index // specification as a document. The default value is nil, which means that no hint will be sent. func (uom *ClientUpdateOneModel) SetHint(hint interface{}) *ClientUpdateOneModel { @@ -162,7 +175,6 @@ func (uom *ClientUpdateOneModel) SetUpsert(upsert bool) *ClientUpdateOneModel { // ClientUpdateManyModel is used to update multiple documents in a client-level BulkWrite operation. type ClientUpdateManyModel struct { - Namespace string Collation *options.Collation Upsert *bool Filter interface{} @@ -171,11 +183,6 @@ type ClientUpdateManyModel struct { Hint interface{} } -// NewClientUpdateManyModel creates a new ClientUpdateManyModel. -func NewClientUpdateManyModel(namespace string) *ClientUpdateManyModel { - return &ClientUpdateManyModel{Namespace: namespace} -} - // SetHint specifies the index to use for the operation. This should either be the index name as a string or the index // specification as a document. The default value is nil, which means that no hint will be sent. func (umm *ClientUpdateManyModel) SetHint(hint interface{}) *ClientUpdateManyModel { @@ -221,7 +228,6 @@ func (umm *ClientUpdateManyModel) SetUpsert(upsert bool) *ClientUpdateManyModel // ClientReplaceOneModel is used to replace at most one document in a client-level BulkWrite operation. type ClientReplaceOneModel struct { - Namespace string Collation *options.Collation Upsert *bool Filter interface{} @@ -229,11 +235,6 @@ type ClientReplaceOneModel struct { Hint interface{} } -// NewClientReplaceOneModel creates a new ClientReplaceOneModel. -func NewClientReplaceOneModel(namespace string) *ClientReplaceOneModel { - return &ClientReplaceOneModel{Namespace: namespace} -} - // SetHint specifies the index to use for the operation. This should either be the index name as a string or the index // specification as a document. The default value is nil, which means that no hint will be sent. func (rom *ClientReplaceOneModel) SetHint(hint interface{}) *ClientReplaceOneModel { @@ -273,17 +274,11 @@ func (rom *ClientReplaceOneModel) SetUpsert(upsert bool) *ClientReplaceOneModel // ClientDeleteOneModel is used to delete at most one document in a client-level BulkWriteOperation. type ClientDeleteOneModel struct { - Namespace string Filter interface{} Collation *options.Collation Hint interface{} } -// NewClientDeleteOneModel creates a new ClientDeleteOneModel. -func NewClientDeleteOneModel(namespace string) *ClientDeleteOneModel { - return &ClientDeleteOneModel{Namespace: namespace} -} - // SetFilter specifies a filter to use to select the document to delete. The filter must be a document containing query // operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching // documents. @@ -308,17 +303,11 @@ func (dom *ClientDeleteOneModel) SetHint(hint interface{}) *ClientDeleteOneModel // ClientDeleteManyModel is used to delete multiple documents in a client-level BulkWrite operation. type ClientDeleteManyModel struct { - Namespace string Filter interface{} Collation *options.Collation Hint interface{} } -// NewClientDeleteManyModel creates a new ClientDeleteManyModel. -func NewClientDeleteManyModel(namespace string) *ClientDeleteManyModel { - return &ClientDeleteManyModel{Namespace: namespace} -} - // SetFilter specifies a filter to use to select documents to delete. The filter must be a document containing query // operators. It cannot be nil. func (dmm *ClientDeleteManyModel) SetFilter(filter interface{}) *ClientDeleteManyModel { diff --git a/mongo/integration/crud_prose_test.go b/mongo/integration/crud_prose_test.go index 2c61c73e4c..ba8e9d6606 100644 --- a/mongo/integration/crud_prose_test.go +++ b/mongo/integration/crud_prose_test.go @@ -437,9 +437,8 @@ func TestClientBulkWrite(t *testing.T) { models := &mongo.ClientWriteModels{} for i := 0; i < hello.MaxWriteBatchSize+1; i++ { models. - AppendInsertOne(&mongo.ClientInsertOneModel{ - Namespace: "db.coll", - Document: bson.D{{"a", "b"}}, + AppendInsertOne("db", "coll", &mongo.ClientInsertOneModel{ + Document: bson.D{{"a", "b"}}, }) } result, err := mt.Client.BulkWrite(context.Background(), models) @@ -472,9 +471,8 @@ func TestClientBulkWrite(t *testing.T) { numModels := hello.MaxMessageSizeBytes/hello.MaxBsonObjectSize + 1 for i := 0; i < numModels; i++ { models. - AppendInsertOne(&mongo.ClientInsertOneModel{ - Namespace: "db.coll", - Document: bson.D{{"a", strings.Repeat("b", hello.MaxBsonObjectSize-500)}}, + AppendInsertOne("db", "coll", &mongo.ClientInsertOneModel{ + Document: bson.D{{"a", strings.Repeat("b", hello.MaxBsonObjectSize-500)}}, }) } result, err := mt.Client.BulkWrite(context.Background(), models) @@ -509,9 +507,8 @@ func TestClientBulkWrite(t *testing.T) { models := &mongo.ClientWriteModels{} for i := 0; i < hello.MaxWriteBatchSize+1; i++ { models. - AppendInsertOne(&mongo.ClientInsertOneModel{ - Namespace: "db.coll", - Document: bson.D{{"a", "b"}}, + AppendInsertOne("db", "coll", &mongo.ClientInsertOneModel{ + Document: bson.D{{"a", "b"}}, }) } _, err := mt.Client.BulkWrite(context.Background(), models) @@ -548,9 +545,8 @@ func TestClientBulkWrite(t *testing.T) { models := &mongo.ClientWriteModels{} for i := 0; i < hello.MaxWriteBatchSize+1; i++ { models. - AppendInsertOne(&mongo.ClientInsertOneModel{ - Namespace: "db.coll", - Document: bson.D{{"_id", 1}}, + AppendInsertOne("db", "coll", &mongo.ClientInsertOneModel{ + Document: bson.D{{"_id", 1}}, }) } @@ -597,17 +593,15 @@ func TestClientBulkWrite(t *testing.T) { require.NoError(mt, err, "Hello error") upsert := true models := (&mongo.ClientWriteModels{}). - AppendUpdateOne(&mongo.ClientUpdateOneModel{ - Namespace: "db.coll", - Filter: bson.D{{"_id", strings.Repeat("a", hello.MaxBsonObjectSize/2)}}, - Update: bson.D{{"$set", bson.D{{"x", 1}}}}, - Upsert: &upsert, + AppendUpdateOne("db", "coll", &mongo.ClientUpdateOneModel{ + Filter: bson.D{{"_id", strings.Repeat("a", hello.MaxBsonObjectSize/2)}}, + Update: bson.D{{"$set", bson.D{{"x", 1}}}}, + Upsert: &upsert, }). - AppendUpdateOne(&mongo.ClientUpdateOneModel{ - Namespace: "db.coll", - Filter: bson.D{{"_id", strings.Repeat("b", hello.MaxBsonObjectSize/2)}}, - Update: bson.D{{"$set", bson.D{{"x", 1}}}}, - Upsert: &upsert, + AppendUpdateOne("db", "coll", &mongo.ClientUpdateOneModel{ + Filter: bson.D{{"_id", strings.Repeat("b", hello.MaxBsonObjectSize/2)}}, + Update: bson.D{{"$set", bson.D{{"x", 1}}}}, + Upsert: &upsert, }) result, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetVerboseResults(true)) require.NoError(mt, err, "BulkWrite error") @@ -640,17 +634,15 @@ func TestClientBulkWrite(t *testing.T) { defer session.EndSession(context.Background()) upsert := true models := (&mongo.ClientWriteModels{}). - AppendUpdateOne(&mongo.ClientUpdateOneModel{ - Namespace: "db.coll", - Filter: bson.D{{"_id", strings.Repeat("a", hello.MaxBsonObjectSize/2)}}, - Update: bson.D{{"$set", bson.D{{"x", 1}}}}, - Upsert: &upsert, + AppendUpdateOne("db", "coll", &mongo.ClientUpdateOneModel{ + Filter: bson.D{{"_id", strings.Repeat("a", hello.MaxBsonObjectSize/2)}}, + Update: bson.D{{"$set", bson.D{{"x", 1}}}}, + Upsert: &upsert, }). - AppendUpdateOne(&mongo.ClientUpdateOneModel{ - Namespace: "db.coll", - Filter: bson.D{{"_id", strings.Repeat("b", hello.MaxBsonObjectSize/2)}}, - Update: bson.D{{"$set", bson.D{{"x", 1}}}}, - Upsert: &upsert, + AppendUpdateOne("db", "coll", &mongo.ClientUpdateOneModel{ + Filter: bson.D{{"_id", strings.Repeat("b", hello.MaxBsonObjectSize/2)}}, + Update: bson.D{{"$set", bson.D{{"x", 1}}}}, + Upsert: &upsert, }) result, err := session.WithTransaction(context.Background(), func(mongo.SessionContext) (interface{}, error) { return mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetVerboseResults(true)) @@ -673,9 +665,8 @@ func TestClientBulkWrite(t *testing.T) { } require.NoError(mt, mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello), "Hello error") models := (&mongo.ClientWriteModels{}). - AppendInsertOne(&mongo.ClientInsertOneModel{ - Namespace: "db.coll", - Document: bson.D{{"a", strings.Repeat("b", hello.MaxBsonObjectSize)}}, + AppendInsertOne("db", "coll", &mongo.ClientInsertOneModel{ + Document: bson.D{{"a", strings.Repeat("b", hello.MaxBsonObjectSize)}}, }) result, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetWriteConcern(writeconcern.Unacknowledged())) require.NoError(mt, err, "BulkWrite error") diff --git a/mongo/integration/unified/client_operation_execution.go b/mongo/integration/unified/client_operation_execution.go index 17cb284315..24f1d145a5 100644 --- a/mongo/integration/unified/client_operation_execution.go +++ b/mongo/integration/unified/client_operation_execution.go @@ -10,6 +10,7 @@ import ( "context" "fmt" "strconv" + "strings" "time" "go.mongodb.org/mongo-driver/bson" @@ -286,61 +287,66 @@ func executeClientBulkWrite(ctx context.Context, operation *operation) (*operati func appendClientBulkWriteModel(key string, value bson.Raw, model *mongo.ClientWriteModels) error { switch key { case "insertOne": - m, err := createClientInsertOneModel(value) + namespace, m, err := createClientInsertOneModel(value) if err != nil { return err } - model.AppendInsertOne(m) + ns := strings.SplitN(namespace, ".", 2) + model.AppendInsertOne(ns[0], ns[1], m) case "updateOne": - m, err := createClientUpdateOneModel(value) + namespace, m, err := createClientUpdateOneModel(value) if err != nil { return err } - model.AppendUpdateOne(m) + ns := strings.SplitN(namespace, ".", 2) + model.AppendUpdateOne(ns[0], ns[1], m) case "updateMany": - m, err := createClientUpdateManyModel(value) + namespace, m, err := createClientUpdateManyModel(value) if err != nil { return err } - model.AppendUpdateMany(m) + ns := strings.SplitN(namespace, ".", 2) + model.AppendUpdateMany(ns[0], ns[1], m) case "replaceOne": - m, err := createClientReplaceOneModel(value) + namespace, m, err := createClientReplaceOneModel(value) if err != nil { return err } - model.AppendReplaceOne(m) + ns := strings.SplitN(namespace, ".", 2) + model.AppendReplaceOne(ns[0], ns[1], m) case "deleteOne": - m, err := createClientDeleteOneModel(value) + namespace, m, err := createClientDeleteOneModel(value) if err != nil { return err } - model.AppendDeleteOne(m) + ns := strings.SplitN(namespace, ".", 2) + model.AppendDeleteOne(ns[0], ns[1], m) case "deleteMany": - m, err := createClientDeleteManyModel(value) + namespace, m, err := createClientDeleteManyModel(value) if err != nil { return err } - model.AppendDeleteMany(m) + ns := strings.SplitN(namespace, ".", 2) + model.AppendDeleteMany(ns[0], ns[1], m) } return nil } -func createClientInsertOneModel(value bson.Raw) (*mongo.ClientInsertOneModel, error) { +func createClientInsertOneModel(value bson.Raw) (string, *mongo.ClientInsertOneModel, error) { var v struct { Namespace string Document bson.Raw } err := bson.Unmarshal(value, &v) if err != nil { - return nil, err + return "", nil, err } - return &mongo.ClientInsertOneModel{ - Namespace: v.Namespace, - Document: v.Document, + return v.Namespace, &mongo.ClientInsertOneModel{ + Document: v.Document, }, nil } -func createClientUpdateOneModel(value bson.Raw) (*mongo.ClientUpdateOneModel, error) { +func createClientUpdateOneModel(value bson.Raw) (string, *mongo.ClientUpdateOneModel, error) { var v struct { Namespace string Filter bson.Raw @@ -352,17 +358,16 @@ func createClientUpdateOneModel(value bson.Raw) (*mongo.ClientUpdateOneModel, er } err := bson.Unmarshal(value, &v) if err != nil { - return nil, err + return "", nil, err } var hint interface{} if v.Hint != nil { hint, err = createHint(*v.Hint) if err != nil { - return nil, err + return "", nil, err } } model := &mongo.ClientUpdateOneModel{ - Namespace: v.Namespace, Filter: v.Filter, Update: v.Update, Collation: v.Collation, @@ -372,11 +377,11 @@ func createClientUpdateOneModel(value bson.Raw) (*mongo.ClientUpdateOneModel, er if len(v.ArrayFilters) > 0 { model.ArrayFilters = &options.ArrayFilters{Filters: v.ArrayFilters} } - return model, nil + return v.Namespace, model, nil } -func createClientUpdateManyModel(value bson.Raw) (*mongo.ClientUpdateManyModel, error) { +func createClientUpdateManyModel(value bson.Raw) (string, *mongo.ClientUpdateManyModel, error) { var v struct { Namespace string Filter bson.Raw @@ -388,17 +393,16 @@ func createClientUpdateManyModel(value bson.Raw) (*mongo.ClientUpdateManyModel, } err := bson.Unmarshal(value, &v) if err != nil { - return nil, err + return "", nil, err } var hint interface{} if v.Hint != nil { hint, err = createHint(*v.Hint) if err != nil { - return nil, err + return "", nil, err } } model := &mongo.ClientUpdateManyModel{ - Namespace: v.Namespace, Filter: v.Filter, Update: v.Update, Collation: v.Collation, @@ -408,10 +412,10 @@ func createClientUpdateManyModel(value bson.Raw) (*mongo.ClientUpdateManyModel, if len(v.ArrayFilters) > 0 { model.ArrayFilters = &options.ArrayFilters{Filters: v.ArrayFilters} } - return model, nil + return v.Namespace, model, nil } -func createClientReplaceOneModel(value bson.Raw) (*mongo.ClientReplaceOneModel, error) { +func createClientReplaceOneModel(value bson.Raw) (string, *mongo.ClientReplaceOneModel, error) { var v struct { Namespace string Filter bson.Raw @@ -422,17 +426,16 @@ func createClientReplaceOneModel(value bson.Raw) (*mongo.ClientReplaceOneModel, } err := bson.Unmarshal(value, &v) if err != nil { - return nil, err + return "", nil, err } var hint interface{} if v.Hint != nil { hint, err = createHint(*v.Hint) if err != nil { - return nil, err + return "", nil, err } } - return &mongo.ClientReplaceOneModel{ - Namespace: v.Namespace, + return v.Namespace, &mongo.ClientReplaceOneModel{ Filter: v.Filter, Replacement: v.Replacement, Collation: v.Collation, @@ -441,7 +444,7 @@ func createClientReplaceOneModel(value bson.Raw) (*mongo.ClientReplaceOneModel, }, nil } -func createClientDeleteOneModel(value bson.Raw) (*mongo.ClientDeleteOneModel, error) { +func createClientDeleteOneModel(value bson.Raw) (string, *mongo.ClientDeleteOneModel, error) { var v struct { Namespace string Filter bson.Raw @@ -450,24 +453,23 @@ func createClientDeleteOneModel(value bson.Raw) (*mongo.ClientDeleteOneModel, er } err := bson.Unmarshal(value, &v) if err != nil { - return nil, err + return "", nil, err } var hint interface{} if v.Hint != nil { hint, err = createHint(*v.Hint) if err != nil { - return nil, err + return "", nil, err } } - return &mongo.ClientDeleteOneModel{ - Namespace: v.Namespace, + return v.Namespace, &mongo.ClientDeleteOneModel{ Filter: v.Filter, Collation: v.Collation, Hint: hint, }, nil } -func createClientDeleteManyModel(value bson.Raw) (*mongo.ClientDeleteManyModel, error) { +func createClientDeleteManyModel(value bson.Raw) (string, *mongo.ClientDeleteManyModel, error) { var v struct { Namespace string Filter bson.Raw @@ -476,17 +478,16 @@ func createClientDeleteManyModel(value bson.Raw) (*mongo.ClientDeleteManyModel, } err := bson.Unmarshal(value, &v) if err != nil { - return nil, err + return "", nil, err } var hint interface{} if v.Hint != nil { hint, err = createHint(*v.Hint) if err != nil { - return nil, err + return "", nil, err } } - return &mongo.ClientDeleteManyModel{ - Namespace: v.Namespace, + return v.Namespace, &mongo.ClientDeleteManyModel{ Filter: v.Filter, Collation: v.Collation, Hint: hint, diff --git a/mongo/integration/unified/error.go b/mongo/integration/unified/error.go index f69a3da341..2bf4cf380a 100644 --- a/mongo/integration/unified/error.go +++ b/mongo/integration/unified/error.go @@ -183,8 +183,10 @@ func extractErrorDetails(err error) (errorDetails, bool) { } details.labels = converted.Labels case mongo.ClientBulkWriteException: - details.raw = converted.TopLevelError.Raw - details.codes = append(details.codes, int32(converted.TopLevelError.Code)) + if converted.TopLevelError != nil { + details.raw = converted.TopLevelError.Raw + details.codes = append(details.codes, int32(converted.TopLevelError.Code)) + } default: return errorDetails{}, false } diff --git a/mongo/integration/unified/operation.go b/mongo/integration/unified/operation.go index 179cf16793..dc0bbcbb62 100644 --- a/mongo/integration/unified/operation.go +++ b/mongo/integration/unified/operation.go @@ -126,6 +126,8 @@ func (op *operation) run(ctx context.Context, loopDone <-chan struct{}) (*operat return executeListDatabases(ctx, op, false) case "listDatabaseNames": return executeListDatabases(ctx, op, true) + case "clientBulkWrite": + return executeClientBulkWrite(ctx, op) // Database operations case "createCollection": @@ -148,8 +150,6 @@ func (op *operation) run(ctx context.Context, loopDone <-chan struct{}) (*operat return executeAggregate(ctx, op) case "bulkWrite": return executeBulkWrite(ctx, op) - case "clientBulkWrite": - return executeClientBulkWrite(ctx, op) case "countDocuments": return executeCountDocuments(ctx, op) case "createFindCursor": diff --git a/testdata/command-monitoring/unacknowledgedBulkWrite.json b/testdata/command-monitoring/unacknowledgedBulkWrite.json index b30e1540f4..61bb00726c 100644 --- a/testdata/command-monitoring/unacknowledgedBulkWrite.json +++ b/testdata/command-monitoring/unacknowledgedBulkWrite.json @@ -91,7 +91,8 @@ } } } - ] + ], + "ordered": false }, "expectResult": { "insertedCount": { @@ -158,7 +159,7 @@ "command": { "bulkWrite": 1, "errorsOnly": true, - "ordered": true, + "ordered": false, "ops": [ { "insert": 0, diff --git a/testdata/command-monitoring/unacknowledgedBulkWrite.yml b/testdata/command-monitoring/unacknowledgedBulkWrite.yml index 35b8d556fb..2d54525953 100644 --- a/testdata/command-monitoring/unacknowledgedBulkWrite.yml +++ b/testdata/command-monitoring/unacknowledgedBulkWrite.yml @@ -50,6 +50,7 @@ tests: namespace: *namespace filter: { _id: 3 } update: { $set: { x: 333 } } + ordered: false expectResult: insertedCount: $$unsetOrMatches: 0 @@ -89,7 +90,7 @@ tests: command: bulkWrite: 1 errorsOnly: true - ordered: true + ordered: false ops: - insert: 0 document: { _id: 4, x: 44 } diff --git a/x/mongo/driver/operation.go b/x/mongo/driver/operation.go index f16afe1da6..2eb9a1be70 100644 --- a/x/mongo/driver/operation.go +++ b/x/mongo/driver/operation.go @@ -554,12 +554,12 @@ func (op Operation) Execute(ctx context.Context) error { retries = -1 } } - } - // If context is a Timeout context, automatically set retries to -1 (infinite) if retrying is - // enabled. - retryEnabled := op.RetryMode != nil && op.RetryMode.Enabled() - if csot.IsTimeoutContext(ctx) && retryEnabled { - retries = -1 + + // If context is a Timeout context, automatically set retries to -1 (infinite) if retrying is + // enabled. + if csot.IsTimeoutContext(ctx) && op.RetryMode.Enabled() { + retries = -1 + } } var srvr Server @@ -693,14 +693,10 @@ func (op Operation) Execute(ctx context.Context) error { // Calling IncrementTxnNumber() for server descriptions or topologies that do not // support retries (e.g. standalone topologies) will cause server errors. Only do this // check for the first attempt to keep retried writes in the same transaction. - if retrySupported && op.RetryMode != nil && op.Type == Write && op.Client != nil { - op.Client.RetryWrite = false - if op.RetryMode.Enabled() { - op.Client.RetryWrite = true - if !op.Client.Committing && !op.Client.Aborting { - op.Client.IncrementTxnNumber() - } - } + retryEnabled := op.RetryMode != nil && op.RetryMode.Enabled() + needToIncrease := op.Client != nil && !op.Client.Committing && !op.Client.Aborting + if retrySupported && op.Type == Write && retryEnabled && needToIncrease { + op.Client.IncrementTxnNumber() } first = false @@ -726,6 +722,7 @@ func (op Operation) Execute(ctx context.Context) error { if err != nil { return err } + retryEnabled := op.RetryMode != nil && op.RetryMode.Enabled() // set extra data and send event if possible startedInfo.connID = conn.ID() @@ -835,7 +832,7 @@ func (op Operation) Execute(ctx context.Context) error { // If retries are supported for the current operation on the first server description, // the error is considered retryable, and there are retries remaining (negative retries // means retry indefinitely), then retry the operation. - if retrySupported && retryableErr && retries != 0 { + if retrySupported && retryEnabled && retryableErr && retries != 0 { if op.Client != nil && op.Client.Committing { // Apply majority write concern for retries op.Client.UpdateCommitTransactionWriteConcern() @@ -958,7 +955,7 @@ func (op Operation) Execute(ctx context.Context) error { // If retries are supported for the current operation on the first server description, // the error is considered retryable, and there are retries remaining (negative retries // means retry indefinitely), then retry the operation. - if retrySupported && retryableErr && retries != 0 { + if retrySupported && retryEnabled && retryableErr && retries != 0 { if op.Client != nil && op.Client.Committing { // Apply majority write concern for retries op.Client.UpdateCommitTransactionWriteConcern() @@ -1037,10 +1034,9 @@ func (op Operation) Execute(ctx context.Context) error { // the session isn't nil, and client retries are enabled, increment the txn number. // Calling IncrementTxnNumber() for server descriptions or topologies that do not // support retries (e.g. standalone topologies) will cause server errors. - if retrySupported && op.Client != nil && op.RetryMode != nil { - if op.RetryMode.Enabled() { - op.Client.IncrementTxnNumber() - } + if retrySupported && op.Client != nil && retryEnabled { + op.Client.IncrementTxnNumber() + // Reset the retries number for RetryOncePerCommand unless context is a Timeout context, in // which case retries should remain as -1 (as many times as possible). if *op.RetryMode == RetryOncePerCommand && !csot.IsTimeoutContext(ctx) { @@ -1049,9 +1045,7 @@ func (op Operation) Execute(ctx context.Context) error { } currIndex += startedInfo.processedBatches op.Batches.AdvanceBatches(startedInfo.processedBatches) - if op.Batches.Size() > 0 { - continue - } + continue } break } @@ -1250,7 +1244,7 @@ func (op Operation) createLegacyHandshakeWireMessage( return dst, nil, err } - dst, err = op.addSession(dst, desc) + dst, err = op.addSession(dst, desc, false) if err != nil { return dst, nil, err } @@ -1266,9 +1260,10 @@ func (op Operation) createLegacyHandshakeWireMessage( dst, _ = bsoncore.AppendDocumentEnd(dst, idx) if len(rp) > 0 { + idx = wrapper var err error dst = bsoncore.AppendDocumentElement(dst, "$readPreference", rp) - dst, err = bsoncore.AppendDocumentEnd(dst, wrapper) + dst, err = bsoncore.AppendDocumentEnd(dst, idx) if err != nil { return dst, nil, err } @@ -1309,7 +1304,11 @@ func (op Operation) createMsgWireMessage( if err != nil { return dst, nil, err } - dst, err = op.addSession(dst, desc) + retryWrite := false + if op.retryable(conn.Description()) && op.RetryMode != nil && op.RetryMode.Enabled() { + retryWrite = true + } + dst, err = op.addSession(dst, desc, retryWrite) if err != nil { return dst, nil, err } @@ -1356,9 +1355,10 @@ func (op Operation) createWireMessage( var wmindex int32 var err error - fIdx := len(dst) + fIdx := -1 isLegacy := isLegacyHandshake(op, desc) - if isLegacy { + switch { + case isLegacy: cmdFn := func(dst []byte, desc description.SelectedServer) ([]byte, error) { info.processedBatches, dst, err = op.addLegacyCommandFields(dst, desc) return dst, err @@ -1366,7 +1366,7 @@ func (op Operation) createWireMessage( requestID := wiremessage.NextRequestID() wmindex, dst = wiremessage.AppendHeaderStart(dst, requestID, 0, wiremessage.OpQuery) dst, info.cmd, err = op.createLegacyHandshakeWireMessage(maxTimeMS, dst, desc, cmdFn) - } else if op.shouldEncrypt() { + case op.shouldEncrypt(): if desc.WireVersion.Max < cryptMinWireVersion { return dst, false, info, errors.New("auto-encryption requires a MongoDB version of 4.2") } @@ -1375,26 +1375,47 @@ func (op Operation) createWireMessage( return dst, err } wmindex, dst = wiremessage.AppendHeaderStart(dst, requestID, 0, wiremessage.OpMsg) + fIdx = len(dst) dst, info.cmd, err = op.createMsgWireMessage(maxTimeMS, dst, desc, conn, cmdFn) - } else { + default: wmindex, dst = wiremessage.AppendHeaderStart(dst, requestID, 0, wiremessage.OpMsg) - dst, info.cmd, err = op.createMsgWireMessage(maxTimeMS, dst, desc, conn, op.CommandFn) - if err == nil && op.Batches != nil { + fIdx = len(dst) + appendBatches := func(dst []byte) ([]byte, error) { var processedBatches int dsOffset := len(dst) processedBatches, dst, err = op.Batches.AppendBatchSequence(dst, int(desc.MaxBatchCount), int(desc.MaxDocumentSize), int(desc.MaxMessageSize)) - if err == nil { - info.processedBatches = processedBatches - info.documentSequence = make([]byte, 0) - for b := dst[dsOffset:]; len(b) > 0; /* nothing */ { - var seq []byte - var ok bool - seq, b, ok = wiremessage.DocumentSequenceToArray(b) - if !ok { - break - } - info.documentSequence = append(info.documentSequence, seq...) + if err != nil { + return dst, err + } + info.processedBatches = processedBatches + info.documentSequence = make([]byte, 0) + for b := dst[dsOffset:]; len(b) > 0; /* nothing */ { + var seq []byte + var ok bool + seq, b, ok = wiremessage.DocumentSequenceToArray(b) + if !ok { + break } + info.documentSequence = append(info.documentSequence, seq...) + } + return dst, nil + } + switch op.Batches.(type) { + case *Batches: + dst, info.cmd, err = op.createMsgWireMessage(maxTimeMS, dst, desc, conn, op.CommandFn) + if err == nil && op.Batches != nil { + dst, err = appendBatches(dst) + } + default: + var batches []byte + if op.Batches != nil { + batches, err = appendBatches(batches) + } + if err == nil { + dst, info.cmd, err = op.createMsgWireMessage(maxTimeMS, dst, desc, conn, op.CommandFn) + } + if err == nil && len(batches) > 0 { + dst = append(dst, batches...) } } } @@ -1407,7 +1428,7 @@ func (op Operation) createWireMessage( // aren't batching or we are encoding the last batch. unacknowledged := op.WriteConcern != nil && !writeconcern.AckWrite(op.WriteConcern) batching := op.Batches != nil && op.Batches.Size() > info.processedBatches - if !isLegacy && unacknowledged && !batching { + if fIdx > 0 && unacknowledged && !batching { dst[fIdx] |= byte(wiremessage.MoreToCome) moreToCome = true } @@ -1562,7 +1583,7 @@ func (op Operation) addWriteConcern(dst []byte, desc description.SelectedServer) return append(bsoncore.AppendHeader(dst, t, "writeConcern"), data...), nil } -func (op Operation) addSession(dst []byte, desc description.SelectedServer) ([]byte, error) { +func (op Operation) addSession(dst []byte, desc description.SelectedServer, retryWrite bool) ([]byte, error) { client := op.Client // If the operation is defined for an explicit session but the server @@ -1580,7 +1601,7 @@ func (op Operation) addSession(dst []byte, desc description.SelectedServer) ([]b dst = bsoncore.AppendDocumentElement(dst, "lsid", client.SessionID) var addedTxnNumber bool - if op.Type == Write && client.RetryWrite { + if op.Type == Write && retryWrite { addedTxnNumber = true dst = bsoncore.AppendInt64Element(dst, "txnNumber", op.Client.TxnNumber) } diff --git a/x/mongo/driver/session/client_session.go b/x/mongo/driver/session/client_session.go index eff27bfe33..5403f49c20 100644 --- a/x/mongo/driver/session/client_session.go +++ b/x/mongo/driver/session/client_session.go @@ -112,7 +112,6 @@ type Client struct { RetryingCommit bool Committing bool Aborting bool - RetryWrite bool RetryRead bool Snapshot bool From cb5e8aa64963786c6e0c0ab5665329ed54f7f6b7 Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Fri, 18 Oct 2024 00:26:00 -0400 Subject: [PATCH 11/14] WIP --- mongo/client_bulk_write.go | 68 +++++-- mongo/integration/crud_prose_test.go | 284 +++++++++++++++++++++++---- x/mongo/driver/operation.go | 14 +- 3 files changed, 304 insertions(+), 62 deletions(-) diff --git a/mongo/client_bulk_write.go b/mongo/client_bulk_write.go index 19d43684b8..5762cc8033 100644 --- a/mongo/client_bulk_write.go +++ b/mongo/client_bulk_write.go @@ -73,8 +73,31 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { Authenticator: bw.client.authenticator, Name: "bulkWrite", }.Execute(ctx) - if err != nil && errors.Is(err, driver.ErrUnacknowledgedWrite) { - return nil + var exception *ClientBulkWriteException + switch tt := err.(type) { + case CommandError: + exception = &ClientBulkWriteException{ + TopLevelError: &WriteError{ + Code: int(tt.Code), + Message: tt.Message, + Raw: tt.Raw, + }, + } + default: + if errors.Is(err, driver.ErrUnacknowledgedWrite) { + err = nil + } + } + if len(batches.writeConcernErrors) > 0 || len(batches.writeErrors) > 0 { + if exception == nil { + exception = new(ClientBulkWriteException) + } + exception.WriteConcernErrors = batches.writeConcernErrors + exception.WriteErrors = batches.writeErrors + } + if exception != nil { + exception.PartialResult = batches.result + return *exception } return err } @@ -219,7 +242,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD return 0, dst, io.EOF } - mb.cursorHandlers = make([]func(*cursorInfo, bson.Raw) bool, len(mb.models)) + mb.cursorHandlers = mb.cursorHandlers[:0] mb.newIDMap = make(map[int]interface{}) nsMap := make(map[string]int) @@ -240,6 +263,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD opsIdx, dst := fn.appendStart(dst, "ops") nsIdx, nsDst := fn.appendStart(nil, "nsInfo") + totalSize -= 1000 size := (len(dst) - l) * 2 var n int for i := mb.offset; i < len(mb.models); i++ { @@ -254,7 +278,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD var err error switch model := mb.models[i].model.(type) { case *ClientInsertOneModel: - mb.cursorHandlers[i] = mb.appendInsertResult + mb.cursorHandlers = append(mb.cursorHandlers, mb.appendInsertResult) var id interface{} id, doc, err = (&clientInsertDoc{ namespace: nsIdx, @@ -265,7 +289,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD } mb.newIDMap[i] = id case *ClientUpdateOneModel: - mb.cursorHandlers[i] = mb.appendUpdateResult + mb.cursorHandlers = append(mb.cursorHandlers, mb.appendUpdateResult) doc, err = (&clientUpdateDoc{ namespace: nsIdx, filter: model.Filter, @@ -279,7 +303,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD }).marshal(mb.client.bsonOpts, mb.client.registry) case *ClientUpdateManyModel: canRetry = false - mb.cursorHandlers[i] = mb.appendUpdateResult + mb.cursorHandlers = append(mb.cursorHandlers, mb.appendUpdateResult) doc, err = (&clientUpdateDoc{ namespace: nsIdx, filter: model.Filter, @@ -292,7 +316,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD checkDollarKey: true, }).marshal(mb.client.bsonOpts, mb.client.registry) case *ClientReplaceOneModel: - mb.cursorHandlers[i] = mb.appendUpdateResult + mb.cursorHandlers = append(mb.cursorHandlers, mb.appendUpdateResult) doc, err = (&clientUpdateDoc{ namespace: nsIdx, filter: model.Filter, @@ -305,7 +329,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD checkDollarKey: false, }).marshal(mb.client.bsonOpts, mb.client.registry) case *ClientDeleteOneModel: - mb.cursorHandlers[i] = mb.appendDeleteResult + mb.cursorHandlers = append(mb.cursorHandlers, mb.appendDeleteResult) doc, err = (&clientDeleteDoc{ namespace: nsIdx, filter: model.Filter, @@ -315,7 +339,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD }).marshal(mb.client.bsonOpts, mb.client.registry) case *ClientDeleteManyModel: canRetry = false - mb.cursorHandlers[i] = mb.appendDeleteResult + mb.cursorHandlers = append(mb.cursorHandlers, mb.appendDeleteResult) doc, err = (&clientDeleteDoc{ namespace: nsIdx, filter: model.Filter, @@ -323,14 +347,19 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD hint: model.Hint, multi: true, }).marshal(mb.client.bsonOpts, mb.client.registry) + default: + mb.cursorHandlers = append(mb.cursorHandlers, nil) } if err != nil { return 0, nil, err } - length := len(doc) + len(ns) + length := len(doc) if length > maxDocSize { break } + if !exists { + length += len(ns) + } size += length if size >= totalSize { break @@ -369,7 +398,6 @@ func (mb *modelBatches) processResponse(ctx context.Context, resp bsoncore.Docum mb.writeConcernErrors = append(mb.writeConcernErrors, *wce) } } - // closeImplicitSession(sess) if len(resp) == 0 { return nil } @@ -435,8 +463,9 @@ func (mb *modelBatches) processResponse(ctx context.Context, resp bsoncore.Docum if err != nil { return err } + isOrdered := mb.ordered == nil || *mb.ordered fmt.Println("ProcessResponse toplevelerror", res.Ok, res.NErrors, res.Code, res.Errmsg) - if writeCmdErr.WriteConcernError != nil || !ok || !res.Ok || res.NErrors > 0 { + if isOrdered && (writeCmdErr.WriteConcernError != nil || !ok || !res.Ok || res.NErrors > 0) { exception := ClientBulkWriteException{ WriteConcernErrors: mb.writeConcernErrors, WriteErrors: mb.writeErrors, @@ -455,48 +484,51 @@ func (mb *modelBatches) processResponse(ctx context.Context, resp bsoncore.Docum } func (mb *modelBatches) appendDeleteResult(cur *cursorInfo, raw bson.Raw) bool { + idx := int(cur.Idx) + mb.offset if err := cur.extractError(); err != nil { err.Raw = raw if mb.writeErrors == nil { mb.writeErrors = make(map[int]WriteError) } - mb.writeErrors[int(cur.Idx)] = *err + mb.writeErrors[idx] = *err return false } if mb.result.DeleteResults == nil { mb.result.DeleteResults = make(map[int]ClientDeleteResult) } - mb.result.DeleteResults[int(cur.Idx)] = ClientDeleteResult{int64(cur.N)} + mb.result.DeleteResults[idx] = ClientDeleteResult{int64(cur.N)} return true } func (mb *modelBatches) appendInsertResult(cur *cursorInfo, raw bson.Raw) bool { + idx := int(cur.Idx) + mb.offset if err := cur.extractError(); err != nil { err.Raw = raw if mb.writeErrors == nil { mb.writeErrors = make(map[int]WriteError) } - mb.writeErrors[int(cur.Idx)] = *err + mb.writeErrors[idx] = *err return false } if mb.result.InsertResults == nil { mb.result.InsertResults = make(map[int]ClientInsertResult) } - mb.result.InsertResults[int(cur.Idx)] = ClientInsertResult{mb.newIDMap[int(cur.Idx)]} + mb.result.InsertResults[idx] = ClientInsertResult{mb.newIDMap[idx]} return true } func (mb *modelBatches) appendUpdateResult(cur *cursorInfo, raw bson.Raw) bool { + idx := int(cur.Idx) + mb.offset if err := cur.extractError(); err != nil { err.Raw = raw if mb.writeErrors == nil { mb.writeErrors = make(map[int]WriteError) } - mb.writeErrors[int(cur.Idx)] = *err + mb.writeErrors[idx] = *err return false } @@ -512,7 +544,7 @@ func (mb *modelBatches) appendUpdateResult(cur *cursorInfo, raw bson.Raw) bool { if cur.Upserted != nil { result.UpsertedID = cur.Upserted.ID } - mb.result.UpdateResults[int(cur.Idx)] = result + mb.result.UpdateResults[idx] = result return true } diff --git a/mongo/integration/crud_prose_test.go b/mongo/integration/crud_prose_test.go index ba8e9d6606..c2ce360537 100644 --- a/mongo/integration/crud_prose_test.go +++ b/mongo/integration/crud_prose_test.go @@ -22,6 +22,7 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/writeconcern" "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" + "go.mongodb.org/mongo-driver/x/mongo/driver" ) func TestWriteErrorsWithLabels(t *testing.T) { @@ -442,11 +443,11 @@ func TestClientBulkWrite(t *testing.T) { }) } result, err := mt.Client.BulkWrite(context.Background(), models) - require.NoError(mt, err, "BulkWrite error") - assert.Equal(mt, hello.MaxWriteBatchSize+1, int(result.InsertedCount), "InsertedCount expected to be %d", hello.MaxWriteBatchSize+1) + require.NoError(mt, err, "BulkWrite error", err) + assert.Equal(mt, hello.MaxWriteBatchSize+1, int(result.InsertedCount), "expected InsertedCount: %d, got %d", hello.MaxWriteBatchSize+1, int(result.InsertedCount)) require.Len(mt, opsCnt, 2, "expected 2 bulkWrite commands") - assert.Equal(mt, hello.MaxWriteBatchSize, opsCnt[0], "the length of firstEvent.command.ops is %d", hello.MaxWriteBatchSize) - assert.Equal(mt, 1, opsCnt[1], "the length of secondEvent.command.ops is 1") + assert.Equal(mt, hello.MaxWriteBatchSize, opsCnt[0], "expected %d firstEvent.command.ops, got: %d", hello.MaxWriteBatchSize, opsCnt[0]) + assert.Equal(mt, 1, opsCnt[1], "expected %d secondEvent.command.ops, got %d", 1, opsCnt[1]) }) mt.Run("input with greater than maxMessageSizeBytes", func(mt *mtest.T) { @@ -476,11 +477,11 @@ func TestClientBulkWrite(t *testing.T) { }) } result, err := mt.Client.BulkWrite(context.Background(), models) - require.NoError(mt, err, "BulkWrite error") - assert.Equal(mt, numModels, int(result.InsertedCount), "InsertedCount expected to be %d", numModels) + require.NoError(mt, err, "BulkWrite error", err) + assert.Equal(mt, numModels, int(result.InsertedCount), "expected InsertedCount: %d, got: %d", numModels, int(result.InsertedCount)) require.Len(mt, opsCnt, 2, "expected 2 bulkWrite commands") - assert.Equal(mt, numModels-1, opsCnt[0], "the length of firstEvent.command.ops is %d", numModels-1) - assert.Equal(mt, 1, opsCnt[1], "the length of secondEvent.command.ops is 1") + assert.Equal(mt, numModels-1, opsCnt[0], "expected %d firstEvent.command.ops, got %d", numModels-1, opsCnt[0]) + assert.Equal(mt, 1, opsCnt[1], "expected %d secondEvent.command.ops, got: %d", 1, opsCnt[1]) }) mt.Run("bulkWrite collects WriteConcernErrors across batches", func(mt *mtest.T) { @@ -488,7 +489,8 @@ func TestClientBulkWrite(t *testing.T) { var hello struct { MaxWriteBatchSize int } - require.NoError(mt, mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello), "Hello error") + err := mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello) + require.NoError(mt, err, "Hello error") mt.SetFailPoint(mtest.FailPoint{ ConfigureFailPoint: "failCommand", @@ -511,14 +513,14 @@ func TestClientBulkWrite(t *testing.T) { Document: bson.D{{"a", "b"}}, }) } - _, err := mt.Client.BulkWrite(context.Background(), models) + _, err = mt.Client.BulkWrite(context.Background(), models) require.Error(mt, err) bwe, ok := err.(mongo.ClientBulkWriteException) - require.True(mt, ok, "expected a BulkWriteException, got %T", err) - assert.Len(mt, bwe.WriteConcernErrors, 2, "expected 2 writeConcernErrors") + require.True(mt, ok, "expected a BulkWriteException, got %T: %v", err, err) + assert.Len(mt, bwe.WriteConcernErrors, 2, "expected writeConcernErrors: %d, got: %d", 2, len(bwe.WriteConcernErrors)) require.NotNil(mt, bwe.PartialResult) assert.Equal(mt, hello.MaxWriteBatchSize+1, int(bwe.PartialResult.InsertedCount), - "InsertedCount expected to be %d", hello.MaxWriteBatchSize+1) + "expected InsertedCount: %d, got: %d", hello.MaxWriteBatchSize+1, int(bwe.PartialResult.InsertedCount)) }) mt.Run("bulkWrite handles individual WriteErrors across batches", func(mt *mtest.T) { @@ -550,25 +552,25 @@ func TestClientBulkWrite(t *testing.T) { }) } - mt.Run("Unordered", func(mt *mtest.T) { + mt.Run("unordered", func(mt *mtest.T) { eventCnt = 0 mt.ResetClient(options.Client().SetMonitor(monitor)) _, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetOrdered(false)) require.Error(mt, err) bwe, ok := err.(mongo.ClientBulkWriteException) - require.True(mt, ok, "expected a BulkWriteException, got %T", err) + require.True(mt, ok, "expected a BulkWriteException, got %T: %v", err, err) assert.Len(mt, bwe.WriteErrors, hello.MaxWriteBatchSize+1, "expected %d writeErrors, got %d", hello.MaxWriteBatchSize+1, len(bwe.WriteErrors)) - require.Equal(mt, 2, eventCnt, "expected 2 bulkWrite commands, got %d", eventCnt) + require.Equal(mt, 2, eventCnt, "expected %d bulkWrite commands, got %d", 2, eventCnt) }) - mt.Run("Ordered", func(mt *mtest.T) { + mt.Run("ordered", func(mt *mtest.T) { eventCnt = 0 mt.ResetClient(options.Client().SetMonitor(monitor)) _, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetOrdered(true)) require.Error(mt, err) bwe, ok := err.(mongo.ClientBulkWriteException) - require.True(mt, ok, "expected a BulkWriteException, got %T", err) - assert.Len(mt, bwe.WriteErrors, 1, "expected %d writeErrors, got %d", 1, len(bwe.WriteErrors)) - require.Equal(mt, 1, eventCnt, "expected 1 bulkWrite commands, got %d", eventCnt) + require.True(mt, ok, "expected a BulkWriteException, got %T: %v", err, err) + assert.Len(mt, bwe.WriteErrors, 1, "expected writeErrors: %d, got: %d", 1, len(bwe.WriteErrors)) + require.Equal(mt, 1, eventCnt, "expected %d bulkWrite commands, got %d", 1, eventCnt) }) }) @@ -577,11 +579,11 @@ func TestClientBulkWrite(t *testing.T) { err := coll.Drop(context.Background()) require.NoError(mt, err, "Drop error") - var getMoreCalled bool + var getMoreCalled int monitor := &event.CommandMonitor{ Started: func(_ context.Context, e *event.CommandStartedEvent) { if e.CommandName == "getMore" { - getMoreCalled = true + getMoreCalled++ } }, } @@ -605,9 +607,9 @@ func TestClientBulkWrite(t *testing.T) { }) result, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetVerboseResults(true)) require.NoError(mt, err, "BulkWrite error") - assert.Equal(mt, 2, int(result.UpsertedCount), "InsertedCount expected to be %d, got %d", 2, result.UpsertedCount) - assert.Len(mt, result.UpdateResults, 2, "expected %d UpdateResults, got %d", 2, len(result.UpdateResults)) - assert.True(mt, getMoreCalled, "the getMore was not called") + assert.Equal(mt, 2, int(result.UpsertedCount), "expected InsertedCount: %d, got: %d", 2, result.UpsertedCount) + assert.Len(mt, result.UpdateResults, 2, "expected UpdateResults: %d, got: %d", 2, len(result.UpdateResults)) + assert.Equal(mt, 1, getMoreCalled, "expected %d getMore call, got %d", 1, getMoreCalled) }) mt.Run("bulkWrite handles a cursor requiring a getMore within a transaction", func(mt *mtest.T) { @@ -615,11 +617,11 @@ func TestClientBulkWrite(t *testing.T) { err := coll.Drop(context.Background()) require.NoError(mt, err, "Drop error") - var getMoreCalled bool + var getMoreCalled int monitor := &event.CommandMonitor{ Started: func(_ context.Context, e *event.CommandStartedEvent) { if e.CommandName == "getMore" { - getMoreCalled = true + getMoreCalled++ } }, } @@ -650,12 +652,69 @@ func TestClientBulkWrite(t *testing.T) { require.NoError(mt, err, "BulkWrite error") cbwResult, ok := result.(*mongo.ClientBulkWriteResult) require.True(mt, ok, "expected a ClientBulkWriteResult") - assert.Equal(mt, 2, int(cbwResult.UpsertedCount), "InsertedCount expected to be %d, got %d", 2, cbwResult.UpsertedCount) - assert.Len(mt, cbwResult.UpdateResults, 2, "expected %d UpdateResults, got %d", 2, len(cbwResult.UpdateResults)) - assert.True(mt, getMoreCalled, "the getMore was not called") + assert.Equal(mt, 2, int(cbwResult.UpsertedCount), "expected InsertedCount: %d, got: %d", 2, cbwResult.UpsertedCount) + assert.Len(mt, cbwResult.UpdateResults, 2, "expected UpdateResults: %d, got: %d", 2, len(cbwResult.UpdateResults)) + assert.Equal(mt, 1, getMoreCalled, "expected %d getMore call, got %d", 1, getMoreCalled) }) - mt.Run("bulkWrite handles a getMore error", func(_ *mtest.T) { + mt.Run("bulkWrite handles a getMore error", func(mt *mtest.T) { + var getMoreCalled int + var killCursorsCalled int + monitor := &event.CommandMonitor{ + Started: func(_ context.Context, e *event.CommandStartedEvent) { + switch e.CommandName { + case "getMore": + getMoreCalled++ + case "killCursors": + killCursorsCalled++ + } + }, + } + mt.ResetClient(options.Client().SetMonitor(monitor)) + var hello struct { + MaxBsonObjectSize int + } + err := mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello) + require.NoError(mt, err, "Hello error") + + mt.SetFailPoint(mtest.FailPoint{ + ConfigureFailPoint: "failCommand", + Mode: mtest.FailPointMode{ + Times: 1, + }, + Data: mtest.FailPointData{ + FailCommands: []string{"getMore"}, + ErrorCode: 8, + }, + }) + + coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, true) + err = coll.Drop(context.Background()) + require.NoError(mt, err, "Drop error") + + upsert := true + models := (&mongo.ClientWriteModels{}). + AppendUpdateOne("db", "coll", &mongo.ClientUpdateOneModel{ + Filter: bson.D{{"_id", strings.Repeat("a", hello.MaxBsonObjectSize/2)}}, + Update: bson.D{{"$set", bson.D{{"x", 1}}}}, + Upsert: &upsert, + }). + AppendUpdateOne("db", "coll", &mongo.ClientUpdateOneModel{ + Filter: bson.D{{"_id", strings.Repeat("b", hello.MaxBsonObjectSize/2)}}, + Update: bson.D{{"$set", bson.D{{"x", 1}}}}, + Upsert: &upsert, + }) + _, err = mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetVerboseResults(true)) + assert.Error(mt, err) + bwe, ok := err.(mongo.ClientBulkWriteException) + require.True(mt, ok, "expected a BulkWriteException, got %T: %v", err, err) + require.NotNil(mt, bwe.PartialResult) + require.NotNil(mt, bwe.TopLevelError) + assert.Equal(mt, 8, bwe.TopLevelError.Code, "expected top level error code: %d, got; %d", 8, bwe.TopLevelError.Code) + assert.Equal(mt, int64(2), bwe.PartialResult.UpsertedCount, "expected UpsertedCount: %d, got: %d", 2, bwe.PartialResult.UpsertedCount) + assert.Len(mt, bwe.PartialResult.UpdateResults, 1, "expected UpdateResults: %d, got: %d", 1, len(bwe.PartialResult.UpdateResults)) + assert.Equal(mt, 1, getMoreCalled, "expected %d getMore call, got %d", 1, getMoreCalled) + assert.Equal(mt, 1, killCursorsCalled, "expected %d killCursors call, got %d", 1, killCursorsCalled) }) mt.Run("bulkWrite returns error for unacknowledged too-large insert", func(mt *mtest.T) { @@ -663,14 +722,165 @@ func TestClientBulkWrite(t *testing.T) { var hello struct { MaxBsonObjectSize int } - require.NoError(mt, mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello), "Hello error") + err := mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello) + require.NoError(mt, err, "Hello error") + mt.Run("insert", func(mt *mtest.T) { + models := (&mongo.ClientWriteModels{}). + AppendInsertOne("db", "coll", &mongo.ClientInsertOneModel{ + Document: bson.D{{"a", strings.Repeat("b", hello.MaxBsonObjectSize)}}, + }) + _, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetOrdered(false).SetWriteConcern(writeconcern.Unacknowledged())) + require.EqualError(mt, err, driver.ErrDocumentTooLarge.Error()) + }) + mt.Run("replace", func(mt *mtest.T) { + models := (&mongo.ClientWriteModels{}). + AppendReplaceOne("db", "coll", &mongo.ClientReplaceOneModel{ + Filter: bson.D{}, + Replacement: bson.D{{"a", strings.Repeat("b", hello.MaxBsonObjectSize)}}, + }) + _, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetOrdered(false).SetWriteConcern(writeconcern.Unacknowledged())) + require.EqualError(mt, err, driver.ErrDocumentTooLarge.Error()) + }) + }) + + mt.Run("bulkWrite batch splits when the addition of a new namespace exceeds the maximum message size", func(mt *mtest.T) { + var bwCmd []bsoncore.Document + monitor := &event.CommandMonitor{ + Started: func(_ context.Context, e *event.CommandStartedEvent) { + if e.CommandName == "bulkWrite" { + bwCmd = append(bwCmd, bsoncore.Document(e.Command)) + } + }, + } + var hello struct { + MaxBsonObjectSize int + MaxMessageSizeBytes int + } + err := mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello) + require.NoError(mt, err, "Hello error") + + newModels := func() (int, *mongo.ClientWriteModels) { + maxBsonObjectSize := hello.MaxBsonObjectSize + opsBytes := hello.MaxMessageSizeBytes - 1122 + numModels := opsBytes / maxBsonObjectSize + + models := &mongo.ClientWriteModels{} + n := numModels + for i := 0; i < n; i++ { + models. + AppendInsertOne("db", "coll", &mongo.ClientInsertOneModel{ + Document: bson.D{{"a", strings.Repeat("b", maxBsonObjectSize-57)}}, + }) + } + if remainderBytes := opsBytes % maxBsonObjectSize; remainderBytes > 217 { + n++ + models. + AppendInsertOne("db", "coll", &mongo.ClientInsertOneModel{ + Document: bson.D{{"a", strings.Repeat("b", remainderBytes-57)}}, + }) + } + return n, models + } + mt.Run("no batch-splitting required", func(mt *mtest.T) { + bwCmd = bwCmd[:0] + mt.ResetClient(options.Client().SetMonitor(monitor)) + + numModels, models := newModels() + models.AppendInsertOne("db", "coll", &mongo.ClientInsertOneModel{ + Document: bson.D{{"a", "b"}}, + }) + result, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite()) + require.NoError(mt, err) + assert.Equal(mt, numModels+1, int(result.InsertedCount), "expected insertedCound: %d, got: %d", numModels+1, result.InsertedCount) + require.Len(mt, bwCmd, 1, "expected %d bulkWrite call, got %d", 1, len(bwCmd)) + + var cmd struct { + Ops []bson.D + NsInfo []struct { + Ns string + } + } + err = bson.Unmarshal(bwCmd[0], &cmd) + require.NoError(mt, err) + assert.Len(mt, cmd.Ops, numModels+1, "expected ops: %d, got: %d", numModels+1, len(cmd.Ops)) + require.Len(mt, cmd.NsInfo, 1, "expected %d nsInfo, got: %d", 1, len(cmd.NsInfo)) + assert.Equal(mt, "db.coll", cmd.NsInfo[0].Ns, "expected namespace: %s, got: %s", "db.coll", cmd.NsInfo[0].Ns) + }) + mt.Run("batch-splitting required", func(mt *mtest.T) { + bwCmd = bwCmd[:0] + mt.ResetClient(options.Client().SetMonitor(monitor)) + + coll := strings.Repeat("c", 200) + numModels, models := newModels() + models.AppendInsertOne("db", coll, &mongo.ClientInsertOneModel{ + Document: bson.D{{"a", "b"}}, + }) + result, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite()) + require.NoError(mt, err) + assert.Equal(mt, numModels+1, int(result.InsertedCount), "expected insertedCound: %d, got: %d", numModels+1, result.InsertedCount) + require.Len(mt, bwCmd, 2, "expected %d bulkWrite call, got %d", 2, len(bwCmd)) + + type cmd struct { + Ops []bson.D + NsInfo []struct { + Ns string + } + } + var c1 cmd + err = bson.Unmarshal(bwCmd[0], &c1) + require.NoError(mt, err) + assert.Len(mt, c1.Ops, numModels, "expected ops: %d, got: %d", numModels, len(c1.Ops)) + require.Len(mt, c1.NsInfo, 1, "expected %d nsInfo, got: %d", 1, len(c1.NsInfo)) + assert.Equal(mt, "db.coll", c1.NsInfo[0].Ns, "expected namespace: %s, got: %s", "db.coll", c1.NsInfo[0].Ns) + + var c2 cmd + err = bson.Unmarshal(bwCmd[1], &c2) + require.NoError(mt, err) + assert.Len(mt, c2.Ops, 1, "expected ops: %d, got: %d", 1, len(c2.Ops)) + require.Len(mt, c2.NsInfo, 1, "expected %d nsInfo, got: %d", 1, len(c2.NsInfo)) + assert.Equal(mt, "db."+coll, c2.NsInfo[0].Ns, "expected namespace: %s, got: %s", "db."+coll, c2.NsInfo[0].Ns) + }) + }) + + mt.Run("bulkWrite returns an error if no operations can be added to ops", func(mt *mtest.T) { + var hello struct { + MaxMessageSizeBytes int + } + err := mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello) + require.NoError(mt, err, "Hello error") + mt.Run("document too large", func(mt *mtest.T) { + models := (&mongo.ClientWriteModels{}). + AppendInsertOne("db", "coll", &mongo.ClientInsertOneModel{ + Document: bson.D{{"a", strings.Repeat("b", hello.MaxMessageSizeBytes)}}, + }) + _, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetOrdered(false).SetWriteConcern(writeconcern.Unacknowledged())) + require.EqualError(mt, err, driver.ErrDocumentTooLarge.Error()) + }) + mt.Run("document too large", func(mt *mtest.T) { + models := (&mongo.ClientWriteModels{}). + AppendInsertOne("db", strings.Repeat("c", hello.MaxMessageSizeBytes), &mongo.ClientInsertOneModel{ + Document: bson.D{{"a", "b"}}, + }) + _, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetOrdered(false).SetWriteConcern(writeconcern.Unacknowledged())) + require.EqualError(mt, err, driver.ErrDocumentTooLarge.Error()) + }) + }) + + mt.Run("bulkWrite returns an error if auto-encryption is configured", func(mt *mtest.T) { + autoEncryptionOpts := options.AutoEncryption(). + SetKeyVaultNamespace("db.coll"). + SetKmsProviders(map[string]map[string]interface{}{ + "aws": { + "accessKeyId": "foo", + "secretAccessKey": "bar", + }, + }) + mt.ResetClient(options.Client().SetAutoEncryptionOptions(autoEncryptionOpts)) models := (&mongo.ClientWriteModels{}). AppendInsertOne("db", "coll", &mongo.ClientInsertOneModel{ - Document: bson.D{{"a", strings.Repeat("b", hello.MaxBsonObjectSize)}}, + Document: bson.D{{"a", "b"}}, }) - result, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetWriteConcern(writeconcern.Unacknowledged())) - require.NoError(mt, err, "BulkWrite error") - assert.Equal(mt, 2, int(result.UpsertedCount), "InsertedCount expected to be %d, got %d", 2, result.UpsertedCount) - assert.Len(mt, result.UpdateResults, 2, "expected %d UpdateResults, got %d", 2, len(result.UpdateResults)) + _, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetOrdered(false).SetWriteConcern(writeconcern.Unacknowledged())) + require.ErrorContains(mt, err, "bulkWrite does not currently support automatic encryption") }) } diff --git a/x/mongo/driver/operation.go b/x/mongo/driver/operation.go index 2eb9a1be70..c2f4601947 100644 --- a/x/mongo/driver/operation.go +++ b/x/mongo/driver/operation.go @@ -862,9 +862,6 @@ func (op Operation) Execute(ctx context.Context) error { Error: tt, } _ = op.ProcessResponseFn(ctx, res, info) - // if perr != nil { - // return perr - // } } // If batching is enabled and either ordered is the default (which is true) or @@ -985,9 +982,6 @@ func (op Operation) Execute(ctx context.Context) error { Error: tt, } _ = op.ProcessResponseFn(ctx, res, info) - // if perr != nil { - // return perr - // } } if op.Client != nil && op.Client.Committing && (retryableErr || tt.Code == 50) { @@ -1385,7 +1379,10 @@ func (op Operation) createWireMessage( dsOffset := len(dst) processedBatches, dst, err = op.Batches.AppendBatchSequence(dst, int(desc.MaxBatchCount), int(desc.MaxDocumentSize), int(desc.MaxMessageSize)) if err != nil { - return dst, err + return nil, err + } + if processedBatches == 0 { + return nil, ErrDocumentTooLarge } info.processedBatches = processedBatches info.documentSequence = make([]byte, 0) @@ -1492,6 +1489,9 @@ func (op Operation) addLegacyCommandFields(dst []byte, desc description.Selected if err != nil { return 0, nil, err } + if n == 0 { + return 0, nil, ErrDocumentTooLarge + } return n, dst, nil } From b287ccf799a24d8f8bb1c9c80cd9443240f2318d Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Sat, 19 Oct 2024 01:32:00 -0400 Subject: [PATCH 12/14] WIP --- mongo/client.go | 6 +- mongo/client_bulk_write.go | 16 +- mongo/integration/crud_prose_test.go | 135 +++-- .../unified/client_operation_execution.go | 100 ++-- .../client-bulkWrite-partialResults.json | 540 ++++++++++++++++++ .../client-bulkWrite-partialResults.yml | 262 +++++++++ 6 files changed, 963 insertions(+), 96 deletions(-) create mode 100644 testdata/crud/unified/client-bulkWrite-partialResults.json create mode 100644 testdata/crud/unified/client-bulkWrite-partialResults.yml diff --git a/mongo/client.go b/mongo/client.go index 5d8e7ece67..cebd06559c 100644 --- a/mongo/client.go +++ b/mongo/client.go @@ -909,8 +909,10 @@ func (c *Client) BulkWrite(ctx context.Context, models *ClientWriteModels, if bwo.VerboseResults == nil || !(*bwo.VerboseResults) { op.errorsOnly = true } - err = op.execute(ctx) - return &op.result, replaceErrors(err) + if err = op.execute(ctx); err != nil { + return nil, replaceErrors(err) + } + return &op.result, nil } // newLogger will use the LoggerOptions to create an internal logger and publish diff --git a/mongo/client_bulk_write.go b/mongo/client_bulk_write.go index 5762cc8033..cccc49e226 100644 --- a/mongo/client_bulk_write.go +++ b/mongo/client_bulk_write.go @@ -9,7 +9,6 @@ package mongo import ( "context" "errors" - "fmt" "io" "strconv" @@ -96,7 +95,16 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { exception.WriteErrors = batches.writeErrors } if exception != nil { - exception.PartialResult = batches.result + var hasSuccess bool + if bw.ordered == nil || *bw.ordered { + _, ok := batches.writeErrors[0] + hasSuccess = !ok + } else { + hasSuccess = len(batches.writeErrors) < len(bw.models) + } + if hasSuccess { + exception.PartialResult = batches.result + } return *exception } return err @@ -390,7 +398,6 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD } func (mb *modelBatches) processResponse(ctx context.Context, resp bsoncore.Document, info driver.ResponseInfo) error { - fmt.Println("ProcessResponse", info.Error) var writeCmdErr driver.WriteCommandError if errors.As(info.Error, &writeCmdErr) && writeCmdErr.WriteConcernError != nil { wce := convertDriverWriteConcernError(writeCmdErr.WriteConcernError) @@ -464,14 +471,13 @@ func (mb *modelBatches) processResponse(ctx context.Context, resp bsoncore.Docum return err } isOrdered := mb.ordered == nil || *mb.ordered - fmt.Println("ProcessResponse toplevelerror", res.Ok, res.NErrors, res.Code, res.Errmsg) if isOrdered && (writeCmdErr.WriteConcernError != nil || !ok || !res.Ok || res.NErrors > 0) { exception := ClientBulkWriteException{ WriteConcernErrors: mb.writeConcernErrors, WriteErrors: mb.writeErrors, PartialResult: mb.result, } - if !res.Ok || res.NErrors > 0 { + if !res.Ok { exception.TopLevelError = &WriteError{ Code: int(res.Code), Message: res.Errmsg, diff --git a/mongo/integration/crud_prose_test.go b/mongo/integration/crud_prose_test.go index c2ce360537..89aa7f1296 100644 --- a/mongo/integration/crud_prose_test.go +++ b/mongo/integration/crud_prose_test.go @@ -10,6 +10,7 @@ import ( "bytes" "context" "errors" + "os" "strings" "testing" @@ -415,7 +416,7 @@ func TestErrorsCodeNamePropagated(t *testing.T) { } func TestClientBulkWrite(t *testing.T) { - mtOpts := mtest.NewOptions().MinServerVersion("8.0").AtlasDataLake(false).CreateClient(false) + mtOpts := mtest.NewOptions().MinServerVersion("8.0").AtlasDataLake(false).CreateClient(false).ClientType(mtest.Pinned) mt := mtest.New(t, mtOpts) mt.Run("input with greater than maxWriteBatchSize", func(mt *mtest.T) { @@ -423,10 +424,12 @@ func TestClientBulkWrite(t *testing.T) { monitor := &event.CommandMonitor{ Started: func(_ context.Context, e *event.CommandStartedEvent) { if e.CommandName == "bulkWrite" { - v := e.Command.Lookup("ops") - elems, err := v.Array().Elements() - require.NoError(mt, err, "monitor error") - opsCnt = append(opsCnt, len(elems)) + var c struct { + Ops []bson.D + } + err := bson.Unmarshal(e.Command, &c) + require.NoError(mt, err) + opsCnt = append(opsCnt, len(c.Ops)) } }, } @@ -455,10 +458,12 @@ func TestClientBulkWrite(t *testing.T) { monitor := &event.CommandMonitor{ Started: func(_ context.Context, e *event.CommandStartedEvent) { if e.CommandName == "bulkWrite" { - v := e.Command.Lookup("ops") - elems, err := v.Array().Elements() - require.NoError(mt, err, "monitor error") - opsCnt = append(opsCnt, len(elems)) + var c struct { + Ops []bson.D + } + err := bson.Unmarshal(e.Command, &c) + require.NoError(mt, err) + opsCnt = append(opsCnt, len(c.Ops)) } }, } @@ -744,11 +749,20 @@ func TestClientBulkWrite(t *testing.T) { }) mt.Run("bulkWrite batch splits when the addition of a new namespace exceeds the maximum message size", func(mt *mtest.T) { - var bwCmd []bsoncore.Document + type cmd struct { + Ops []bson.D + NsInfo []struct { + Ns string + } + } + var bwCmd []cmd monitor := &event.CommandMonitor{ Started: func(_ context.Context, e *event.CommandStartedEvent) { if e.CommandName == "bulkWrite" { - bwCmd = append(bwCmd, bsoncore.Document(e.Command)) + var c cmd + err := bson.Unmarshal(e.Command, &c) + require.NoError(mt, err) + bwCmd = append(bwCmd, c) } }, } @@ -794,17 +808,9 @@ func TestClientBulkWrite(t *testing.T) { assert.Equal(mt, numModels+1, int(result.InsertedCount), "expected insertedCound: %d, got: %d", numModels+1, result.InsertedCount) require.Len(mt, bwCmd, 1, "expected %d bulkWrite call, got %d", 1, len(bwCmd)) - var cmd struct { - Ops []bson.D - NsInfo []struct { - Ns string - } - } - err = bson.Unmarshal(bwCmd[0], &cmd) - require.NoError(mt, err) - assert.Len(mt, cmd.Ops, numModels+1, "expected ops: %d, got: %d", numModels+1, len(cmd.Ops)) - require.Len(mt, cmd.NsInfo, 1, "expected %d nsInfo, got: %d", 1, len(cmd.NsInfo)) - assert.Equal(mt, "db.coll", cmd.NsInfo[0].Ns, "expected namespace: %s, got: %s", "db.coll", cmd.NsInfo[0].Ns) + assert.Len(mt, bwCmd[0].Ops, numModels+1, "expected ops: %d, got: %d", numModels+1, len(bwCmd[0].Ops)) + require.Len(mt, bwCmd[0].NsInfo, 1, "expected %d nsInfo, got: %d", 1, len(bwCmd[0].NsInfo)) + assert.Equal(mt, "db.coll", bwCmd[0].NsInfo[0].Ns, "expected namespace: %s, got: %s", "db.coll", bwCmd[0].NsInfo[0].Ns) }) mt.Run("batch-splitting required", func(mt *mtest.T) { bwCmd = bwCmd[:0] @@ -818,27 +824,15 @@ func TestClientBulkWrite(t *testing.T) { result, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite()) require.NoError(mt, err) assert.Equal(mt, numModels+1, int(result.InsertedCount), "expected insertedCound: %d, got: %d", numModels+1, result.InsertedCount) - require.Len(mt, bwCmd, 2, "expected %d bulkWrite call, got %d", 2, len(bwCmd)) + require.Len(mt, bwCmd, 2, "expected %d bulkWrite calls, got %d", 2, len(bwCmd)) - type cmd struct { - Ops []bson.D - NsInfo []struct { - Ns string - } - } - var c1 cmd - err = bson.Unmarshal(bwCmd[0], &c1) - require.NoError(mt, err) - assert.Len(mt, c1.Ops, numModels, "expected ops: %d, got: %d", numModels, len(c1.Ops)) - require.Len(mt, c1.NsInfo, 1, "expected %d nsInfo, got: %d", 1, len(c1.NsInfo)) - assert.Equal(mt, "db.coll", c1.NsInfo[0].Ns, "expected namespace: %s, got: %s", "db.coll", c1.NsInfo[0].Ns) + assert.Len(mt, bwCmd[0].Ops, numModels, "expected ops: %d, got: %d", numModels, len(bwCmd[0].Ops)) + require.Len(mt, bwCmd[0].NsInfo, 1, "expected %d nsInfo, got: %d", 1, len(bwCmd[0].NsInfo)) + assert.Equal(mt, "db.coll", bwCmd[0].NsInfo[0].Ns, "expected namespace: %s, got: %s", "db.coll", bwCmd[0].NsInfo[0].Ns) - var c2 cmd - err = bson.Unmarshal(bwCmd[1], &c2) - require.NoError(mt, err) - assert.Len(mt, c2.Ops, 1, "expected ops: %d, got: %d", 1, len(c2.Ops)) - require.Len(mt, c2.NsInfo, 1, "expected %d nsInfo, got: %d", 1, len(c2.NsInfo)) - assert.Equal(mt, "db."+coll, c2.NsInfo[0].Ns, "expected namespace: %s, got: %s", "db."+coll, c2.NsInfo[0].Ns) + assert.Len(mt, bwCmd[1].Ops, 1, "expected ops: %d, got: %d", 1, len(bwCmd[1].Ops)) + require.Len(mt, bwCmd[1].NsInfo, 1, "expected %d nsInfo, got: %d", 1, len(bwCmd[1].NsInfo)) + assert.Equal(mt, "db."+coll, bwCmd[1].NsInfo[0].Ns, "expected namespace: %s, got: %s", "db."+coll, bwCmd[1].NsInfo[0].Ns) }) }) @@ -867,6 +861,10 @@ func TestClientBulkWrite(t *testing.T) { }) mt.Run("bulkWrite returns an error if auto-encryption is configured", func(mt *mtest.T) { + if os.Getenv("DOCKER_RUNNING") != "" { + mt.Skip("skipping test in docker environment") + } + autoEncryptionOpts := options.AutoEncryption(). SetKeyVaultNamespace("db.coll"). SetKmsProviders(map[string]map[string]interface{}{ @@ -883,4 +881,59 @@ func TestClientBulkWrite(t *testing.T) { _, err := mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetOrdered(false).SetWriteConcern(writeconcern.Unacknowledged())) require.ErrorContains(mt, err, "bulkWrite does not currently support automatic encryption") }) + + mt.Run("bulkWrite with unacknowledged write concern uses w:0 for all batches", func(mt *mtest.T) { + type cmd struct { + Ops []bson.D + WriteConcern struct { + W interface{} + } + } + var bwCmd []cmd + monitor := &event.CommandMonitor{ + Started: func(_ context.Context, e *event.CommandStartedEvent) { + if e.CommandName == "bulkWrite" { + var c cmd + err := bson.Unmarshal(e.Command, &c) + require.NoError(mt, err) + + bwCmd = append(bwCmd, c) + } + }, + } + var hello struct { + MaxBsonObjectSize int + MaxMessageSizeBytes int + } + err := mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello) + require.NoError(mt, err, "Hello error") + + mt.ResetClient(options.Client().SetMonitor(monitor)) + + coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, true) + err = coll.Drop(context.Background()) + require.NoError(mt, err, "Drop error") + + numModels := hello.MaxMessageSizeBytes / hello.MaxBsonObjectSize + models := &mongo.ClientWriteModels{} + for i := 0; i < numModels+1; i++ { + models. + AppendInsertOne("db", "coll", &mongo.ClientInsertOneModel{ + Document: bson.D{{"a", strings.Repeat("b", hello.MaxBsonObjectSize-500)}}, + }) + } + _, err = mt.Client.BulkWrite(context.Background(), models, options.ClientBulkWrite().SetOrdered(false).SetWriteConcern(writeconcern.Unacknowledged())) + require.NoError(mt, err) + require.Len(mt, bwCmd, 2, "expected %d bulkWrite calls, got %d", 2, len(bwCmd)) + + assert.Len(mt, bwCmd[0].Ops, numModels, "expected ops: %d, got: %d", numModels, len(bwCmd[0].Ops)) + assert.Equal(mt, int32(0), bwCmd[0].WriteConcern.W, "expected writeConcern: %d, got: %v", 0, bwCmd[0].WriteConcern.W) + + assert.Len(mt, bwCmd[1].Ops, 1, "expected ops: %d, got: %d", 1, len(bwCmd[1].Ops)) + assert.Equal(mt, int32(0), bwCmd[1].WriteConcern.W, "expected writeConcern: %d, got: %v", 0, bwCmd[1].WriteConcern.W) + + n, err := coll.CountDocuments(context.Background(), bson.D{}) + require.NoError(mt, err) + assert.Equal(mt, numModels+1, int(n), "expected %d documents, got %d", numModels+1, n) + }) } diff --git a/mongo/integration/unified/client_operation_execution.go b/mongo/integration/unified/client_operation_execution.go index 24f1d145a5..6e18af44c9 100644 --- a/mongo/integration/unified/client_operation_execution.go +++ b/mongo/integration/unified/client_operation_execution.go @@ -8,6 +8,7 @@ package unified import ( "context" + "errors" "fmt" "strconv" "strings" @@ -228,60 +229,63 @@ func executeClientBulkWrite(ctx context.Context, operation *operation) (*operati } res, err := client.BulkWrite(ctx, wirteModels, opts) - raw := emptyCoreDocument - if res != nil { - rawBuilder := bsoncore.NewDocumentBuilder(). - AppendInt64("deletedCount", res.DeletedCount). - AppendInt64("insertedCount", res.InsertedCount). - AppendInt64("matchedCount", res.MatchedCount). - AppendInt64("modifiedCount", res.ModifiedCount). - AppendInt64("upsertedCount", res.UpsertedCount) - - var resBuilder *bsoncore.DocumentBuilder - - resBuilder = bsoncore.NewDocumentBuilder() - for k, v := range res.DeleteResults { - resBuilder.AppendDocument(strconv.Itoa(k), - bsoncore.NewDocumentBuilder(). - AppendInt64("deletedCount", v.DeletedCount). - Build(), - ) + if res == nil { + var bwe mongo.ClientBulkWriteException + if !errors.As(err, &bwe) || bwe.PartialResult == nil { + return newDocumentResult(emptyCoreDocument, err), nil } - rawBuilder.AppendDocument("deleteResults", resBuilder.Build()) - - resBuilder = bsoncore.NewDocumentBuilder() - for k, v := range res.InsertResults { - t, d, err := bson.MarshalValue(v.InsertedID) + res = bwe.PartialResult + } + rawBuilder := bsoncore.NewDocumentBuilder(). + AppendInt64("deletedCount", res.DeletedCount). + AppendInt64("insertedCount", res.InsertedCount). + AppendInt64("matchedCount", res.MatchedCount). + AppendInt64("modifiedCount", res.ModifiedCount). + AppendInt64("upsertedCount", res.UpsertedCount) + + var resBuilder *bsoncore.DocumentBuilder + + resBuilder = bsoncore.NewDocumentBuilder() + for k, v := range res.DeleteResults { + resBuilder.AppendDocument(strconv.Itoa(k), + bsoncore.NewDocumentBuilder(). + AppendInt64("deletedCount", v.DeletedCount). + Build(), + ) + } + rawBuilder.AppendDocument("deleteResults", resBuilder.Build()) + + resBuilder = bsoncore.NewDocumentBuilder() + for k, v := range res.InsertResults { + t, d, err := bson.MarshalValue(v.InsertedID) + if err != nil { + return nil, err + } + resBuilder.AppendDocument(strconv.Itoa(k), + bsoncore.NewDocumentBuilder(). + AppendValue("insertedId", bsoncore.Value{Type: t, Data: d}). + Build(), + ) + } + rawBuilder.AppendDocument("insertResults", resBuilder.Build()) + + resBuilder = bsoncore.NewDocumentBuilder() + for k, v := range res.UpdateResults { + b := bsoncore.NewDocumentBuilder(). + AppendInt64("matchedCount", v.MatchedCount). + AppendInt64("modifiedCount", v.ModifiedCount) + if v.UpsertedID != nil { + t, d, err := bson.MarshalValue(v.UpsertedID) if err != nil { return nil, err } - resBuilder.AppendDocument(strconv.Itoa(k), - bsoncore.NewDocumentBuilder(). - AppendValue("insertedId", bsoncore.Value{Type: t, Data: d}). - Build(), - ) - } - rawBuilder.AppendDocument("insertResults", resBuilder.Build()) - - resBuilder = bsoncore.NewDocumentBuilder() - for k, v := range res.UpdateResults { - b := bsoncore.NewDocumentBuilder(). - AppendInt64("matchedCount", v.MatchedCount). - AppendInt64("modifiedCount", v.ModifiedCount) - if v.UpsertedID != nil { - t, d, err := bson.MarshalValue(v.UpsertedID) - if err != nil { - return nil, err - } - b.AppendValue("upsertedId", bsoncore.Value{Type: t, Data: d}) - } - resBuilder.AppendDocument(strconv.Itoa(k), b.Build()) + b.AppendValue("upsertedId", bsoncore.Value{Type: t, Data: d}) } - rawBuilder.AppendDocument("updateResults", resBuilder.Build()) - - raw = rawBuilder.Build() + resBuilder.AppendDocument(strconv.Itoa(k), b.Build()) } - return newDocumentResult(raw, err), nil + rawBuilder.AppendDocument("updateResults", resBuilder.Build()) + + return newDocumentResult(rawBuilder.Build(), err), nil } func appendClientBulkWriteModel(key string, value bson.Raw, model *mongo.ClientWriteModels) error { diff --git a/testdata/crud/unified/client-bulkWrite-partialResults.json b/testdata/crud/unified/client-bulkWrite-partialResults.json new file mode 100644 index 0000000000..1b75e37834 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-partialResults.json @@ -0,0 +1,540 @@ +{ + "description": "client bulkWrite partial results", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0", + "newDocument": { + "_id": 2, + "x": 22 + } + }, + "tests": [ + { + "description": "partialResult is unset when first operation fails during an ordered bulk write (verbose)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "ordered": true, + "verboseResults": true + }, + "expectError": { + "expectResult": { + "$$unsetOrMatches": { + "insertedCount": { + "$$exists": false + }, + "upsertedCount": { + "$$exists": false + }, + "matchedCount": { + "$$exists": false + }, + "modifiedCount": { + "$$exists": false + }, + "deletedCount": { + "$$exists": false + }, + "insertResults": { + "$$exists": false + }, + "updateResults": { + "$$exists": false + }, + "deleteResults": { + "$$exists": false + } + } + } + } + } + ] + }, + { + "description": "partialResult is unset when first operation fails during an ordered bulk write (summary)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "ordered": true, + "verboseResults": false + }, + "expectError": { + "expectResult": { + "$$unsetOrMatches": { + "insertedCount": { + "$$exists": false + }, + "upsertedCount": { + "$$exists": false + }, + "matchedCount": { + "$$exists": false + }, + "modifiedCount": { + "$$exists": false + }, + "deletedCount": { + "$$exists": false + }, + "insertResults": { + "$$exists": false + }, + "updateResults": { + "$$exists": false + }, + "deleteResults": { + "$$exists": false + } + } + } + } + } + ] + }, + { + "description": "partialResult is set when second operation fails during an ordered bulk write (verbose)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "ordered": true, + "verboseResults": true + }, + "expectError": { + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 2 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + } + ] + }, + { + "description": "partialResult is set when second operation fails during an ordered bulk write (summary)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "ordered": true, + "verboseResults": false + }, + "expectError": { + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + } + } + } + ] + }, + { + "description": "partialResult is unset when all operations fail during an unordered bulk write", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "ordered": false + }, + "expectError": { + "expectResult": { + "$$unsetOrMatches": { + "insertedCount": { + "$$exists": false + }, + "upsertedCount": { + "$$exists": false + }, + "matchedCount": { + "$$exists": false + }, + "modifiedCount": { + "$$exists": false + }, + "deletedCount": { + "$$exists": false + }, + "insertResults": { + "$$exists": false + }, + "updateResults": { + "$$exists": false + }, + "deleteResults": { + "$$exists": false + } + } + } + } + } + ] + }, + { + "description": "partialResult is set when first operation fails during an unordered bulk write (verbose)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "ordered": false, + "verboseResults": true + }, + "expectError": { + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "1": { + "insertedId": 2 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + } + ] + }, + { + "description": "partialResult is set when first operation fails during an unordered bulk write (summary)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "ordered": false, + "verboseResults": false + }, + "expectError": { + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + } + } + } + ] + }, + { + "description": "partialResult is set when second operation fails during an unordered bulk write (verbose)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "ordered": false, + "verboseResults": true + }, + "expectError": { + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 2 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + } + ] + }, + { + "description": "partialResult is set when second operation fails during an unordered bulk write (summary)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "ordered": false, + "verboseResults": false + }, + "expectError": { + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + } + } + } + ] + } + ] +} diff --git a/testdata/crud/unified/client-bulkWrite-partialResults.yml b/testdata/crud/unified/client-bulkWrite-partialResults.yml new file mode 100644 index 0000000000..1cda7318f8 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-partialResults.yml @@ -0,0 +1,262 @@ +description: "client bulkWrite partial results" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - &existingDocument { _id: 1, x: 11 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + newDocument: &newDocument { _id: 2, x: 22 } + +tests: + - description: "partialResult is unset when first operation fails during an ordered bulk write (verbose)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *existingDocument + - insertOne: + namespace: *namespace + document: *newDocument + ordered: true + verboseResults: true + expectError: + expectResult: + $$unsetOrMatches: + insertedCount: { $$exists: false } + upsertedCount: { $$exists: false } + matchedCount: { $$exists: false } + modifiedCount: { $$exists: false } + deletedCount: { $$exists: false } + insertResults: { $$exists: false } + updateResults: { $$exists: false } + deleteResults: { $$exists: false } + - description: "partialResult is unset when first operation fails during an ordered bulk write (summary)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *existingDocument + - insertOne: + namespace: *namespace + document: *newDocument + ordered: true + verboseResults: false + expectError: + expectResult: + $$unsetOrMatches: + insertedCount: { $$exists: false } + upsertedCount: { $$exists: false } + matchedCount: { $$exists: false } + modifiedCount: { $$exists: false } + deletedCount: { $$exists: false } + insertResults: { $$exists: false } + updateResults: { $$exists: false } + deleteResults: { $$exists: false } + - description: "partialResult is set when second operation fails during an ordered bulk write (verbose)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *newDocument + - insertOne: + namespace: *namespace + document: *existingDocument + ordered: true + verboseResults: true + expectError: + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 2 + updateResults: {} + deleteResults: {} + - description: "partialResult is set when second operation fails during an ordered bulk write (summary)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *newDocument + - insertOne: + namespace: *namespace + document: *existingDocument + ordered: true + verboseResults: false + expectError: + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} + - description: "partialResult is unset when all operations fail during an unordered bulk write" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *existingDocument + - insertOne: + namespace: *namespace + document: *existingDocument + ordered: false + expectError: + expectResult: + $$unsetOrMatches: + insertedCount: { $$exists: false } + upsertedCount: { $$exists: false } + matchedCount: { $$exists: false } + modifiedCount: { $$exists: false } + deletedCount: { $$exists: false } + insertResults: { $$exists: false } + updateResults: { $$exists: false } + deleteResults: { $$exists: false } + - description: "partialResult is set when first operation fails during an unordered bulk write (verbose)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *existingDocument + - insertOne: + namespace: *namespace + document: *newDocument + ordered: false + verboseResults: true + expectError: + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 1: + insertedId: 2 + updateResults: {} + deleteResults: {} + - description: "partialResult is set when first operation fails during an unordered bulk write (summary)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *existingDocument + - insertOne: + namespace: *namespace + document: *newDocument + ordered: false + verboseResults: false + expectError: + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} + - description: "partialResult is set when second operation fails during an unordered bulk write (verbose)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *newDocument + - insertOne: + namespace: *namespace + document: *existingDocument + ordered: false + verboseResults: true + expectError: + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 2 + updateResults: {} + deleteResults: {} + - description: "partialResult is set when second operation fails during an unordered bulk write (summary)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *newDocument + - insertOne: + namespace: *namespace + document: *existingDocument + ordered: false + verboseResults: false + expectError: + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} From f1b81d6e321a077325a19572fddff30892d75421 Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Mon, 21 Oct 2024 21:55:29 -0400 Subject: [PATCH 13/14] cleanup --- internal/driverutil/operation.go | 1 + mongo/client.go | 8 ++- mongo/client_bulk_write.go | 78 +++++++++++++++++-------- mongo/client_bulk_write_models.go | 9 ++- mongo/errors.go | 6 ++ mongo/options/clientbulkwriteoptions.go | 19 +++--- x/mongo/driver/operation.go | 15 +++-- 7 files changed, 90 insertions(+), 46 deletions(-) diff --git a/internal/driverutil/operation.go b/internal/driverutil/operation.go index 32704312ff..e37cba5903 100644 --- a/internal/driverutil/operation.go +++ b/internal/driverutil/operation.go @@ -28,4 +28,5 @@ const ( ListIndexesOp = "listIndexes" // ListIndexesOp is the name for listing indexes ListDatabasesOp = "listDatabases" // ListDatabasesOp is the name for listing databases UpdateOp = "update" // UpdateOp is the name for updating + BulkWriteOp = "bulkWrite" // BulkWriteOp is the name for client-level bulk write ) diff --git a/mongo/client.go b/mongo/client.go index cebd06559c..734ab4b761 100644 --- a/mongo/client.go +++ b/mongo/client.go @@ -851,9 +851,10 @@ func (c *Client) createBaseCursorOptions() driver.CursorOptions { } } -// BulkWrite performs a client-levelbulk write operation. +// BulkWrite performs a client-level bulk write operation. func (c *Client) BulkWrite(ctx context.Context, models *ClientWriteModels, opts ...*options.ClientBulkWriteOptions) (*ClientBulkWriteResult, error) { + // TODO: Remove once DRIVERS-2888 is implemented. if c.isAutoEncryptionSet { return nil, errors.New("bulkWrite does not currently support automatic encryption") } @@ -886,6 +887,9 @@ func (c *Client) BulkWrite(ctx context.Context, models *ClientWriteModels, wc = bwo.WriteConcern } if !writeconcern.AckWrite(wc) { + if bwo.Ordered == nil || *bwo.Ordered { + return nil, errors.New("cannot request unacknowledged write concern and ordered writes") + } sess = nil } @@ -908,6 +912,8 @@ func (c *Client) BulkWrite(ctx context.Context, models *ClientWriteModels, } if bwo.VerboseResults == nil || !(*bwo.VerboseResults) { op.errorsOnly = true + } else if !writeconcern.AckWrite(wc) { + return nil, errors.New("cannot request unacknowledged write concern and verbose results") } if err = op.execute(ctx); err != nil { return nil, replaceErrors(err) diff --git a/mongo/client_bulk_write.go b/mongo/client_bulk_write.go index cccc49e226..7cf0a12f89 100644 --- a/mongo/client_bulk_write.go +++ b/mongo/client_bulk_write.go @@ -15,6 +15,7 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/bsoncodec" "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/internal/driverutil" "go.mongodb.org/mongo-driver/mongo/description" "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/writeconcern" @@ -24,6 +25,10 @@ import ( "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" ) +const ( + database = "admin" +) + // bulkWrite performs a bulkwrite operation type clientBulkWrite struct { models []clientWriteModel @@ -42,12 +47,17 @@ type clientBulkWrite struct { func (bw *clientBulkWrite) execute(ctx context.Context) error { if len(bw.models) == 0 { - return errors.New("empty write models") + return ErrEmptySlice + } + for _, m := range bw.models { + if m.model == nil { + return ErrNilDocument + } } batches := &modelBatches{ session: bw.session, client: bw.client, - ordered: bw.ordered, + ordered: bw.ordered == nil || *bw.ordered, models: bw.models, result: &bw.result, retryMode: driver.RetryOnce, @@ -61,7 +71,7 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { Type: driver.Write, Batches: batches, CommandMonitor: bw.client.monitor, - Database: "admin", + Database: database, Deployment: bw.client.deployment, Selector: bw.selector, WriteConcern: bw.writeConcern, @@ -70,7 +80,7 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { Timeout: bw.client.timeout, Logger: bw.client.logger, Authenticator: bw.client.authenticator, - Name: "bulkWrite", + Name: driverutil.BulkWriteOp, }.Execute(ctx) var exception *ClientBulkWriteException switch tt := err.(type) { @@ -96,7 +106,7 @@ func (bw *clientBulkWrite) execute(ctx context.Context) error { } if exception != nil { var hasSuccess bool - if bw.ordered == nil || *bw.ordered { + if batches.ordered { _, ok := batches.writeErrors[0] hasSuccess = !ok } else { @@ -125,9 +135,7 @@ func (bw *clientBulkWrite) newCommand() func([]byte, description.SelectedServer) } dst = bsoncore.AppendValueElement(dst, "comment", comment) } - if bw.ordered != nil { - dst = bsoncore.AppendBooleanElement(dst, "ordered", *bw.ordered) - } + dst = bsoncore.AppendBooleanElement(dst, "ordered", bw.ordered == nil || *bw.ordered) if bw.let != nil { let, err := marshal(bw.let, bw.client.bsonOpts, bw.client.registry) if err != nil { @@ -173,7 +181,7 @@ type modelBatches struct { session *session.Client client *Client - ordered *bool + ordered bool models []clientWriteModel offset int @@ -188,7 +196,7 @@ type modelBatches struct { } func (mb *modelBatches) IsOrdered() *bool { - return mb.ordered + return &mb.ordered } func (mb *modelBatches) AdvanceBatches(n int) { @@ -265,6 +273,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD } canRetry := true + checkSize := true l := len(dst) @@ -272,7 +281,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD nsIdx, nsDst := fn.appendStart(nil, "nsInfo") totalSize -= 1000 - size := (len(dst) - l) * 2 + size := len(dst) + len(nsDst) var n int for i := mb.offset; i < len(mb.models); i++ { if n == maxCount { @@ -286,11 +295,13 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD var err error switch model := mb.models[i].model.(type) { case *ClientInsertOneModel: + checkSize = false mb.cursorHandlers = append(mb.cursorHandlers, mb.appendInsertResult) var id interface{} id, doc, err = (&clientInsertDoc{ namespace: nsIdx, document: model.Document, + sizeLimit: maxDocSize, }).marshal(mb.client.bsonOpts, mb.client.registry) if err != nil { break @@ -324,6 +335,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD checkDollarKey: true, }).marshal(mb.client.bsonOpts, mb.client.registry) case *ClientReplaceOneModel: + checkSize = false mb.cursorHandlers = append(mb.cursorHandlers, mb.appendUpdateResult) doc, err = (&clientUpdateDoc{ namespace: nsIdx, @@ -335,6 +347,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD upsert: model.Upsert, multi: false, checkDollarKey: false, + sizeLimit: maxDocSize, }).marshal(mb.client.bsonOpts, mb.client.registry) case *ClientDeleteOneModel: mb.cursorHandlers = append(mb.cursorHandlers, mb.appendDeleteResult) @@ -362,8 +375,8 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD return 0, nil, err } length := len(doc) - if length > maxDocSize { - break + if maxDocSize > 0 && length > maxDocSize+16*1024 { + return 0, nil, driver.ErrDocumentTooLarge } if !exists { length += len(ns) @@ -389,6 +402,9 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, maxD dst = fn.updateLength(dst, opsIdx, int32(len(dst[opsIdx:]))) nsDst = fn.updateLength(nsDst, nsIdx, int32(len(nsDst[nsIdx:]))) dst = append(dst, nsDst...) + if checkSize && maxDocSize > 0 && len(dst)-l > maxDocSize+16*1024 { + return 0, nil, driver.ErrDocumentTooLarge + } mb.retryMode = driver.RetryNone if mb.client.retryWrites && canRetry { @@ -424,6 +440,19 @@ func (mb *modelBatches) processResponse(ctx context.Context, resp bsoncore.Docum if err != nil { return err } + if !res.Ok { + return ClientBulkWriteException{ + TopLevelError: &WriteError{ + Code: int(res.Code), + Message: res.Errmsg, + Raw: bson.Raw(resp), + }, + WriteConcernErrors: mb.writeConcernErrors, + WriteErrors: mb.writeErrors, + PartialResult: mb.result, + } + } + mb.result.DeletedCount += int64(res.NDeleted) mb.result.InsertedCount += int64(res.NInserted) mb.result.MatchedCount += int64(res.NMatched) @@ -470,21 +499,12 @@ func (mb *modelBatches) processResponse(ctx context.Context, resp bsoncore.Docum if err != nil { return err } - isOrdered := mb.ordered == nil || *mb.ordered - if isOrdered && (writeCmdErr.WriteConcernError != nil || !ok || !res.Ok || res.NErrors > 0) { - exception := ClientBulkWriteException{ + if mb.ordered && (writeCmdErr.WriteConcernError != nil || !ok || !res.Ok || res.NErrors > 0) { + return ClientBulkWriteException{ WriteConcernErrors: mb.writeConcernErrors, WriteErrors: mb.writeErrors, PartialResult: mb.result, } - if !res.Ok { - exception.TopLevelError = &WriteError{ - Code: int(res.Code), - Message: res.Errmsg, - Raw: bson.Raw(resp), - } - } - return exception } return nil } @@ -558,6 +578,8 @@ func (mb *modelBatches) appendUpdateResult(cur *cursorInfo, raw bson.Raw) bool { type clientInsertDoc struct { namespace int document interface{} + + sizeLimit int } func (d *clientInsertDoc) marshal(bsonOpts *options.BSONOptions, registry *bsoncodec.Registry) (interface{}, bsoncore.Document, error) { @@ -568,6 +590,9 @@ func (d *clientInsertDoc) marshal(bsonOpts *options.BSONOptions, registry *bsonc if err != nil { return nil, nil, err } + if d.sizeLimit > 0 && len(f) > d.sizeLimit { + return nil, nil, driver.ErrDocumentTooLarge + } var id interface{} f, id, err = ensureID(f, primitive.NilObjectID, bsonOpts, registry) if err != nil { @@ -588,6 +613,8 @@ type clientUpdateDoc struct { upsert *bool multi bool checkDollarKey bool + + sizeLimit int } func (d *clientUpdateDoc) marshal(bsonOpts *options.BSONOptions, registry *bsoncodec.Registry) (bsoncore.Document, error) { @@ -605,6 +632,9 @@ func (d *clientUpdateDoc) marshal(bsonOpts *options.BSONOptions, registry *bsonc if err != nil { return nil, err } + if d.sizeLimit > 0 && len(u.Data) > d.sizeLimit { + return nil, driver.ErrDocumentTooLarge + } doc = bsoncore.AppendValueElement(doc, "updateMods", u) doc = bsoncore.AppendBooleanElement(doc, "multi", d.multi) diff --git a/mongo/client_bulk_write_models.go b/mongo/client_bulk_write_models.go index 4a2259a5c9..526e209e50 100644 --- a/mongo/client_bulk_write_models.go +++ b/mongo/client_bulk_write_models.go @@ -16,7 +16,6 @@ import ( type ClientWriteModels struct { models []clientWriteModel } - type clientWriteModel struct { namespace string model interface{} @@ -106,7 +105,7 @@ func (m *ClientWriteModels) AppendDeleteMany(database, collection string, models return m } -// ClientInsertOneModel is used to insert a single document in a BulkWrite operation. +// ClientInsertOneModel is used to insert a single document in a client-level BulkWrite operation. type ClientInsertOneModel struct { Document interface{} } @@ -166,7 +165,7 @@ func (uom *ClientUpdateOneModel) SetCollation(collation *options.Collation) *Cli } // SetUpsert specifies whether or not a new document should be inserted if no document matching the filter is found. If -// an upsert is performed, the _id of the upserted document can be retrieved from the UpsertedIDs field of the +// an upsert is performed, the _id of the upserted document can be retrieved from the UpdateResults field of the // ClientBulkWriteResult. func (uom *ClientUpdateOneModel) SetUpsert(upsert bool) *ClientUpdateOneModel { uom.Upsert = &upsert @@ -219,7 +218,7 @@ func (umm *ClientUpdateManyModel) SetCollation(collation *options.Collation) *Cl } // SetUpsert specifies whether or not a new document should be inserted if no document matching the filter is found. If -// an upsert is performed, the _id of the upserted document can be retrieved from the UpsertedIDs field of the +// an upsert is performed, the _id of the upserted document can be retrieved from the UpdateResults field of the // ClientBulkWriteResult. func (umm *ClientUpdateManyModel) SetUpsert(upsert bool) *ClientUpdateManyModel { umm.Upsert = &upsert @@ -265,7 +264,7 @@ func (rom *ClientReplaceOneModel) SetCollation(collation *options.Collation) *Cl } // SetUpsert specifies whether or not the replacement document should be inserted if no document matching the filter is -// found. If an upsert is performed, the _id of the upserted document can be retrieved from the UpsertedIDs field of the +// found. If an upsert is performed, the _id of the upserted document can be retrieved from the UpdateResults field of the // BulkWriteResult. func (rom *ClientReplaceOneModel) SetUpsert(upsert bool) *ClientReplaceOneModel { rom.Upsert = &upsert diff --git a/mongo/errors.go b/mongo/errors.go index 547d27827a..5340d632cc 100644 --- a/mongo/errors.go +++ b/mongo/errors.go @@ -611,14 +611,20 @@ func (bwe BulkWriteException) serverError() {} // ClientBulkWriteException is the error type returned by ClientBulkWrite operations. type ClientBulkWriteException struct { + // A top-level error that occurred when attempting to communicate with the server + // or execute the bulk write. This value may not be populated if the exception was + // thrown due to errors occurring on individual writes. TopLevelError *WriteError // The write concern errors that occurred. WriteConcernErrors []WriteConcernError // The write errors that occurred during individual operation execution. + // This map will contain at most one entry if the bulk write was ordered. WriteErrors map[int]WriteError + // The results of any successful operations that were performed before the error + // was encountered. PartialResult *ClientBulkWriteResult } diff --git a/mongo/options/clientbulkwriteoptions.go b/mongo/options/clientbulkwriteoptions.go index ad91f37488..7c460e47ce 100644 --- a/mongo/options/clientbulkwriteoptions.go +++ b/mongo/options/clientbulkwriteoptions.go @@ -12,21 +12,19 @@ import ( // ClientBulkWriteOptions represents options that can be used to configure a client-level BulkWrite operation. type ClientBulkWriteOptions struct { - // If true, writes executed as part of the operation will opt out of document-level validation on the server. This - // option is valid for MongoDB versions >= 3.2 and is ignored for previous server versions. The default value is - // false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about document - // validation. + // If true, writes executed as part of the operation will opt out of document-level validation on the server. The + // default value is false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information + // about document validation. BypassDocumentValidation *bool // A string or document that will be included in server logs, profiling logs, and currentOp queries to help trace - // the operation. The default value is nil, which means that no comment will be included in the logs. + // the operation. The default value is nil, which means that no comment will be included in the logs. Comment interface{} // If true, no writes will be executed after one fails. The default value is true. Ordered *bool - // Specifies parameters for all update and delete commands in the BulkWrite. This option is only valid for MongoDB - // versions >= 5.0. Older servers will report an error for using this option. This must be a document mapping + // Specifies parameters for all update and delete commands in the BulkWrite. This must be a document mapping // parameter names to values. Values must be constant or closed expressions that do not reference document fields. // Parameters can then be accessed as variables in an aggregate expression context (e.g. "$$var"). Let interface{} @@ -63,8 +61,7 @@ func (b *ClientBulkWriteOptions) SetBypassDocumentValidation(bypass bool) *Clien return b } -// SetLet sets the value for the Let field. Let specifies parameters for all update and delete commands in the BulkWrite. -// This option is only valid for MongoDB versions >= 5.0. Older servers will report an error for using this option. +// SetLet sets the value for the Let field. Let specifies parameters for all update and delete commands in the ClientBulkWrite. // This must be a document mapping parameter names to values. Values must be constant or closed expressions that do not // reference document fields. Parameters can then be accessed as variables in an aggregate expression context (e.g. "$$var"). func (b *ClientBulkWriteOptions) SetLet(let interface{}) *ClientBulkWriteOptions { @@ -84,8 +81,8 @@ func (b *ClientBulkWriteOptions) SetVerboseResults(verboseResults bool) *ClientB return b } -// MergeClientBulkWriteOptions combines the given BulkWriteOptions instances into a single BulkWriteOptions in a last-one-wins -// fashion. +// MergeClientBulkWriteOptions combines the given ClientBulkWriteOptions instances into a single +// ClientBulkWriteOptions in a last-one-wins fashion. // // Deprecated: Merging options structs will not be supported in Go Driver 2.0. Users should create a // single options struct instead. diff --git a/x/mongo/driver/operation.go b/x/mongo/driver/operation.go index c2f4601947..21573488c5 100644 --- a/x/mongo/driver/operation.go +++ b/x/mongo/driver/operation.go @@ -1349,6 +1349,8 @@ func (op Operation) createWireMessage( var wmindex int32 var err error + unacknowledged := op.WriteConcern != nil && !writeconcern.AckWrite(op.WriteConcern) + fIdx := -1 isLegacy := isLegacyHandshake(op, desc) switch { @@ -1374,10 +1376,10 @@ func (op Operation) createWireMessage( default: wmindex, dst = wiremessage.AppendHeaderStart(dst, requestID, 0, wiremessage.OpMsg) fIdx = len(dst) - appendBatches := func(dst []byte) ([]byte, error) { + appendBatches := func(dst []byte, maxCount, maxDocSize, totalSize int) ([]byte, error) { var processedBatches int dsOffset := len(dst) - processedBatches, dst, err = op.Batches.AppendBatchSequence(dst, int(desc.MaxBatchCount), int(desc.MaxDocumentSize), int(desc.MaxMessageSize)) + processedBatches, dst, err = op.Batches.AppendBatchSequence(dst, maxCount, maxDocSize, totalSize) if err != nil { return nil, err } @@ -1401,12 +1403,16 @@ func (op Operation) createWireMessage( case *Batches: dst, info.cmd, err = op.createMsgWireMessage(maxTimeMS, dst, desc, conn, op.CommandFn) if err == nil && op.Batches != nil { - dst, err = appendBatches(dst) + dst, err = appendBatches(dst, int(desc.MaxBatchCount), int(desc.MaxDocumentSize), int(desc.MaxDocumentSize)) } default: var batches []byte if op.Batches != nil { - batches, err = appendBatches(batches) + maxDocSize := -1 + if unacknowledged { + maxDocSize = int(desc.MaxDocumentSize) + } + batches, err = appendBatches(batches, int(desc.MaxBatchCount), maxDocSize, int(desc.MaxMessageSize)) } if err == nil { dst, info.cmd, err = op.createMsgWireMessage(maxTimeMS, dst, desc, conn, op.CommandFn) @@ -1423,7 +1429,6 @@ func (op Operation) createWireMessage( var moreToCome bool // We set the MoreToCome bit if we have a write concern, it's unacknowledged, and we either // aren't batching or we are encoding the last batch. - unacknowledged := op.WriteConcern != nil && !writeconcern.AckWrite(op.WriteConcern) batching := op.Batches != nil && op.Batches.Size() > info.processedBatches if fIdx > 0 && unacknowledged && !batching { dst[fIdx] |= byte(wiremessage.MoreToCome) From ac20fb74d8cd5de10eddef5c67975f4d86a77052 Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Tue, 22 Oct 2024 16:27:43 -0400 Subject: [PATCH 14/14] cleanup --- mongo/integration/crud_prose_test.go | 10 +- mongo/integration/csot_prose_test.go | 54 + mongo/integration/unified/error.go | 59 +- .../unacknowledged-client-bulkWrite.json | 220 ++ .../unacknowledged-client-bulkWrite.yml | 111 + .../crud/unified/client-bulkWrite-errors.json | 58 + .../crud/unified/client-bulkWrite-errors.yml | 29 + .../legacy/mongos-pin-auto-tests.py | 340 --- .../unified/mongos-pin-auto-tests.py | 353 +++ .../{legacy => unified}/mongos-pin-auto.json | 2663 ++++++++++------- .../{legacy => unified}/mongos-pin-auto.yml | 1063 +++---- 11 files changed, 3088 insertions(+), 1872 deletions(-) create mode 100644 testdata/command-monitoring/unacknowledged-client-bulkWrite.json create mode 100644 testdata/command-monitoring/unacknowledged-client-bulkWrite.yml delete mode 100644 testdata/transactions/legacy/mongos-pin-auto-tests.py create mode 100644 testdata/transactions/unified/mongos-pin-auto-tests.py rename testdata/transactions/{legacy => unified}/mongos-pin-auto.json (66%) rename testdata/transactions/{legacy => unified}/mongos-pin-auto.yml (67%) diff --git a/mongo/integration/crud_prose_test.go b/mongo/integration/crud_prose_test.go index 89aa7f1296..b4c752e0ce 100644 --- a/mongo/integration/crud_prose_test.go +++ b/mongo/integration/crud_prose_test.go @@ -529,7 +529,7 @@ func TestClientBulkWrite(t *testing.T) { }) mt.Run("bulkWrite handles individual WriteErrors across batches", func(mt *mtest.T) { - coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, true) + coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, false) err := coll.Drop(context.Background()) require.NoError(mt, err, "Drop error") _, err = coll.InsertOne(context.Background(), bson.D{{"_id", 1}}) @@ -580,7 +580,7 @@ func TestClientBulkWrite(t *testing.T) { }) mt.Run("bulkWrite handles a cursor requiring a getMore", func(mt *mtest.T) { - coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, true) + coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, false) err := coll.Drop(context.Background()) require.NoError(mt, err, "Drop error") @@ -618,7 +618,7 @@ func TestClientBulkWrite(t *testing.T) { }) mt.Run("bulkWrite handles a cursor requiring a getMore within a transaction", func(mt *mtest.T) { - coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, true) + coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, false) err := coll.Drop(context.Background()) require.NoError(mt, err, "Drop error") @@ -693,7 +693,7 @@ func TestClientBulkWrite(t *testing.T) { }, }) - coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, true) + coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, false) err = coll.Drop(context.Background()) require.NoError(mt, err, "Drop error") @@ -910,7 +910,7 @@ func TestClientBulkWrite(t *testing.T) { mt.ResetClient(options.Client().SetMonitor(monitor)) - coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, true) + coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, false) err = coll.Drop(context.Background()) require.NoError(mt, err, "Drop error") diff --git a/mongo/integration/csot_prose_test.go b/mongo/integration/csot_prose_test.go index c8ddfd68df..775bd7037b 100644 --- a/mongo/integration/csot_prose_test.go +++ b/mongo/integration/csot_prose_test.go @@ -16,6 +16,7 @@ import ( "go.mongodb.org/mongo-driver/event" "go.mongodb.org/mongo-driver/internal/assert" "go.mongodb.org/mongo-driver/internal/integtest" + "go.mongodb.org/mongo-driver/internal/require" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/integration/mtest" "go.mongodb.org/mongo-driver/mongo/options" @@ -160,4 +161,57 @@ func TestCSOTProse(t *testing.T) { "expected ping to fail within 150ms") }) }) + mt.RunOpts("11. multi-batch bulkWrites", mtest.NewOptions().MinServerVersion("8.0"). + AtlasDataLake(false).Topologies(mtest.Single), func(mt *mtest.T) { + coll := mt.CreateCollection(mtest.Collection{DB: "db", Name: "coll"}, false) + err := coll.Drop(context.Background()) + require.NoError(mt, err, "Drop error: %v", err) + + mt.SetFailPoint(mtest.FailPoint{ + ConfigureFailPoint: "failCommand", + Mode: mtest.FailPointMode{ + Times: 2, + }, + Data: mtest.FailPointData{ + FailCommands: []string{"bulkWrite"}, + BlockConnection: true, + BlockTimeMS: 1010, + }, + }) + + var hello struct { + MaxBsonObjectSize int + MaxMessageSizeBytes int + } + err = mt.DB.RunCommand(context.Background(), bson.D{{"hello", 1}}).Decode(&hello) + require.NoError(mt, err, "Hello error: %v", err) + + models := &mongo.ClientWriteModels{} + n := hello.MaxMessageSizeBytes/hello.MaxBsonObjectSize + 1 + for i := 0; i < n; i++ { + models. + AppendInsertOne("db", "coll", &mongo.ClientInsertOneModel{ + Document: bson.D{{"a", strings.Repeat("b", hello.MaxBsonObjectSize-500)}}, + }) + } + + var cnt int + cm := &event.CommandMonitor{ + Started: func(_ context.Context, evt *event.CommandStartedEvent) { + if evt.CommandName == "bulkWrite" { + cnt++ + } + }, + } + cliOptions := options.Client(). + SetTimeout(2 * time.Second). + SetMonitor(cm). + ApplyURI(mtest.ClusterURI()) + integtest.AddTestServerAPIVersion(cliOptions) + cli, err := mongo.Connect(context.Background(), cliOptions) + require.NoError(mt, err, "Connect error: %v", err) + _, err = cli.BulkWrite(context.Background(), models) + assert.ErrorContains(mt, err, "context deadline exceeded", "expected a timeout error, got: %v", err) + assert.Equal(mt, 2, cnt, "expected bulkWrite calls: %d, got: %d", 2, cnt) + }) } diff --git a/mongo/integration/unified/error.go b/mongo/integration/unified/error.go index 2bf4cf380a..d967c35a38 100644 --- a/mongo/integration/unified/error.go +++ b/mongo/integration/unified/error.go @@ -19,15 +19,22 @@ import ( // expectedError represents an error that is expected to occur during a test. This type ignores the "isError" field in // test files because it is always true if it is specified, so the runner can simply assert that an error occurred. type expectedError struct { - IsClientError *bool `bson:"isClientError"` - IsTimeoutError *bool `bson:"isTimeoutError"` - ErrorSubstring *string `bson:"errorContains"` - Code *int32 `bson:"errorCode"` - CodeName *string `bson:"errorCodeName"` - IncludedLabels []string `bson:"errorLabelsContain"` - OmittedLabels []string `bson:"errorLabelsOmit"` - ExpectedResult *bson.RawValue `bson:"expectResult"` - ErrorResponse *bson.Raw `bson:"errorResponse"` + IsClientError *bool `bson:"isClientError"` + IsTimeoutError *bool `bson:"isTimeoutError"` + ErrorSubstring *string `bson:"errorContains"` + Code *int32 `bson:"errorCode"` + CodeName *string `bson:"errorCodeName"` + IncludedLabels []string `bson:"errorLabelsContain"` + OmittedLabels []string `bson:"errorLabelsOmit"` + ExpectedResult *bson.RawValue `bson:"expectResult"` + ErrorResponse *bson.Raw `bson:"errorResponse"` + WriteErrors map[int]clientBulkWriteException `bson:"writeErrors"` + WriteConcernErrors []clientBulkWriteException `bson:"writeConcernErrors"` +} + +type clientBulkWriteException struct { + Code *int `bson:"code"` + Message *string `bson:"message"` } // verifyOperationError compares the expected error to the actual operation result. If the expected parameter is nil, @@ -140,6 +147,40 @@ func verifyOperationError(ctx context.Context, expected *expectedError, result * return fmt.Errorf("error response comparison error: %w", err) } } + if expected.WriteErrors != nil { + var exception mongo.ClientBulkWriteException + if !errors.As(result.Err, &exception) { + return fmt.Errorf("expected a ClientBulkWriteException, got %T", result.Err) + } + if len(expected.WriteErrors) != len(exception.WriteErrors) { + return fmt.Errorf("expected errors: %v, got: %v", expected.WriteErrors, exception.WriteErrors) + } + for k, e := range expected.WriteErrors { + if e.Code != nil && *e.Code != exception.WriteErrors[k].Code { + return fmt.Errorf("expected errors: %v, got: %v", expected.WriteConcernErrors, exception.WriteConcernErrors) + } + if e.Message != nil && *e.Message != exception.WriteErrors[k].Message { + return fmt.Errorf("expected errors: %v, got: %v", expected.WriteConcernErrors, exception.WriteConcernErrors) + } + } + } + if expected.WriteConcernErrors != nil { + var exception mongo.ClientBulkWriteException + if !errors.As(result.Err, &exception) { + return fmt.Errorf("expected a ClientBulkWriteException, got %T", result.Err) + } + if len(expected.WriteConcernErrors) != len(exception.WriteConcernErrors) { + return fmt.Errorf("expected errors: %v, got: %v", expected.WriteConcernErrors, exception.WriteConcernErrors) + } + for i, e := range expected.WriteConcernErrors { + if e.Code != nil && *e.Code != exception.WriteConcernErrors[i].Code { + return fmt.Errorf("expected errors: %v, got: %v", expected.WriteConcernErrors, exception.WriteConcernErrors) + } + if e.Message != nil && *e.Message != exception.WriteConcernErrors[i].Message { + return fmt.Errorf("expected errors: %v, got: %v", expected.WriteConcernErrors, exception.WriteConcernErrors) + } + } + } return nil } diff --git a/testdata/command-monitoring/unacknowledged-client-bulkWrite.json b/testdata/command-monitoring/unacknowledged-client-bulkWrite.json new file mode 100644 index 0000000000..61bb00726c --- /dev/null +++ b/testdata/command-monitoring/unacknowledged-client-bulkWrite.json @@ -0,0 +1,220 @@ +{ + "description": "unacknowledged-client-bulkWrite", + "schemaVersion": "1.7", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ], + "uriOptions": { + "w": 0 + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "command-monitoring-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "command-monitoring-tests.test" + }, + "tests": [ + { + "description": "A successful mixed client bulkWrite", + "operations": [ + { + "object": "client", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "command-monitoring-tests.test", + "document": { + "_id": 4, + "x": 44 + } + } + }, + { + "updateOne": { + "namespace": "command-monitoring-tests.test", + "filter": { + "_id": 3 + }, + "update": { + "$set": { + "x": 333 + } + } + } + } + ], + "ordered": false + }, + "expectResult": { + "insertedCount": { + "$$unsetOrMatches": 0 + }, + "upsertedCount": { + "$$unsetOrMatches": 0 + }, + "matchedCount": { + "$$unsetOrMatches": 0 + }, + "modifiedCount": { + "$$unsetOrMatches": 0 + }, + "deletedCount": { + "$$unsetOrMatches": 0 + }, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + } + }, + { + "object": "collection", + "name": "find", + "arguments": { + "filter": {} + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 333 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client", + "ignoreExtraEvents": true, + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": true, + "ordered": false, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + }, + { + "update": 0, + "filter": { + "_id": 3 + }, + "updateMods": { + "$set": { + "x": 333 + } + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "command-monitoring-tests.test" + } + ] + } + } + }, + { + "commandSucceededEvent": { + "commandName": "bulkWrite", + "reply": { + "ok": 1, + "nInserted": { + "$$exists": false + }, + "nMatched": { + "$$exists": false + }, + "nModified": { + "$$exists": false + }, + "nUpserted": { + "$$exists": false + }, + "nDeleted": { + "$$exists": false + } + } + } + } + ] + } + ] + } + ] +} diff --git a/testdata/command-monitoring/unacknowledged-client-bulkWrite.yml b/testdata/command-monitoring/unacknowledged-client-bulkWrite.yml new file mode 100644 index 0000000000..2d54525953 --- /dev/null +++ b/testdata/command-monitoring/unacknowledged-client-bulkWrite.yml @@ -0,0 +1,111 @@ +description: "unacknowledged-client-bulkWrite" + +schemaVersion: "1.7" + +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - commandSucceededEvent + - commandFailedEvent + uriOptions: + w: 0 + - database: + id: &database database + client: *client + databaseName: &databaseName command-monitoring-tests + - collection: + id: &collection collection + database: *database + collectionName: &collectionName test + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "command-monitoring-tests.test" + +tests: + - description: 'A successful mixed client bulkWrite' + operations: + - object: *client + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 4, x: 44 } + - updateOne: + namespace: *namespace + filter: { _id: 3 } + update: { $set: { x: 333 } } + ordered: false + expectResult: + insertedCount: + $$unsetOrMatches: 0 + upsertedCount: + $$unsetOrMatches: 0 + matchedCount: + $$unsetOrMatches: 0 + modifiedCount: + $$unsetOrMatches: 0 + deletedCount: + $$unsetOrMatches: 0 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} + # Force completion of the w:0 write by executing a find on the same connection + - object: *collection + name: find + arguments: + filter: {} + expectResult: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 333 } + - { _id: 4, x: 44 } + + expectEvents: + - + client: *client + ignoreExtraEvents: true + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: true + ordered: false + ops: + - insert: 0 + document: { _id: 4, x: 44 } + - update: 0 + filter: { _id: 3 } + updateMods: { $set: { x: 333 } } + multi: false + nsInfo: + - ns: *namespace + - commandSucceededEvent: + commandName: bulkWrite + reply: + ok: 1 + nInserted: { $$exists: false } + nMatched: { $$exists: false } + nModified: { $$exists: false } + nUpserted: { $$exists: false } + nDeleted: { $$exists: false } diff --git a/testdata/crud/unified/client-bulkWrite-errors.json b/testdata/crud/unified/client-bulkWrite-errors.json index 8cc45bb5f2..015bd95c99 100644 --- a/testdata/crud/unified/client-bulkWrite-errors.json +++ b/testdata/crud/unified/client-bulkWrite-errors.json @@ -450,6 +450,64 @@ } } ] + }, + { + "description": "Requesting unacknowledged write with verboseResults is a client-side error", + "operations": [ + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 10 + } + } + } + ], + "verboseResults": true, + "ordered": false, + "writeConcern": { + "w": 0 + } + }, + "expectError": { + "isClientError": true, + "errorContains": "Cannot request unacknowledged write concern and verbose results" + } + } + ] + }, + { + "description": "Requesting unacknowledged write with ordered is a client-side error", + "operations": [ + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 10 + } + } + } + ], + "writeConcern": { + "w": 0 + } + }, + "expectError": { + "isClientError": true, + "errorContains": "Cannot request unacknowledged write concern and ordered writes" + } + } + ] } ] } diff --git a/testdata/crud/unified/client-bulkWrite-errors.yml b/testdata/crud/unified/client-bulkWrite-errors.yml index 6c513006ce..79c0496161 100644 --- a/testdata/crud/unified/client-bulkWrite-errors.yml +++ b/testdata/crud/unified/client-bulkWrite-errors.yml @@ -239,3 +239,32 @@ tests: verboseResults: true expectError: isClientError: true + - description: "Requesting unacknowledged write with verboseResults is a client-side error" + operations: + - name: clientBulkWrite + object: *client0 + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 10 } + verboseResults: true + ordered: false + writeConcern: { w: 0 } + expectError: + isClientError: true + errorContains: "Cannot request unacknowledged write concern and verbose results" + - description: "Requesting unacknowledged write with ordered is a client-side error" + operations: + - name: clientBulkWrite + object: *client0 + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 10 } + # Omit `ordered` option. Defaults to true. + writeConcern: { w: 0 } + expectError: + isClientError: true + errorContains: "Cannot request unacknowledged write concern and ordered writes" diff --git a/testdata/transactions/legacy/mongos-pin-auto-tests.py b/testdata/transactions/legacy/mongos-pin-auto-tests.py deleted file mode 100644 index 1072ec2907..0000000000 --- a/testdata/transactions/legacy/mongos-pin-auto-tests.py +++ /dev/null @@ -1,340 +0,0 @@ -import itertools -import sys - -# Require Python 3.7+ for ordered dictionaries so that the order of the -# generated tests remain the same. -# Usage: -# python3.7 mongos-pin-auto-tests.py > mongos-pin-auto.yml -if sys.version_info[:2] < (3, 7): - print('ERROR: This script requires Python >= 3.7, not:') - print(sys.version) - print('Usage: python3.7 mongos-pin-auto-tests.py > mongos-pin-auto.yml') - exit(1) - -HEADER = '''# Autogenerated tests that transient errors in a transaction unpin the session. -# See mongos-pin-auto-tests.py -runOn: - - - minServerVersion: "4.1.8" - topology: ["sharded"] - # serverless proxy doesn't append error labels to errors in transactions - # caused by failpoints (CLOUDP-88216) - serverless: "forbid" - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: &data - - {_id: 1} - - {_id: 2} - -tests: - - description: remain pinned after non-transient Interrupted error on insertOne - useMultipleMongoses: true - operations: - - &startTransaction - name: startTransaction - object: session0 - - &initialCommand - name: insertOne - object: collection - arguments: - session: session0 - document: {_id: 3} - result: - insertedId: 3 - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 11601 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 4 - result: - errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] - errorCodeName: Interrupted - - &assertSessionPinned - name: assertSessionPinned - object: testRunner - arguments: - session: session0 - - &commitTransaction - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 4 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - recoveryToken: 42 - command_name: commitTransaction - database_name: admin - - outcome: &outcome - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} - - - description: unpin after transient error within a transaction - useMultipleMongoses: true - operations: - - &startTransaction - name: startTransaction - object: session0 - - &initialCommand - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 3 - result: - insertedId: 3 - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - closeConnection: true - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 4 - result: - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["UnknownTransactionCommitResult"] - # Session unpins from the first mongos after the insert error and - # abortTransaction succeeds immediately on any mongos. - - &assertSessionUnpinned - name: assertSessionUnpinned - object: testRunner - arguments: - session: session0 - - &abortTransaction - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 4 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - recoveryToken: 42 - command_name: abortTransaction - database_name: admin - - outcome: &outcome - collection: - data: *data - - # The rest of the tests in this file test every operation type against - # multiple types of transient errors (connection and error code).''' - -TEMPLATE = ''' - - description: {test_name} {error_name} error on {op_name} {command_name} - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {{times: 1}} - data: - failCommands: ["{command_name}"] - {error_data} - - name: {op_name} - object: {object_name} - arguments: - session: session0 - {op_args} - result: - {error_labels}: ["TransientTransactionError"] - - *{assertion} - - *abortTransaction - outcome: *outcome -''' - - -# Maps from op_name to (command_name, object_name, op_args) -OPS = { - # Write ops: - 'insertOne': ('insert', 'collection', r'document: {_id: 4}'), - 'insertMany': ('insert', 'collection', r'documents: [{_id: 4}, {_id: 5}]'), - 'updateOne': ('update', 'collection', r'''filter: {_id: 1} - update: {$inc: {x: 1}}'''), - 'replaceOne': ('update', 'collection', r'''filter: {_id: 1} - replacement: {y: 1}'''), - 'updateMany': ('update', 'collection', r'''filter: {_id: {$gte: 1}} - update: {$set: {z: 1}}'''), - 'deleteOne': ('delete', 'collection', r'filter: {_id: 1}'), - 'deleteMany': ('delete', 'collection', r'filter: {_id: {$gte: 1}}'), - 'findOneAndDelete': ('findAndModify', 'collection', r'filter: {_id: 1}'), - 'findOneAndUpdate': ('findAndModify', 'collection', r'''filter: {_id: 1} - update: {$inc: {x: 1}} - returnDocument: Before'''), - 'findOneAndReplace': ('findAndModify', 'collection', r'''filter: {_id: 1} - replacement: {y: 1} - returnDocument: Before'''), - # Bulk write insert/update/delete: - 'bulkWrite insert': ('insert', 'collection', r'''requests: - - name: insertOne - arguments: - document: {_id: 1}'''), - 'bulkWrite update': ('update', 'collection', r'''requests: - - name: updateOne - arguments: - filter: {_id: 1} - update: {$set: {x: 1}}'''), - 'bulkWrite delete': ('delete', 'collection', r'''requests: - - name: deleteOne - arguments: - filter: {_id: 1}'''), - # Read ops: - 'find': ('find', 'collection', r'filter: {_id: 1}'), - 'countDocuments': ('aggregate', 'collection', r'filter: {}'), - 'aggregate': ('aggregate', 'collection', r'pipeline: []'), - 'distinct': ('distinct', 'collection', r'fieldName: _id'), - # runCommand: - 'runCommand': ( - 'insert', - r'''database - command_name: insert''', # runCommand requires command_name. - r'''command: - insert: *collection_name - documents: - - _id : 1'''), -} - -# Maps from error_name to error_data. -NON_TRANSIENT_ERRORS = { - 'Interrupted': 'errorCode: 11601', -} - -# Maps from error_name to error_data. -TRANSIENT_ERRORS = { - 'connection': 'closeConnection: true', - 'ShutdownInProgress': 'errorCode: 91', -} - - -def create_pin_test(op_name, error_name): - test_name = 'remain pinned after non-transient' - assertion = 'assertSessionPinned' - error_labels = 'errorLabelsOmit' - command_name, object_name, op_args = OPS[op_name] - error_data = NON_TRANSIENT_ERRORS[error_name] - if op_name.startswith('bulkWrite'): - op_name = 'bulkWrite' - return TEMPLATE.format(**locals()) - - -def create_unpin_test(op_name, error_name): - test_name = 'unpin after transient' - assertion = 'assertSessionUnpinned' - error_labels = 'errorLabelsContain' - command_name, object_name, op_args = OPS[op_name] - error_data = TRANSIENT_ERRORS[error_name] - if op_name.startswith('bulkWrite'): - op_name = 'bulkWrite' - return TEMPLATE.format(**locals()) - -tests = [] -for op_name, error_name in itertools.product(OPS, NON_TRANSIENT_ERRORS): - tests.append(create_pin_test(op_name, error_name)) -for op_name, error_name in itertools.product(OPS, TRANSIENT_ERRORS): - tests.append(create_unpin_test(op_name, error_name)) - -print(HEADER) -print(''.join(tests)) diff --git a/testdata/transactions/unified/mongos-pin-auto-tests.py b/testdata/transactions/unified/mongos-pin-auto-tests.py new file mode 100644 index 0000000000..09306780fa --- /dev/null +++ b/testdata/transactions/unified/mongos-pin-auto-tests.py @@ -0,0 +1,353 @@ +import itertools +import sys + +# Require Python 3.7+ for ordered dictionaries so that the order of the +# generated tests remain the same. +# Usage: +# python3.7 mongos-pin-auto-tests.py > mongos-pin-auto.yml +if sys.version_info[:2] < (3, 7): + print('ERROR: This script requires Python >= 3.7, not:') + print(sys.version) + print('Usage: python3.7 mongos-pin-auto-tests.py > mongos-pin-auto.yml') + exit(1) + +HEADER = '''# Autogenerated tests that transient errors in a transaction unpin the session. +# See mongos-pin-auto-tests.py + +description: mongos-pin-auto + +schemaVersion: '1.4' + +runOnRequirements: + - minServerVersion: "4.1.8" + # Note: tests utilize targetedFailPoint, which is incompatible with + # load-balanced and useMultipleMongoses:true + topologies: [ sharded ] + # serverless proxy doesn't append error labels to errors in transactions + # caused by failpoints (CLOUDP-88216) + serverless: "forbid" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collection_name + databaseName: *database_name + documents: &data + - { _id: 1 } + - { _id: 2 } + +tests: + - description: remain pinned after non-transient Interrupted error on insertOne + operations: + - &startTransaction + object: session0 + name: startTransaction + - &initialCommand + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 3 } + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } } + - object: testRunner + name: targetedFailPoint + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ "insert" ] + errorCode: 11601 + - object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] + errorCodeName: Interrupted + - &assertSessionPinned + object: testRunner + name: assertSessionPinned + arguments: + session: *session0 + - &commitTransaction + object: *session0 + name: commitTransaction + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 3 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + + - description: 'unpin after transient error within a transaction' + operations: + - *startTransaction + - *initialCommand + - object: testRunner + name: targetedFailPoint + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ "insert" ] + closeConnection: true + - object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + errorLabelsOmit: ["UnknownTransactionCommitResult"] + - &assertSessionUnpinned + object: testRunner + name: assertSessionUnpinned + arguments: + session: *session0 + - &abortTransaction + object: *session0 + name: abortTransaction + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 3 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: abortTransaction + databaseName: admin + outcome: &outcome + - collectionName: *collection_name + databaseName: *database_name + documents: *data + + # The rest of the tests in this file test every operation type against + # multiple types of transient errors (connection and error code).''' + +TEMPLATE = ''' + - description: {test_name} {error_name} error on {op_name} {command_name} + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {{times: 1}} + data: + failCommands: ["{command_name}"] + {error_data} + - name: {op_name} + object: {object_name} + arguments: + session: *session0 + {op_args} + expectError: + {error_labels}: ["TransientTransactionError"] + - *{assertion} + - *abortTransaction + outcome: *outcome +''' + + +# Maps from op_name to (command_name, object_name, op_args) +OPS = { + # Write ops: + 'insertOne': ('insert', '*collection0', r'document: { _id: 4 }'), + 'insertMany': ('insert', '*collection0', r'documents: [ { _id: 4 }, { _id: 5 } ]'), + 'updateOne': ('update', '*collection0', r'''filter: { _id: 1 } + update: { $inc: { x: 1 } }'''), + 'replaceOne': ('update', '*collection0', r'''filter: { _id: 1 } + replacement: { y: 1 }'''), + 'updateMany': ('update', '*collection0', r'''filter: { _id: { $gte: 1 } } + update: {$set: { z: 1 } }'''), + 'deleteOne': ('delete', '*collection0', r'filter: { _id: 1 }'), + 'deleteMany': ('delete', '*collection0', r'filter: { _id: { $gte: 1 } }'), + 'findOneAndDelete': ('findAndModify', '*collection0', r'filter: { _id: 1 }'), + 'findOneAndUpdate': ('findAndModify', '*collection0', r'''filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before'''), + 'findOneAndReplace': ('findAndModify', '*collection0', r'''filter: { _id: 1 } + replacement: { y: 1 } + returnDocument: Before'''), + # Bulk write insert/update/delete: + 'bulkWrite insert': ('insert', '*collection0', r'''requests: + - insertOne: + document: { _id: 1 }'''), + 'bulkWrite update': ('update', '*collection0', r'''requests: + - updateOne: + filter: { _id: 1 } + update: { $set: { x: 1 } }'''), + 'bulkWrite delete': ('delete', '*collection0', r'''requests: + - deleteOne: + filter: { _id: 1 }'''), + # Read ops: + 'find': ('find', '*collection0', r'filter: { _id: 1 }'), + 'countDocuments': ('aggregate', '*collection0', r'filter: {}'), + 'aggregate': ('aggregate', '*collection0', r'pipeline: []'), + 'distinct': ('distinct', '*collection0', r'''fieldName: _id + filter: {}'''), + # runCommand: + 'runCommand': ('insert', '*database0', r'''commandName: insert + command: + insert: *collection_name + documents: + - { _id : 1 }'''), + # clientBulkWrite: + 'clientBulkWrite': ('bulkWrite', '*client0', r'''models: + - insertOne: + namespace: database0.collection0 + document: { _id: 8, x: 88 }'''), +} + +# Maps from error_name to error_data. +NON_TRANSIENT_ERRORS = { + 'Interrupted': 'errorCode: 11601', +} + +# Maps from error_name to error_data. +TRANSIENT_ERRORS = { + 'connection': 'closeConnection: true', + 'ShutdownInProgress': 'errorCode: 91', +} + + +def create_pin_test(op_name, error_name): + test_name = 'remain pinned after non-transient' + assertion = 'assertSessionPinned' + error_labels = 'errorLabelsOmit' + command_name, object_name, op_args = OPS[op_name] + error_data = NON_TRANSIENT_ERRORS[error_name] + if op_name.startswith('bulkWrite'): + op_name = 'bulkWrite' + test = TEMPLATE.format(**locals()) + if op_name == 'clientBulkWrite': + test += ' runOnRequirements:\n' + test += ' - minServerVersion: "8.0" # `bulkWrite` added to server 8.0"\n' + test += ' serverless: forbid\n' + return test + + +def create_unpin_test(op_name, error_name): + test_name = 'unpin after transient' + assertion = 'assertSessionUnpinned' + error_labels = 'errorLabelsContain' + command_name, object_name, op_args = OPS[op_name] + error_data = TRANSIENT_ERRORS[error_name] + if op_name.startswith('bulkWrite'): + op_name = 'bulkWrite' + test = TEMPLATE.format(**locals()) + if op_name == 'clientBulkWrite': + test += ' runOnRequirements:\n' + test += ' - minServerVersion: "8.0" # `bulkWrite` added to server 8.0"\n' + test += ' serverless: forbid\n' + return test + + + +tests = [] +for op_name, error_name in itertools.product(OPS, NON_TRANSIENT_ERRORS): + tests.append(create_pin_test(op_name, error_name)) +for op_name, error_name in itertools.product(OPS, TRANSIENT_ERRORS): + tests.append(create_unpin_test(op_name, error_name)) + +print(HEADER) +print(''.join(tests)) diff --git a/testdata/transactions/legacy/mongos-pin-auto.json b/testdata/transactions/unified/mongos-pin-auto.json similarity index 66% rename from testdata/transactions/legacy/mongos-pin-auto.json rename to testdata/transactions/unified/mongos-pin-auto.json index 037f212f49..27db520401 100644 --- a/testdata/transactions/legacy/mongos-pin-auto.json +++ b/testdata/transactions/unified/mongos-pin-auto.json @@ -1,48 +1,88 @@ { - "runOn": [ + "description": "mongos-pin-auto", + "schemaVersion": "1.4", + "runOnRequirements": [ { "minServerVersion": "4.1.8", - "topology": [ + "topologies": [ "sharded" ], "serverless": "forbid" } ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [ + "createEntities": [ { - "_id": 1 + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } }, { - "_id": 2 + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] } ], "tests": [ { "description": "remain pinned after non-transient Interrupted error on insertOne", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { - "name": "targetedFailPoint", "object": "testRunner", + "name": "targetedFailPoint", "arguments": { "session": "session0", "failPoint": { @@ -60,15 +100,15 @@ } }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 4 } }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError", "UnknownTransactionCommitResult" @@ -77,85 +117,114 @@ } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "commitTransaction", - "object": "session0" + "object": "session0", + "name": "commitTransaction" } ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 3 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, + "expectEvents": [ { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 4 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 3 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null, - "recoveryToken": 42 + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 4 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } }, - "command_name": "commitTransaction", - "database_name": "admin" - } + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -167,32 +236,35 @@ } ] } - } + ] }, { "description": "unpin after transient error within a transaction", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { - "name": "targetedFailPoint", "object": "testRunner", + "name": "targetedFailPoint", "arguments": { "session": "session0", "failPoint": { @@ -210,15 +282,15 @@ } }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 4 } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ], @@ -228,85 +300,114 @@ } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 3 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, + "expectEvents": [ { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 4 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 3 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null, - "recoveryToken": 42 + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 4 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } }, - "command_name": "abortTransaction", - "database_name": "admin" - } + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -315,27 +416,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on insertOne insert", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -359,34 +463,36 @@ }, { "name": "insertOne", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "document": { "_id": 4 } }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -395,27 +501,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on insertMany insert", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -439,7 +548,7 @@ }, { "name": "insertMany", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "documents": [ @@ -451,27 +560,29 @@ } ] }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -480,27 +591,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on updateOne update", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -524,7 +638,7 @@ }, { "name": "updateOne", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -536,27 +650,29 @@ } } }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -565,27 +681,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on replaceOne update", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -609,7 +728,7 @@ }, { "name": "replaceOne", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -619,27 +738,29 @@ "y": 1 } }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -648,27 +769,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on updateMany update", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -692,7 +816,7 @@ }, { "name": "updateMany", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -706,27 +830,29 @@ } } }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -735,27 +861,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on deleteOne delete", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -779,34 +908,36 @@ }, { "name": "deleteOne", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { "_id": 1 } }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -815,27 +946,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on deleteMany delete", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -859,7 +993,7 @@ }, { "name": "deleteMany", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -868,27 +1002,29 @@ } } }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -897,27 +1033,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on findOneAndDelete findAndModify", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -941,34 +1080,36 @@ }, { "name": "findOneAndDelete", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { "_id": 1 } }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -977,27 +1118,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on findOneAndUpdate findAndModify", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -1021,7 +1165,7 @@ }, { "name": "findOneAndUpdate", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -1034,27 +1178,29 @@ }, "returnDocument": "Before" }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -1063,27 +1209,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on findOneAndReplace findAndModify", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -1107,7 +1256,7 @@ }, { "name": "findOneAndReplace", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -1118,27 +1267,29 @@ }, "returnDocument": "Before" }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -1147,27 +1298,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on bulkWrite insert", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -1191,13 +1345,12 @@ }, { "name": "bulkWrite", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "requests": [ { - "name": "insertOne", - "arguments": { + "insertOne": { "document": { "_id": 1 } @@ -1205,27 +1358,29 @@ } ] }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -1234,27 +1389,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on bulkWrite update", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -1278,13 +1436,12 @@ }, { "name": "bulkWrite", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "requests": [ { - "name": "updateOne", - "arguments": { + "updateOne": { "filter": { "_id": 1 }, @@ -1297,27 +1454,29 @@ } ] }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -1326,27 +1485,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on bulkWrite delete", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -1370,13 +1532,12 @@ }, { "name": "bulkWrite", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "requests": [ { - "name": "deleteOne", - "arguments": { + "deleteOne": { "filter": { "_id": 1 } @@ -1384,27 +1545,29 @@ } ] }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -1413,27 +1576,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on find find", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -1457,34 +1623,36 @@ }, { "name": "find", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { "_id": 1 } }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -1493,27 +1661,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on countDocuments aggregate", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -1537,32 +1708,34 @@ }, { "name": "countDocuments", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": {} }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -1571,27 +1744,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on aggregate aggregate", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -1615,32 +1791,34 @@ }, { "name": "aggregate", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "pipeline": [] }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -1649,27 +1827,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on distinct distinct", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -1693,32 +1874,35 @@ }, { "name": "distinct", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", - "fieldName": "_id" + "fieldName": "_id", + "filter": {} }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -1727,27 +1911,30 @@ } ] } - } + ] }, { "description": "remain pinned after non-transient Interrupted error on runCommand insert", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -1771,10 +1958,10 @@ }, { "name": "runCommand", - "object": "database", - "command_name": "insert", + "object": "database0", "arguments": { "session": "session0", + "commandName": "insert", "command": { "insert": "test", "documents": [ @@ -1784,27 +1971,29 @@ ] } }, - "result": { + "expectError": { "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionPinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -1813,27 +2002,30 @@ } ] } - } + ] }, { - "description": "unpin after transient connection error on insertOne insert", - "useMultipleMongoses": true, + "description": "remain pinned after non-transient Interrupted error on clientBulkWrite bulkWrite", "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -1848,43 +2040,53 @@ }, "data": { "failCommands": [ - "insert" + "bulkWrite" ], - "closeConnection": true + "errorCode": 11601 } } } }, { - "name": "insertOne", - "object": "collection", + "name": "clientBulkWrite", + "object": "client0", "arguments": { "session": "session0", - "document": { - "_id": 4 - } + "models": [ + { + "insertOne": { + "namespace": "database0.collection0", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] }, - "result": { - "errorLabelsContain": [ + "expectError": { + "errorLabelsOmit": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionPinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -1893,27 +2095,120 @@ } ] } - } - }, - { + ], + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ] + }, + { + "description": "unpin after transient connection error on insertOne insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { "description": "unpin after transient ShutdownInProgress error on insertOne insert", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -1937,34 +2232,36 @@ }, { "name": "insertOne", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "document": { "_id": 4 } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -1973,27 +2270,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on insertMany insert", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -2017,7 +2317,7 @@ }, { "name": "insertMany", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "documents": [ @@ -2029,27 +2329,29 @@ } ] }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -2058,27 +2360,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on insertMany insert", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -2102,7 +2407,7 @@ }, { "name": "insertMany", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "documents": [ @@ -2114,27 +2419,29 @@ } ] }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -2143,27 +2450,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on updateOne update", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -2187,7 +2497,7 @@ }, { "name": "updateOne", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -2199,27 +2509,29 @@ } } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -2228,27 +2540,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on updateOne update", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -2272,7 +2587,7 @@ }, { "name": "updateOne", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -2284,27 +2599,29 @@ } } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -2313,27 +2630,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on replaceOne update", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -2357,7 +2677,7 @@ }, { "name": "replaceOne", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -2367,27 +2687,29 @@ "y": 1 } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -2396,27 +2718,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on replaceOne update", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -2440,7 +2765,7 @@ }, { "name": "replaceOne", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -2450,27 +2775,29 @@ "y": 1 } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -2479,27 +2806,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on updateMany update", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -2523,7 +2853,7 @@ }, { "name": "updateMany", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -2537,27 +2867,29 @@ } } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -2566,27 +2898,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on updateMany update", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -2610,7 +2945,7 @@ }, { "name": "updateMany", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -2624,27 +2959,29 @@ } } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -2653,27 +2990,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on deleteOne delete", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -2697,34 +3037,36 @@ }, { "name": "deleteOne", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { "_id": 1 } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -2733,27 +3075,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on deleteOne delete", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -2777,34 +3122,36 @@ }, { "name": "deleteOne", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { "_id": 1 } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -2813,27 +3160,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on deleteMany delete", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -2857,7 +3207,7 @@ }, { "name": "deleteMany", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -2866,27 +3216,29 @@ } } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -2895,27 +3247,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on deleteMany delete", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -2939,7 +3294,7 @@ }, { "name": "deleteMany", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -2948,27 +3303,29 @@ } } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -2977,27 +3334,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on findOneAndDelete findAndModify", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -3021,34 +3381,36 @@ }, { "name": "findOneAndDelete", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { "_id": 1 } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -3057,27 +3419,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on findOneAndDelete findAndModify", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -3101,34 +3466,36 @@ }, { "name": "findOneAndDelete", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { "_id": 1 } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -3137,27 +3504,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on findOneAndUpdate findAndModify", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -3181,7 +3551,7 @@ }, { "name": "findOneAndUpdate", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -3194,27 +3564,29 @@ }, "returnDocument": "Before" }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -3223,27 +3595,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on findOneAndUpdate findAndModify", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -3267,7 +3642,7 @@ }, { "name": "findOneAndUpdate", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -3280,27 +3655,29 @@ }, "returnDocument": "Before" }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -3309,27 +3686,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on findOneAndReplace findAndModify", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -3353,7 +3733,7 @@ }, { "name": "findOneAndReplace", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -3364,27 +3744,29 @@ }, "returnDocument": "Before" }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -3393,27 +3775,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on findOneAndReplace findAndModify", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -3437,7 +3822,7 @@ }, { "name": "findOneAndReplace", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { @@ -3448,27 +3833,29 @@ }, "returnDocument": "Before" }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -3477,27 +3864,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on bulkWrite insert", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -3521,13 +3911,12 @@ }, { "name": "bulkWrite", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "requests": [ { - "name": "insertOne", - "arguments": { + "insertOne": { "document": { "_id": 1 } @@ -3535,27 +3924,29 @@ } ] }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -3564,27 +3955,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on bulkWrite insert", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -3608,13 +4002,12 @@ }, { "name": "bulkWrite", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "requests": [ { - "name": "insertOne", - "arguments": { + "insertOne": { "document": { "_id": 1 } @@ -3622,27 +4015,29 @@ } ] }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -3651,27 +4046,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on bulkWrite update", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -3695,13 +4093,12 @@ }, { "name": "bulkWrite", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "requests": [ { - "name": "updateOne", - "arguments": { + "updateOne": { "filter": { "_id": 1 }, @@ -3714,27 +4111,29 @@ } ] }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -3743,27 +4142,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on bulkWrite update", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -3787,13 +4189,12 @@ }, { "name": "bulkWrite", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "requests": [ { - "name": "updateOne", - "arguments": { + "updateOne": { "filter": { "_id": 1 }, @@ -3806,27 +4207,29 @@ } ] }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -3835,27 +4238,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on bulkWrite delete", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -3879,13 +4285,12 @@ }, { "name": "bulkWrite", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "requests": [ { - "name": "deleteOne", - "arguments": { + "deleteOne": { "filter": { "_id": 1 } @@ -3893,27 +4298,29 @@ } ] }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -3922,27 +4329,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on bulkWrite delete", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -3966,13 +4376,12 @@ }, { "name": "bulkWrite", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "requests": [ { - "name": "deleteOne", - "arguments": { + "deleteOne": { "filter": { "_id": 1 } @@ -3980,27 +4389,29 @@ } ] }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -4009,27 +4420,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on find find", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -4053,34 +4467,36 @@ }, { "name": "find", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { "_id": 1 } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -4089,27 +4505,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on find find", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -4133,34 +4552,36 @@ }, { "name": "find", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": { "_id": 1 } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -4169,27 +4590,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on countDocuments aggregate", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -4213,32 +4637,34 @@ }, { "name": "countDocuments", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": {} }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -4247,27 +4673,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on countDocuments aggregate", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -4291,32 +4720,34 @@ }, { "name": "countDocuments", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "filter": {} }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -4325,27 +4756,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on aggregate aggregate", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -4369,32 +4803,34 @@ }, { "name": "aggregate", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "pipeline": [] }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -4403,27 +4839,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on aggregate aggregate", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -4447,32 +4886,34 @@ }, { "name": "aggregate", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", "pipeline": [] }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -4481,27 +4922,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on distinct distinct", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -4525,32 +4969,35 @@ }, { "name": "distinct", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", - "fieldName": "_id" + "fieldName": "_id", + "filter": {} }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -4559,27 +5006,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on distinct distinct", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -4603,32 +5053,35 @@ }, { "name": "distinct", - "object": "collection", + "object": "collection0", "arguments": { "session": "session0", - "fieldName": "_id" + "fieldName": "_id", + "filter": {} }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -4637,27 +5090,30 @@ } ] } - } + ] }, { "description": "unpin after transient connection error on runCommand insert", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -4681,10 +5137,10 @@ }, { "name": "runCommand", - "object": "database", - "command_name": "insert", + "object": "database0", "arguments": { "session": "session0", + "commandName": "insert", "command": { "insert": "test", "documents": [ @@ -4694,27 +5150,29 @@ ] } }, - "result": { + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { - "name": "assertSessionUnpinned", "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -4723,27 +5181,30 @@ } ] } - } + ] }, { "description": "unpin after transient ShutdownInProgress error on runCommand insert", - "useMultipleMongoses": true, "operations": [ { - "name": "startTransaction", - "object": "session0" + "object": "session0", + "name": "startTransaction" }, { + "object": "collection0", "name": "insertOne", - "object": "collection", "arguments": { "session": "session0", "document": { "_id": 3 } }, - "result": { - "insertedId": 3 + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } } }, { @@ -4767,10 +5228,10 @@ }, { "name": "runCommand", - "object": "database", - "command_name": "insert", + "object": "database0", "arguments": { "session": "session0", + "commandName": "insert", "command": { "insert": "test", "documents": [ @@ -4780,27 +5241,220 @@ ] } }, - "result": { + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on clientBulkWrite bulkWrite", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "closeConnection": true + } + } + } + }, + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "session": "session0", + "models": [ + { + "insertOne": { + "namespace": "database0.collection0", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + }, + "expectError": { "errorLabelsContain": [ "TransientTransactionError" ] } }, { + "object": "testRunner", "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on clientBulkWrite bulkWrite", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "session": "session0", + "models": [ + { + "insertOne": { + "namespace": "database0.collection0", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", "arguments": { "session": "session0" } }, { - "name": "abortTransaction", - "object": "session0" + "object": "session0", + "name": "abortTransaction" } ], - "outcome": { - "collection": { - "data": [ + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ { "_id": 1 }, @@ -4809,7 +5463,12 @@ } ] } - } + ], + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ] } ] } diff --git a/testdata/transactions/legacy/mongos-pin-auto.yml b/testdata/transactions/unified/mongos-pin-auto.yml similarity index 67% rename from testdata/transactions/legacy/mongos-pin-auto.yml rename to testdata/transactions/unified/mongos-pin-auto.yml index 7e2e3e4453..db620c371b 100644 --- a/testdata/transactions/legacy/mongos-pin-auto.yml +++ b/testdata/transactions/unified/mongos-pin-auto.yml @@ -1,219 +1,220 @@ # Autogenerated tests that transient errors in a transaction unpin the session. # See mongos-pin-auto-tests.py -runOn: - - - minServerVersion: "4.1.8" - topology: ["sharded"] - # serverless proxy doesn't append error labels to errors in transactions - # caused by failpoints (CLOUDP-88216) - serverless: "forbid" -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: &data - - {_id: 1} - - {_id: 2} +description: mongos-pin-auto + +schemaVersion: '1.4' + +runOnRequirements: + - minServerVersion: "4.1.8" + # Note: tests utilize targetedFailPoint, which is incompatible with + # load-balanced and useMultipleMongoses:true + topologies: [ sharded ] + # serverless proxy doesn't append error labels to errors in transactions + # caused by failpoints (CLOUDP-88216) + serverless: "forbid" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collection_name + databaseName: *database_name + documents: &data + - { _id: 1 } + - { _id: 2 } tests: - description: remain pinned after non-transient Interrupted error on insertOne - useMultipleMongoses: true operations: - &startTransaction - name: startTransaction object: session0 + name: startTransaction - &initialCommand + object: *collection0 name: insertOne - object: collection arguments: - session: session0 - document: {_id: 3} - result: - insertedId: 3 - - name: targetedFailPoint - object: testRunner + session: *session0 + document: { _id: 3 } + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } } + - object: testRunner + name: targetedFailPoint arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand - mode: {times: 1} + mode: { times: 1 } data: - failCommands: ["insert"] + failCommands: [ "insert" ] errorCode: 11601 - - name: insertOne - object: collection + - object: *collection0 + name: insertOne arguments: - session: session0 - document: - _id: 4 - result: + session: *session0 + document: { _id: 4 } + expectError: errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] errorCodeName: Interrupted - &assertSessionPinned - name: assertSessionPinned object: testRunner + name: assertSessionPinned arguments: - session: session0 + session: *session0 - &commitTransaction + object: *session0 name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 4 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - recoveryToken: 42 - command_name: commitTransaction - database_name: admin - - outcome: &outcome - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} - - - description: unpin after transient error within a transaction - useMultipleMongoses: true + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 3 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + + - description: 'unpin after transient error within a transaction' operations: - - &startTransaction - name: startTransaction - object: session0 - - &initialCommand - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 3 - result: - insertedId: 3 - - name: targetedFailPoint - object: testRunner + - *startTransaction + - *initialCommand + - object: testRunner + name: targetedFailPoint arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: { times: 1 } data: - failCommands: ["insert"] + failCommands: [ "insert" ] closeConnection: true - - name: insertOne - object: collection + - object: *collection0 + name: insertOne arguments: - session: session0 - document: - _id: 4 - result: + session: *session0 + document: { _id: 4 } + expectError: errorLabelsContain: ["TransientTransactionError"] errorLabelsOmit: ["UnknownTransactionCommitResult"] - # Session unpins from the first mongos after the insert error and - # abortTransaction succeeds immediately on any mongos. - &assertSessionUnpinned - name: assertSessionUnpinned object: testRunner + name: assertSessionUnpinned arguments: - session: session0 + session: *session0 - &abortTransaction + object: *session0 name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 4 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - recoveryToken: 42 - command_name: abortTransaction - database_name: admin - + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 3 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: abortTransaction + databaseName: admin outcome: &outcome - collection: - data: *data + - collectionName: *collection_name + databaseName: *database_name + documents: *data # The rest of the tests in this file test every operation type against # multiple types of transient errors (connection and error code). - description: remain pinned after non-transient Interrupted error on insertOne insert - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -221,25 +222,24 @@ tests: failCommands: ["insert"] errorCode: 11601 - name: insertOne - object: collection + object: *collection0 arguments: - session: session0 - document: {_id: 4} - result: + session: *session0 + document: { _id: 4 } + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on insertMany insert - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -247,25 +247,24 @@ tests: failCommands: ["insert"] errorCode: 11601 - name: insertMany - object: collection + object: *collection0 arguments: - session: session0 - documents: [{_id: 4}, {_id: 5}] - result: + session: *session0 + documents: [ { _id: 4 }, { _id: 5 } ] + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on updateOne update - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -273,26 +272,25 @@ tests: failCommands: ["update"] errorCode: 11601 - name: updateOne - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - update: {$inc: {x: 1}} - result: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on replaceOne update - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -300,26 +298,25 @@ tests: failCommands: ["update"] errorCode: 11601 - name: replaceOne - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - replacement: {y: 1} - result: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on updateMany update - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -327,26 +324,25 @@ tests: failCommands: ["update"] errorCode: 11601 - name: updateMany - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: {$gte: 1}} - update: {$set: {z: 1}} - result: + session: *session0 + filter: { _id: { $gte: 1 } } + update: {$set: { z: 1 } } + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on deleteOne delete - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -354,25 +350,24 @@ tests: failCommands: ["delete"] errorCode: 11601 - name: deleteOne - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - result: + session: *session0 + filter: { _id: 1 } + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on deleteMany delete - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -380,25 +375,24 @@ tests: failCommands: ["delete"] errorCode: 11601 - name: deleteMany - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: {$gte: 1}} - result: + session: *session0 + filter: { _id: { $gte: 1 } } + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on findOneAndDelete findAndModify - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -406,25 +400,24 @@ tests: failCommands: ["findAndModify"] errorCode: 11601 - name: findOneAndDelete - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - result: + session: *session0 + filter: { _id: 1 } + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on findOneAndUpdate findAndModify - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -432,27 +425,26 @@ tests: failCommands: ["findAndModify"] errorCode: 11601 - name: findOneAndUpdate - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - update: {$inc: {x: 1}} + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } returnDocument: Before - result: + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on findOneAndReplace findAndModify - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -460,27 +452,26 @@ tests: failCommands: ["findAndModify"] errorCode: 11601 - name: findOneAndReplace - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - replacement: {y: 1} + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } returnDocument: Before - result: + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on bulkWrite insert - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -488,28 +479,26 @@ tests: failCommands: ["insert"] errorCode: 11601 - name: bulkWrite - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 requests: - - name: insertOne - arguments: - document: {_id: 1} - result: + - insertOne: + document: { _id: 1 } + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on bulkWrite update - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -517,29 +506,27 @@ tests: failCommands: ["update"] errorCode: 11601 - name: bulkWrite - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 requests: - - name: updateOne - arguments: - filter: {_id: 1} - update: {$set: {x: 1}} - result: + - updateOne: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on bulkWrite delete - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -547,28 +534,26 @@ tests: failCommands: ["delete"] errorCode: 11601 - name: bulkWrite - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 requests: - - name: deleteOne - arguments: - filter: {_id: 1} - result: + - deleteOne: + filter: { _id: 1 } + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on find find - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -576,25 +561,24 @@ tests: failCommands: ["find"] errorCode: 11601 - name: find - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - result: + session: *session0 + filter: { _id: 1 } + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on countDocuments aggregate - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -602,25 +586,24 @@ tests: failCommands: ["aggregate"] errorCode: 11601 - name: countDocuments - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 filter: {} - result: + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on aggregate aggregate - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -628,25 +611,24 @@ tests: failCommands: ["aggregate"] errorCode: 11601 - name: aggregate - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 pipeline: [] - result: + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on distinct distinct - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -654,25 +636,25 @@ tests: failCommands: ["distinct"] errorCode: 11601 - name: distinct - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 fieldName: _id - result: + filter: {} + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome - description: remain pinned after non-transient Interrupted error on runCommand insert - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -680,29 +662,58 @@ tests: failCommands: ["insert"] errorCode: 11601 - name: runCommand - object: database - command_name: insert + object: *database0 arguments: - session: session0 + session: *session0 + commandName: insert command: insert: *collection_name documents: - - _id : 1 - result: + - { _id : 1 } + expectError: errorLabelsOmit: ["TransientTransactionError"] - *assertSessionPinned - *abortTransaction outcome: *outcome + - description: remain pinned after non-transient Interrupted error on clientBulkWrite bulkWrite + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["bulkWrite"] + errorCode: 11601 + - name: clientBulkWrite + object: *client0 + arguments: + session: *session0 + models: + - insertOne: + namespace: database0.collection0 + document: { _id: 8, x: 88 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0" + - description: unpin after transient connection error on insertOne insert - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -710,25 +721,24 @@ tests: failCommands: ["insert"] closeConnection: true - name: insertOne - object: collection + object: *collection0 arguments: - session: session0 - document: {_id: 4} - result: + session: *session0 + document: { _id: 4 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on insertOne insert - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -736,25 +746,24 @@ tests: failCommands: ["insert"] errorCode: 91 - name: insertOne - object: collection + object: *collection0 arguments: - session: session0 - document: {_id: 4} - result: + session: *session0 + document: { _id: 4 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on insertMany insert - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -762,25 +771,24 @@ tests: failCommands: ["insert"] closeConnection: true - name: insertMany - object: collection + object: *collection0 arguments: - session: session0 - documents: [{_id: 4}, {_id: 5}] - result: + session: *session0 + documents: [ { _id: 4 }, { _id: 5 } ] + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on insertMany insert - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -788,25 +796,24 @@ tests: failCommands: ["insert"] errorCode: 91 - name: insertMany - object: collection + object: *collection0 arguments: - session: session0 - documents: [{_id: 4}, {_id: 5}] - result: + session: *session0 + documents: [ { _id: 4 }, { _id: 5 } ] + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on updateOne update - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -814,26 +821,25 @@ tests: failCommands: ["update"] closeConnection: true - name: updateOne - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - update: {$inc: {x: 1}} - result: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on updateOne update - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -841,26 +847,25 @@ tests: failCommands: ["update"] errorCode: 91 - name: updateOne - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - update: {$inc: {x: 1}} - result: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on replaceOne update - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -868,26 +873,25 @@ tests: failCommands: ["update"] closeConnection: true - name: replaceOne - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - replacement: {y: 1} - result: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on replaceOne update - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -895,26 +899,25 @@ tests: failCommands: ["update"] errorCode: 91 - name: replaceOne - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - replacement: {y: 1} - result: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on updateMany update - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -922,26 +925,25 @@ tests: failCommands: ["update"] closeConnection: true - name: updateMany - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: {$gte: 1}} - update: {$set: {z: 1}} - result: + session: *session0 + filter: { _id: { $gte: 1 } } + update: {$set: { z: 1 } } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on updateMany update - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -949,26 +951,25 @@ tests: failCommands: ["update"] errorCode: 91 - name: updateMany - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: {$gte: 1}} - update: {$set: {z: 1}} - result: + session: *session0 + filter: { _id: { $gte: 1 } } + update: {$set: { z: 1 } } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on deleteOne delete - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -976,25 +977,24 @@ tests: failCommands: ["delete"] closeConnection: true - name: deleteOne - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - result: + session: *session0 + filter: { _id: 1 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on deleteOne delete - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1002,25 +1002,24 @@ tests: failCommands: ["delete"] errorCode: 91 - name: deleteOne - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - result: + session: *session0 + filter: { _id: 1 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on deleteMany delete - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1028,25 +1027,24 @@ tests: failCommands: ["delete"] closeConnection: true - name: deleteMany - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: {$gte: 1}} - result: + session: *session0 + filter: { _id: { $gte: 1 } } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on deleteMany delete - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1054,25 +1052,24 @@ tests: failCommands: ["delete"] errorCode: 91 - name: deleteMany - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: {$gte: 1}} - result: + session: *session0 + filter: { _id: { $gte: 1 } } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on findOneAndDelete findAndModify - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1080,25 +1077,24 @@ tests: failCommands: ["findAndModify"] closeConnection: true - name: findOneAndDelete - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - result: + session: *session0 + filter: { _id: 1 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on findOneAndDelete findAndModify - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1106,25 +1102,24 @@ tests: failCommands: ["findAndModify"] errorCode: 91 - name: findOneAndDelete - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - result: + session: *session0 + filter: { _id: 1 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on findOneAndUpdate findAndModify - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1132,27 +1127,26 @@ tests: failCommands: ["findAndModify"] closeConnection: true - name: findOneAndUpdate - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - update: {$inc: {x: 1}} + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } returnDocument: Before - result: + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on findOneAndUpdate findAndModify - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1160,27 +1154,26 @@ tests: failCommands: ["findAndModify"] errorCode: 91 - name: findOneAndUpdate - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - update: {$inc: {x: 1}} + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } returnDocument: Before - result: + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on findOneAndReplace findAndModify - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1188,27 +1181,26 @@ tests: failCommands: ["findAndModify"] closeConnection: true - name: findOneAndReplace - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - replacement: {y: 1} + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } returnDocument: Before - result: + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on findOneAndReplace findAndModify - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1216,27 +1208,26 @@ tests: failCommands: ["findAndModify"] errorCode: 91 - name: findOneAndReplace - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - replacement: {y: 1} + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } returnDocument: Before - result: + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on bulkWrite insert - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1244,28 +1235,26 @@ tests: failCommands: ["insert"] closeConnection: true - name: bulkWrite - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 requests: - - name: insertOne - arguments: - document: {_id: 1} - result: + - insertOne: + document: { _id: 1 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on bulkWrite insert - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1273,28 +1262,26 @@ tests: failCommands: ["insert"] errorCode: 91 - name: bulkWrite - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 requests: - - name: insertOne - arguments: - document: {_id: 1} - result: + - insertOne: + document: { _id: 1 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on bulkWrite update - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1302,29 +1289,27 @@ tests: failCommands: ["update"] closeConnection: true - name: bulkWrite - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 requests: - - name: updateOne - arguments: - filter: {_id: 1} - update: {$set: {x: 1}} - result: + - updateOne: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on bulkWrite update - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1332,29 +1317,27 @@ tests: failCommands: ["update"] errorCode: 91 - name: bulkWrite - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 requests: - - name: updateOne - arguments: - filter: {_id: 1} - update: {$set: {x: 1}} - result: + - updateOne: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on bulkWrite delete - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1362,28 +1345,26 @@ tests: failCommands: ["delete"] closeConnection: true - name: bulkWrite - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 requests: - - name: deleteOne - arguments: - filter: {_id: 1} - result: + - deleteOne: + filter: { _id: 1 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on bulkWrite delete - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1391,28 +1372,26 @@ tests: failCommands: ["delete"] errorCode: 91 - name: bulkWrite - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 requests: - - name: deleteOne - arguments: - filter: {_id: 1} - result: + - deleteOne: + filter: { _id: 1 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on find find - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1420,25 +1399,24 @@ tests: failCommands: ["find"] closeConnection: true - name: find - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - result: + session: *session0 + filter: { _id: 1 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on find find - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1446,25 +1424,24 @@ tests: failCommands: ["find"] errorCode: 91 - name: find - object: collection + object: *collection0 arguments: - session: session0 - filter: {_id: 1} - result: + session: *session0 + filter: { _id: 1 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on countDocuments aggregate - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1472,25 +1449,24 @@ tests: failCommands: ["aggregate"] closeConnection: true - name: countDocuments - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 filter: {} - result: + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on countDocuments aggregate - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1498,25 +1474,24 @@ tests: failCommands: ["aggregate"] errorCode: 91 - name: countDocuments - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 filter: {} - result: + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on aggregate aggregate - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1524,25 +1499,24 @@ tests: failCommands: ["aggregate"] closeConnection: true - name: aggregate - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 pipeline: [] - result: + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on aggregate aggregate - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1550,25 +1524,24 @@ tests: failCommands: ["aggregate"] errorCode: 91 - name: aggregate - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 pipeline: [] - result: + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on distinct distinct - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1576,25 +1549,25 @@ tests: failCommands: ["distinct"] closeConnection: true - name: distinct - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 fieldName: _id - result: + filter: {} + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on distinct distinct - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1602,25 +1575,25 @@ tests: failCommands: ["distinct"] errorCode: 91 - name: distinct - object: collection + object: *collection0 arguments: - session: session0 + session: *session0 fieldName: _id - result: + filter: {} + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient connection error on runCommand insert - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1628,29 +1601,28 @@ tests: failCommands: ["insert"] closeConnection: true - name: runCommand - object: database - command_name: insert + object: *database0 arguments: - session: session0 + session: *session0 + commandName: insert command: insert: *collection_name documents: - - _id : 1 - result: + - { _id : 1 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome - description: unpin after transient ShutdownInProgress error on runCommand insert - useMultipleMongoses: true operations: - *startTransaction - *initialCommand - name: targetedFailPoint object: testRunner arguments: - session: session0 + session: *session0 failPoint: configureFailPoint: failCommand mode: {times: 1} @@ -1658,17 +1630,76 @@ tests: failCommands: ["insert"] errorCode: 91 - name: runCommand - object: database - command_name: insert + object: *database0 arguments: - session: session0 + session: *session0 + commandName: insert command: insert: *collection_name documents: - - _id : 1 - result: + - { _id : 1 } + expectError: errorLabelsContain: ["TransientTransactionError"] - *assertSessionUnpinned - *abortTransaction outcome: *outcome + - description: unpin after transient connection error on clientBulkWrite bulkWrite + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["bulkWrite"] + closeConnection: true + - name: clientBulkWrite + object: *client0 + arguments: + session: *session0 + models: + - insertOne: + namespace: database0.collection0 + document: { _id: 8, x: 88 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0" + + - description: unpin after transient ShutdownInProgress error on clientBulkWrite bulkWrite + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["bulkWrite"] + errorCode: 91 + - name: clientBulkWrite + object: *client0 + arguments: + session: *session0 + models: + - insertOne: + namespace: database0.collection0 + document: { _id: 8, x: 88 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0"