From ebf9ccb5a782424a733eb1817cb1a601daf61cf8 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 13 Apr 2023 16:49:45 -0400 Subject: [PATCH 01/28] wip --- test/integration/run-command/.gitkeep | 0 .../run-command/run_command.spec.test.ts | 6 + test/spec/run-command/run-command.json | 372 ++++++++++++++++++ test/spec/run-command/run-command.yml | 205 ++++++++++ test/spec/run-command/run-cursor-command.json | 160 ++++++++ test/spec/run-command/run-cursor-command.yml | 85 ++++ test/tools/unified-spec-runner/match.ts | 2 +- test/tools/unified-spec-runner/operations.ts | 17 +- 8 files changed, 843 insertions(+), 4 deletions(-) create mode 100644 test/integration/run-command/.gitkeep create mode 100644 test/integration/run-command/run_command.spec.test.ts create mode 100644 test/spec/run-command/run-command.json create mode 100644 test/spec/run-command/run-command.yml create mode 100644 test/spec/run-command/run-cursor-command.json create mode 100644 test/spec/run-command/run-cursor-command.yml diff --git a/test/integration/run-command/.gitkeep b/test/integration/run-command/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/integration/run-command/run_command.spec.test.ts b/test/integration/run-command/run_command.spec.test.ts new file mode 100644 index 00000000000..c2ca5e91b5f --- /dev/null +++ b/test/integration/run-command/run_command.spec.test.ts @@ -0,0 +1,6 @@ +import { loadSpecTests } from '../../spec'; +import { runUnifiedSuite } from '../../tools/unified-spec-runner/runner'; + +describe('RunCommand spec', () => { + runUnifiedSuite(loadSpecTests('run-command')); +}); diff --git a/test/spec/run-command/run-command.json b/test/spec/run-command/run-command.json new file mode 100644 index 00000000000..5f55f3304cd --- /dev/null +++ b/test/spec/run-command/run-command.json @@ -0,0 +1,372 @@ +{ + "description": "runCommand spec requirements", + "schemaVersion": "1.0", + "runOnRequirements": {}, + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "plainDb", + "client": "client", + "databaseName": "plainDb" + } + }, + { + "collection": { + "id": "collection", + "database": "plainDb", + "collectionName": "collection" + } + }, + { + "database": { + "id": "dbWithRC", + "client": "client", + "databaseName": "dbWithRC", + "readConcern": { + "level": "local" + } + } + }, + { + "database": { + "id": "dbWithWC", + "client": "client", + "databaseName": "dbWithWC", + "writeConcern": { + "w": 0 + } + } + }, + { + "session": { + "id": "session", + "client": "client" + } + } + ], + "tests": [ + { + "description": "executes a ping with $db attached", + "operations": [ + { + "name": "runCommand", + "object": "plainDb", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "$db": "plainDb" + }, + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "executes a ping with lsid attached", + "operations": [ + { + "name": "runCommand", + "object": "plainDb", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + }, + "session": "session" + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "lsid": { + "$$sessionLsid": "session" + }, + "$db": "plainDb" + }, + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "executes a ping with $readPreference attached", + "operations": [ + { + "name": "runCommand", + "object": "plainDb", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + }, + "readPreference": "nearest" + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "$readPreference": { + "mode": "nearest" + }, + "$db": "plainDb" + }, + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "executes a ping without attaching readConcern", + "operations": [ + { + "name": "runCommand", + "object": "dbWithRC", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "readConcern": { + "$$exists": false + }, + "$db": "dbWithRC" + }, + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "executes a ping without attaching writeConcern", + "operations": [ + { + "name": "runCommand", + "object": "dbWithWC", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "writeConcern": { + "$$exists": false + }, + "$db": "dbWithWC" + }, + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "executes a ping and does not retry retryable error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "ping" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "plainDb", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + } + ] + }, + { + "description": "executes a ping inside a transaction", + "operations": [ + { + "name": "withTransaction", + "object": "session", + "arguments": { + "callback": [ + { + "name": "runCommand", + "object": "plainDb", + "arguments": { + "session": "session", + "commandName": "insert", + "command": { + "insert": "collection", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "collection", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session" + }, + "txnNumber": 1, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "local" + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "plainDb" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session" + }, + "txnNumber": 1, + "autocommit": false, + "writeConcern": { + "w": 1 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + } + ] +} diff --git a/test/spec/run-command/run-command.yml b/test/spec/run-command/run-command.yml new file mode 100644 index 00000000000..8f8111218b5 --- /dev/null +++ b/test/spec/run-command/run-command.yml @@ -0,0 +1,205 @@ +description: runCommand spec requirements + +schemaVersion: "1.0" + +runOnRequirements: {} + +createEntities: + - client: + id: client + observeEvents: [commandStartedEvent] + - database: + id: plainDb + client: client + databaseName: plainDb + - collection: + id: collection + database: plainDb + collectionName: collection + - database: + id: dbWithRC + client: client + databaseName: dbWithRC + readConcern: + level: 'local' + - database: + id: dbWithWC + client: client + databaseName: dbWithWC + writeConcern: + w: 0 + - session: + id: session + client: client + + +tests: + - description: executes a ping with $db attached + operations: + - name: runCommand + object: plainDb + arguments: + commandName: ping + command: + ping: 1 + expectResult: + ok: 1 + expectEvents: + - client: client + events: + - commandStartedEvent: + command: + ping: 1 + $db: plainDb + commandName: ping + + - description: executes a ping with lsid attached + operations: + - name: runCommand + object: plainDb + arguments: + commandName: ping + command: + ping: 1 + session: session + expectResult: + ok: 1 + expectEvents: + - client: client + events: + - commandStartedEvent: + command: + ping: 1 + lsid: { $$sessionLsid: session } + $db: plainDb + commandName: ping + + - description: executes a ping with $readPreference attached + operations: + - name: runCommand + object: plainDb + arguments: + commandName: ping + command: + ping: 1 + readPreference: 'nearest' + expectResult: + ok: 1 + expectEvents: + - client: client + events: + - commandStartedEvent: + command: + ping: 1 + $readPreference: { mode: 'nearest' } + $db: plainDb + commandName: ping + + - description: executes a ping without attaching readConcern + operations: + - name: runCommand + object: dbWithRC + arguments: + commandName: ping + command: + ping: 1 + expectResult: + ok: 1 + expectEvents: + - client: client + events: + - commandStartedEvent: + command: + ping: 1 + readConcern: { $$exists: false } + $db: dbWithRC + commandName: ping + + - description: executes a ping without attaching writeConcern + operations: + - name: runCommand + object: dbWithWC + arguments: + commandName: ping + command: + ping: 1 + expectResult: + ok: 1 + expectEvents: + - client: client + events: + - commandStartedEvent: + command: + ping: 1 + writeConcern: { $$exists: false } + $db: dbWithWC + commandName: ping + + - description: executes a ping and does not retry retryable error + operations: + - name: failPoint + object: testRunner + arguments: + client: client + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ping] + closeConnection: true + - name: runCommand + object: plainDb + arguments: + commandName: ping + command: + ping: 1 + expectError: + isError: true + + - description: executes a ping inside a transaction + operations: + - name: withTransaction + object: session + arguments: + callback: + - name: runCommand + object: plainDb + arguments: + session: session + commandName: insert + command: + insert: collection + documents: [ { _id: 1 } ] + ordered: true + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } } + expectEvents: + - client: client + events: + - commandStartedEvent: + command: + insert: collection + documents: [ { _id: 1 } ] + ordered: true + lsid: { $$sessionLsid: session } + txnNumber: 1 + startTransaction: true + autocommit: false + readConcern: { level: local } + # omitted fields + writeConcern: { $$exists: false } + commandName: insert + databaseName: plainDb + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: session } + txnNumber: 1 + autocommit: false + writeConcern: { w: 1 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + +# TODO - Stable API coverage diff --git a/test/spec/run-command/run-cursor-command.json b/test/spec/run-command/run-cursor-command.json new file mode 100644 index 00000000000..0a1502cf02d --- /dev/null +++ b/test/spec/run-command/run-cursor-command.json @@ -0,0 +1,160 @@ +{ + "description": "run some cursor commands", + "schemaVersion": "1.0", + "runOnRequirements": {}, + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collection0" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "collection0", + "databaseName": "database0", + "documents": [ + { + "_id": 0, + "x": 20 + }, + { + "_id": 1, + "x": 21 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "a find is run by runCursorCommand", + "operations": [ + { + "name": "runCursorCommand", + "object": "database0", + "arguments": { + "commandName": "find", + "command": { + "find": "collection0", + "batchSize": 1 + }, + "comment": "manually running a find" + }, + "saveResultAsEntity": "manualFindCursor" + }, + { + "name": "iterateUntilDocumentOrError", + "object": "manualFindCursor", + "expectResult": { + "_id": 0, + "x": 20 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "manualFindCursor", + "expectResult": { + "_id": 1, + "x": 21 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "manualFindCursor", + "expectResult": { + "_id": 2, + "x": 22 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "manualFindCursor", + "expectResult": null + }, + { + "name": "iterateUntilDocumentOrError", + "object": "manualFindCursor", + "expectError": { + "isClientError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "collection0", + "$db": "database0", + "comment": { + "$$exists": false + }, + "lsid": { + "id": { + "$$type": "binData" + } + }, + "batchSize": 1, + "$clusterTime": { + "$$exists": false + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": "long" + }, + "collection": "collection0", + "comment": "manually running a find", + "$db": "database0", + "lsid": { + "id": { + "$$type": "binData" + } + }, + "$clusterTime": { + "$$exists": false + } + }, + "commandName": "getMore" + } + } + ] + } + ] + } + ] +} diff --git a/test/spec/run-command/run-cursor-command.yml b/test/spec/run-command/run-cursor-command.yml new file mode 100644 index 00000000000..bb9f0a6dbdc --- /dev/null +++ b/test/spec/run-command/run-cursor-command.yml @@ -0,0 +1,85 @@ +description: run some cursor commands + +schemaVersion: "1.0" + +runOnRequirements: {} + +createEntities: + - client: + id: &client0 client0 + observeEvents: [commandStartedEvent] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name database0 + - collection: + id: collection0 + database: *database0 + collectionName: &collection0Name collection0 + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 0, x: 20 } + - { _id: 1, x: 21 } + - { _id: 2, x: 22 } + +tests: + - description: a find is run by runCursorCommand + operations: + - name: runCursorCommand + object: *database0 + arguments: + commandName: find + command: + find: *collection0Name + batchSize: 1 + comment: 'manually running a find' + saveResultAsEntity: &manualFindCursor manualFindCursor + - name: iterateUntilDocumentOrError + object: *manualFindCursor + expectResult: + _id: 0 + x: 20 + - name: iterateUntilDocumentOrError + object: *manualFindCursor + expectResult: + _id: 1 + x: 21 + - name: iterateUntilDocumentOrError + object: *manualFindCursor + expectResult: + _id: 2 + x: 22 + - name: iterateUntilDocumentOrError + object: *manualFindCursor + expectResult: null + - name: iterateUntilDocumentOrError + object: *manualFindCursor + expectError: + isClientError: true + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + $db: *database0Name + comment: { $$exists: false } + lsid: { id: { $$type: 'binData' } } + batchSize: 1 + $clusterTime: { $$exists: false } + commandName: find + - commandStartedEvent: + command: + getMore: { $$type: 'long' } + collection: *collection0Name + comment: 'manually running a find' + $db: *database0Name + lsid: { id: { $$type: 'binData' } } + $clusterTime: { $$exists: false } + commandName: getMore diff --git a/test/tools/unified-spec-runner/match.ts b/test/tools/unified-spec-runner/match.ts index 13d031187a2..4d52eaa9dff 100644 --- a/test/tools/unified-spec-runner/match.ts +++ b/test/tools/unified-spec-runner/match.ts @@ -220,7 +220,7 @@ export function resultCheck( } if (typeof actual !== 'object') { - expect.fail('Expected actual value to be an object'); + expect.fail('Expected actual value to be an object' + path.join('.')); } const expectedEntries = Object.entries(expected); diff --git a/test/tools/unified-spec-runner/operations.ts b/test/tools/unified-spec-runner/operations.ts index ee83486ef4f..25e20360fe6 100644 --- a/test/tools/unified-spec-runner/operations.ts +++ b/test/tools/unified-spec-runner/operations.ts @@ -592,11 +592,22 @@ operations.set('withTransaction', async ({ entities, operation, client, testConf maxCommitTimeMS: operation.arguments!.maxCommitTimeMS }; - return session.withTransaction(async () => { - for (const callbackOperation of operation.arguments!.callback) { - await executeOperationAndCheck(callbackOperation, entities, client, testConfig); + let errorFromOperations = null; + const result = await session.withTransaction(async () => { + try { + await (async () => { + for (const callbackOperation of operation.arguments!.callback) { + await executeOperationAndCheck(callbackOperation, entities, client, testConfig); + } + })(); + } catch (error) { + errorFromOperations = error; } }, options); + + if (result == null) { + throw errorFromOperations ?? Error('transaction not committed'); + } }); operations.set('countDocuments', async ({ entities, operation }) => { From ccc624c823a624b47350650f6ef26dd925ac2c40 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 14 Apr 2023 09:48:34 -0400 Subject: [PATCH 02/28] test: runCommand --- test/spec/run-command/run-command.json | 78 ++++++++- test/spec/run-command/run-command.yml | 44 ++++- test/spec/run-command/run-cursor-command.json | 160 ------------------ test/spec/run-command/run-cursor-command.yml | 85 ---------- 4 files changed, 111 insertions(+), 256 deletions(-) delete mode 100644 test/spec/run-command/run-cursor-command.json delete mode 100644 test/spec/run-command/run-cursor-command.yml diff --git a/test/spec/run-command/run-command.json b/test/spec/run-command/run-command.json index 5f55f3304cd..eca7cb8a880 100644 --- a/test/spec/run-command/run-command.json +++ b/test/spec/run-command/run-command.json @@ -50,6 +50,36 @@ "id": "session", "client": "client" } + }, + { + "client": { + "id": "stableApiClient", + "observeEvents": [ + "commandStartedEvent" + ], + "serverApi": { + "version": "1", + "strict": true + } + } + }, + { + "database": { + "id": "stableApiDb", + "client": "stableApiClient", + "databaseName": "stableApiDb" + } + } + ], + "initialData": [ + { + "collectionName": "collection", + "databaseName": "plainDb", + "documents": [ + { + "_id": 0 + } + ] } ], "tests": [ @@ -275,7 +305,7 @@ ] }, { - "description": "executes a ping inside a transaction", + "description": "executes a insert via runCommand inside a transaction", "operations": [ { "name": "withTransaction", @@ -331,7 +361,7 @@ "startTransaction": true, "autocommit": false, "readConcern": { - "level": "local" + "$$exists": false }, "writeConcern": { "$$exists": false @@ -351,12 +381,9 @@ "txnNumber": 1, "autocommit": false, "writeConcern": { - "w": 1 - }, - "readConcern": { "$$exists": false }, - "startTransaction": { + "readConcern": { "$$exists": false } }, @@ -367,6 +394,45 @@ ] } ] + }, + { + "description": "executes a ping with stableApi fields attached", + "operations": [ + { + "name": "runCommand", + "object": "stableApiDb", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "stableApiClient", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "$db": "stableApiDb", + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + }, + "commandName": "ping" + } + } + ] + } + ] } ] } diff --git a/test/spec/run-command/run-command.yml b/test/spec/run-command/run-command.yml index 8f8111218b5..10e40874d54 100644 --- a/test/spec/run-command/run-command.yml +++ b/test/spec/run-command/run-command.yml @@ -31,7 +31,22 @@ createEntities: - session: id: session client: client + # Stable API test + - client: + id: stableApiClient + observeEvents: [commandStartedEvent] + serverApi: + version: "1" + strict: true + - database: + id: stableApiDb + client: stableApiClient + databaseName: stableApiDb +initialData: +- collectionName: collection + databaseName: plainDb + documents: [ { _id: 0 } ] tests: - description: executes a ping with $db attached @@ -156,7 +171,7 @@ tests: expectError: isError: true - - description: executes a ping inside a transaction + - description: executes a insert via runCommand inside a transaction operations: - name: withTransaction object: session @@ -184,8 +199,8 @@ tests: txnNumber: 1 startTransaction: true autocommit: false - readConcern: { level: local } # omitted fields + readConcern: { $$exists: false } writeConcern: { $$exists: false } commandName: insert databaseName: plainDb @@ -195,11 +210,30 @@ tests: lsid: { $$sessionLsid: session } txnNumber: 1 autocommit: false - writeConcern: { w: 1 } # omitted fields + writeConcern: { $$exists: false } readConcern: { $$exists: false } - startTransaction: { $$exists: false } commandName: commitTransaction databaseName: admin -# TODO - Stable API coverage + - description: executes a ping with stableApi fields attached + operations: + - name: runCommand + object: stableApiDb + arguments: + commandName: ping + command: + ping: 1 + expectResult: + ok: 1 + expectEvents: + - client: stableApiClient + events: + - commandStartedEvent: + command: + ping: 1 + $db: stableApiDb + apiVersion: "1" + apiStrict: true + apiDeprecationErrors: { $$unsetOrMatches: false } + commandName: ping diff --git a/test/spec/run-command/run-cursor-command.json b/test/spec/run-command/run-cursor-command.json deleted file mode 100644 index 0a1502cf02d..00000000000 --- a/test/spec/run-command/run-cursor-command.json +++ /dev/null @@ -1,160 +0,0 @@ -{ - "description": "run some cursor commands", - "schemaVersion": "1.0", - "runOnRequirements": {}, - "createEntities": [ - { - "client": { - "id": "client0", - "observeEvents": [ - "commandStartedEvent" - ] - } - }, - { - "database": { - "id": "database0", - "client": "client0", - "databaseName": "database0" - } - }, - { - "collection": { - "id": "collection0", - "database": "database0", - "collectionName": "collection0" - } - }, - { - "session": { - "id": "session0", - "client": "client0" - } - } - ], - "initialData": [ - { - "collectionName": "collection0", - "databaseName": "database0", - "documents": [ - { - "_id": 0, - "x": 20 - }, - { - "_id": 1, - "x": 21 - }, - { - "_id": 2, - "x": 22 - } - ] - } - ], - "tests": [ - { - "description": "a find is run by runCursorCommand", - "operations": [ - { - "name": "runCursorCommand", - "object": "database0", - "arguments": { - "commandName": "find", - "command": { - "find": "collection0", - "batchSize": 1 - }, - "comment": "manually running a find" - }, - "saveResultAsEntity": "manualFindCursor" - }, - { - "name": "iterateUntilDocumentOrError", - "object": "manualFindCursor", - "expectResult": { - "_id": 0, - "x": 20 - } - }, - { - "name": "iterateUntilDocumentOrError", - "object": "manualFindCursor", - "expectResult": { - "_id": 1, - "x": 21 - } - }, - { - "name": "iterateUntilDocumentOrError", - "object": "manualFindCursor", - "expectResult": { - "_id": 2, - "x": 22 - } - }, - { - "name": "iterateUntilDocumentOrError", - "object": "manualFindCursor", - "expectResult": null - }, - { - "name": "iterateUntilDocumentOrError", - "object": "manualFindCursor", - "expectError": { - "isClientError": true - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "find": "collection0", - "$db": "database0", - "comment": { - "$$exists": false - }, - "lsid": { - "id": { - "$$type": "binData" - } - }, - "batchSize": 1, - "$clusterTime": { - "$$exists": false - } - }, - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "command": { - "getMore": { - "$$type": "long" - }, - "collection": "collection0", - "comment": "manually running a find", - "$db": "database0", - "lsid": { - "id": { - "$$type": "binData" - } - }, - "$clusterTime": { - "$$exists": false - } - }, - "commandName": "getMore" - } - } - ] - } - ] - } - ] -} diff --git a/test/spec/run-command/run-cursor-command.yml b/test/spec/run-command/run-cursor-command.yml deleted file mode 100644 index bb9f0a6dbdc..00000000000 --- a/test/spec/run-command/run-cursor-command.yml +++ /dev/null @@ -1,85 +0,0 @@ -description: run some cursor commands - -schemaVersion: "1.0" - -runOnRequirements: {} - -createEntities: - - client: - id: &client0 client0 - observeEvents: [commandStartedEvent] - - database: - id: &database0 database0 - client: *client0 - databaseName: &database0Name database0 - - collection: - id: collection0 - database: *database0 - collectionName: &collection0Name collection0 - - session: - id: &session0 session0 - client: *client0 - -initialData: - - collectionName: *collection0Name - databaseName: *database0Name - documents: - - { _id: 0, x: 20 } - - { _id: 1, x: 21 } - - { _id: 2, x: 22 } - -tests: - - description: a find is run by runCursorCommand - operations: - - name: runCursorCommand - object: *database0 - arguments: - commandName: find - command: - find: *collection0Name - batchSize: 1 - comment: 'manually running a find' - saveResultAsEntity: &manualFindCursor manualFindCursor - - name: iterateUntilDocumentOrError - object: *manualFindCursor - expectResult: - _id: 0 - x: 20 - - name: iterateUntilDocumentOrError - object: *manualFindCursor - expectResult: - _id: 1 - x: 21 - - name: iterateUntilDocumentOrError - object: *manualFindCursor - expectResult: - _id: 2 - x: 22 - - name: iterateUntilDocumentOrError - object: *manualFindCursor - expectResult: null - - name: iterateUntilDocumentOrError - object: *manualFindCursor - expectError: - isClientError: true - expectEvents: - - client: *client0 - events: - - commandStartedEvent: - command: - find: *collection0Name - $db: *database0Name - comment: { $$exists: false } - lsid: { id: { $$type: 'binData' } } - batchSize: 1 - $clusterTime: { $$exists: false } - commandName: find - - commandStartedEvent: - command: - getMore: { $$type: 'long' } - collection: *collection0Name - comment: 'manually running a find' - $db: *database0Name - lsid: { id: { $$type: 'binData' } } - $clusterTime: { $$exists: false } - commandName: getMore From 6fcae13f0362b5e63080439cc8beef774d89e539 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 14 Apr 2023 10:13:39 -0400 Subject: [PATCH 03/28] test: update runOnReqs --- test/spec/run-command/run-command.json | 12 +++++++++++- test/spec/run-command/run-command.yml | 4 +++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/test/spec/run-command/run-command.json b/test/spec/run-command/run-command.json index eca7cb8a880..dfe45407bf0 100644 --- a/test/spec/run-command/run-command.json +++ b/test/spec/run-command/run-command.json @@ -1,7 +1,7 @@ { "description": "runCommand spec requirements", "schemaVersion": "1.0", - "runOnRequirements": {}, + "runOnRequirements": [], "createEntities": [ { "client": { @@ -306,6 +306,16 @@ }, { "description": "executes a insert via runCommand inside a transaction", + "runOnRequirements": [ + { + "topologies": [ + "replicaset", + "sharded-replicaset", + "load-balanced", + "sharded" + ] + } + ], "operations": [ { "name": "withTransaction", diff --git a/test/spec/run-command/run-command.yml b/test/spec/run-command/run-command.yml index 10e40874d54..8cc431b050e 100644 --- a/test/spec/run-command/run-command.yml +++ b/test/spec/run-command/run-command.yml @@ -2,7 +2,7 @@ description: runCommand spec requirements schemaVersion: "1.0" -runOnRequirements: {} +runOnRequirements: [] createEntities: - client: @@ -172,6 +172,8 @@ tests: isError: true - description: executes a insert via runCommand inside a transaction + runOnRequirements: + - topologies: [ replicaset, sharded-replicaset, load-balanced, sharded ] operations: - name: withTransaction object: session From 6417b5b09b8358e5d6e918fb51cba55dc19a3de5 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 14 Apr 2023 10:26:47 -0400 Subject: [PATCH 04/28] fix: db options --- test/spec/run-command/run-command.json | 15 +++++++++------ test/spec/run-command/run-command.yml | 14 +++++++------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/test/spec/run-command/run-command.json b/test/spec/run-command/run-command.json index dfe45407bf0..5b142a5a170 100644 --- a/test/spec/run-command/run-command.json +++ b/test/spec/run-command/run-command.json @@ -1,7 +1,6 @@ { "description": "runCommand spec requirements", - "schemaVersion": "1.0", - "runOnRequirements": [], + "schemaVersion": "1.5", "createEntities": [ { "client": { @@ -30,8 +29,10 @@ "id": "dbWithRC", "client": "client", "databaseName": "dbWithRC", - "readConcern": { - "level": "local" + "databaseOptions": { + "readConcern": { + "level": "local" + } } } }, @@ -40,8 +41,10 @@ "id": "dbWithWC", "client": "client", "databaseName": "dbWithWC", - "writeConcern": { - "w": 0 + "databaseOptions": { + "writeConcern": { + "w": 0 + } } } }, diff --git a/test/spec/run-command/run-command.yml b/test/spec/run-command/run-command.yml index 8cc431b050e..1ab649c73f6 100644 --- a/test/spec/run-command/run-command.yml +++ b/test/spec/run-command/run-command.yml @@ -1,8 +1,6 @@ description: runCommand spec requirements -schemaVersion: "1.0" - -runOnRequirements: [] +schemaVersion: "1.5" createEntities: - client: @@ -20,14 +18,16 @@ createEntities: id: dbWithRC client: client databaseName: dbWithRC - readConcern: - level: 'local' + databaseOptions: + readConcern: + level: 'local' - database: id: dbWithWC client: client databaseName: dbWithWC - writeConcern: - w: 0 + databaseOptions: + writeConcern: + w: 0 - session: id: session client: client From 0b6c680bd08936b4f425952f6741ae4a2d6afd8a Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 14 Apr 2023 16:27:47 -0400 Subject: [PATCH 05/28] minServerVersion --- test/spec/run-command/run-command.json | 3 ++- test/spec/run-command/run-command.yml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/spec/run-command/run-command.json b/test/spec/run-command/run-command.json index 5b142a5a170..e24714e830c 100644 --- a/test/spec/run-command/run-command.json +++ b/test/spec/run-command/run-command.json @@ -316,7 +316,8 @@ "sharded-replicaset", "load-balanced", "sharded" - ] + ], + "minServerVersion": "4.4" } ], "operations": [ diff --git a/test/spec/run-command/run-command.yml b/test/spec/run-command/run-command.yml index 1ab649c73f6..57823b79f16 100644 --- a/test/spec/run-command/run-command.yml +++ b/test/spec/run-command/run-command.yml @@ -174,6 +174,7 @@ tests: - description: executes a insert via runCommand inside a transaction runOnRequirements: - topologies: [ replicaset, sharded-replicaset, load-balanced, sharded ] + minServerVersion: '4.4' operations: - name: withTransaction object: session From 80c8fdf374eb6cd0860ad62dacd01212bdf0479a Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 17 Apr 2023 15:53:25 -0400 Subject: [PATCH 06/28] fix: test titles --- test/spec/run-command/run-command.json | 16 ++++++++-------- test/spec/run-command/run-command.yml | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/test/spec/run-command/run-command.json b/test/spec/run-command/run-command.json index e24714e830c..47ba8d61694 100644 --- a/test/spec/run-command/run-command.json +++ b/test/spec/run-command/run-command.json @@ -87,7 +87,7 @@ ], "tests": [ { - "description": "executes a ping with $db attached", + "description": "always attaches $db to given command", "operations": [ { "name": "runCommand", @@ -121,7 +121,7 @@ ] }, { - "description": "executes a ping with lsid attached", + "description": "attaches the provided session lsid to ping command", "operations": [ { "name": "runCommand", @@ -159,7 +159,7 @@ ] }, { - "description": "executes a ping with $readPreference attached", + "description": "attaches the provided $readPreference to given command", "operations": [ { "name": "runCommand", @@ -197,7 +197,7 @@ ] }, { - "description": "executes a ping without attaching readConcern", + "description": "does not inherit readConcern specified at the db level", "operations": [ { "name": "runCommand", @@ -234,7 +234,7 @@ ] }, { - "description": "executes a ping without attaching writeConcern", + "description": "does not inherit writeConcern specified at the db level", "operations": [ { "name": "runCommand", @@ -271,7 +271,7 @@ ] }, { - "description": "executes a ping and does not retry retryable error", + "description": "does not retry retryable errors on given command", "operations": [ { "name": "failPoint", @@ -308,7 +308,7 @@ ] }, { - "description": "executes a insert via runCommand inside a transaction", + "description": "attaches transaction fields to given command", "runOnRequirements": [ { "topologies": [ @@ -410,7 +410,7 @@ ] }, { - "description": "executes a ping with stableApi fields attached", + "description": "attaches apiVersion fields to given command when stableApi is configured on the client", "operations": [ { "name": "runCommand", diff --git a/test/spec/run-command/run-command.yml b/test/spec/run-command/run-command.yml index 57823b79f16..5d16b2f882b 100644 --- a/test/spec/run-command/run-command.yml +++ b/test/spec/run-command/run-command.yml @@ -49,7 +49,7 @@ initialData: documents: [ { _id: 0 } ] tests: - - description: executes a ping with $db attached + - description: always attaches $db to given command operations: - name: runCommand object: plainDb @@ -68,7 +68,7 @@ tests: $db: plainDb commandName: ping - - description: executes a ping with lsid attached + - description: attaches the provided session lsid to ping command operations: - name: runCommand object: plainDb @@ -89,7 +89,7 @@ tests: $db: plainDb commandName: ping - - description: executes a ping with $readPreference attached + - description: attaches the provided $readPreference to given command operations: - name: runCommand object: plainDb @@ -110,7 +110,7 @@ tests: $db: plainDb commandName: ping - - description: executes a ping without attaching readConcern + - description: does not inherit readConcern specified at the db level operations: - name: runCommand object: dbWithRC @@ -130,7 +130,7 @@ tests: $db: dbWithRC commandName: ping - - description: executes a ping without attaching writeConcern + - description: does not inherit writeConcern specified at the db level operations: - name: runCommand object: dbWithWC @@ -150,7 +150,7 @@ tests: $db: dbWithWC commandName: ping - - description: executes a ping and does not retry retryable error + - description: does not retry retryable errors on given command operations: - name: failPoint object: testRunner @@ -171,7 +171,7 @@ tests: expectError: isError: true - - description: executes a insert via runCommand inside a transaction + - description: attaches transaction fields to given command runOnRequirements: - topologies: [ replicaset, sharded-replicaset, load-balanced, sharded ] minServerVersion: '4.4' @@ -219,7 +219,7 @@ tests: commandName: commitTransaction databaseName: admin - - description: executes a ping with stableApi fields attached + - description: attaches apiVersion fields to given command when stableApi is configured on the client operations: - name: runCommand object: stableApiDb From 2e26e93e4422aa5bbf9411af73448c4a2d039537 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 20 Apr 2023 14:18:33 -0400 Subject: [PATCH 07/28] wip --- .../run-command/run_command.spec.test.ts | 2 +- .../{run-command.json => runCommand.json} | 99 +++++---- .../{run-command.yml => runCommand.yml} | 196 +++++++++--------- 3 files changed, 149 insertions(+), 148 deletions(-) rename test/spec/run-command/{run-command.json => runCommand.json} (85%) rename test/spec/run-command/{run-command.yml => runCommand.yml} (53%) diff --git a/test/integration/run-command/run_command.spec.test.ts b/test/integration/run-command/run_command.spec.test.ts index c2ca5e91b5f..fd459859c55 100644 --- a/test/integration/run-command/run_command.spec.test.ts +++ b/test/integration/run-command/run_command.spec.test.ts @@ -1,6 +1,6 @@ import { loadSpecTests } from '../../spec'; import { runUnifiedSuite } from '../../tools/unified-spec-runner/runner'; -describe('RunCommand spec', () => { +describe.only('RunCommand spec', () => { runUnifiedSuite(loadSpecTests('run-command')); }); diff --git a/test/spec/run-command/run-command.json b/test/spec/run-command/runCommand.json similarity index 85% rename from test/spec/run-command/run-command.json rename to test/spec/run-command/runCommand.json index 47ba8d61694..5d5b62e0a00 100644 --- a/test/spec/run-command/run-command.json +++ b/test/spec/run-command/runCommand.json @@ -1,10 +1,11 @@ { - "description": "runCommand spec requirements", - "schemaVersion": "1.5", + "description": "runCommand", + "schemaVersion": "1.3", "createEntities": [ { "client": { "id": "client", + "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" ] @@ -12,15 +13,15 @@ }, { "database": { - "id": "plainDb", + "id": "db", "client": "client", - "databaseName": "plainDb" + "databaseName": "db" } }, { "collection": { "id": "collection", - "database": "plainDb", + "database": "db", "collectionName": "collection" } }, @@ -56,7 +57,7 @@ }, { "client": { - "id": "stableApiClient", + "id": "clientWithStableApi", "observeEvents": [ "commandStartedEvent" ], @@ -68,21 +69,17 @@ }, { "database": { - "id": "stableApiDb", - "client": "stableApiClient", - "databaseName": "stableApiDb" + "id": "dbWithStableApi", + "client": "clientWithStableApi", + "databaseName": "dbWithStableApi" } } ], "initialData": [ { "collectionName": "collection", - "databaseName": "plainDb", - "documents": [ - { - "_id": 0 - } - ] + "databaseName": "db", + "documents": [] } ], "tests": [ @@ -91,7 +88,7 @@ "operations": [ { "name": "runCommand", - "object": "plainDb", + "object": "db", "arguments": { "commandName": "ping", "command": { @@ -111,7 +108,7 @@ "commandStartedEvent": { "command": { "ping": 1, - "$db": "plainDb" + "$db": "db" }, "commandName": "ping" } @@ -121,11 +118,11 @@ ] }, { - "description": "attaches the provided session lsid to ping command", + "description": "attaches the provided session lsid to given command", "operations": [ { "name": "runCommand", - "object": "plainDb", + "object": "db", "arguments": { "commandName": "ping", "command": { @@ -149,7 +146,7 @@ "lsid": { "$$sessionLsid": "session" }, - "$db": "plainDb" + "$db": "db" }, "commandName": "ping" } @@ -163,13 +160,15 @@ "operations": [ { "name": "runCommand", - "object": "plainDb", + "object": "db", "arguments": { "commandName": "ping", "command": { "ping": 1 }, - "readPreference": "nearest" + "readPreference": { + "mode": "nearest" + } }, "expectResult": { "ok": 1 @@ -187,7 +186,7 @@ "$readPreference": { "mode": "nearest" }, - "$db": "plainDb" + "$db": "db" }, "commandName": "ping" } @@ -203,13 +202,11 @@ "name": "runCommand", "object": "dbWithRC", "arguments": { - "commandName": "ping", + "commandName": "aggregate", "command": { - "ping": 1 + "aggregate": 1, + "pipeline": [] } - }, - "expectResult": { - "ok": 1 } } ], @@ -226,7 +223,7 @@ }, "$db": "dbWithRC" }, - "commandName": "ping" + "commandName": "aggregate" } } ] @@ -240,9 +237,15 @@ "name": "runCommand", "object": "dbWithWC", "arguments": { - "commandName": "ping", + "commandName": "insert", "command": { - "ping": 1 + "insert": "collection", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true } }, "expectResult": { @@ -257,7 +260,7 @@ { "commandStartedEvent": { "command": { - "ping": 1, + "insert": "collection", "writeConcern": { "$$exists": false }, @@ -294,7 +297,7 @@ }, { "name": "runCommand", - "object": "plainDb", + "object": "db", "arguments": { "commandName": "ping", "command": { @@ -302,7 +305,7 @@ } }, "expectError": { - "isError": true + "isClientError": true } } ] @@ -311,13 +314,17 @@ "description": "attaches transaction fields to given command", "runOnRequirements": [ { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.2", "topologies": [ - "replicaset", "sharded-replicaset", - "load-balanced", - "sharded" - ], - "minServerVersion": "4.4" + "load-balanced" + ] } ], "operations": [ @@ -328,7 +335,7 @@ "callback": [ { "name": "runCommand", - "object": "plainDb", + "object": "db", "arguments": { "session": "session", "commandName": "insert", @@ -336,7 +343,7 @@ "insert": "collection", "documents": [ { - "_id": 1 + "_id": 2 } ], "ordered": true @@ -364,7 +371,7 @@ "insert": "collection", "documents": [ { - "_id": 1 + "_id": 2 } ], "ordered": true, @@ -382,7 +389,7 @@ } }, "commandName": "insert", - "databaseName": "plainDb" + "databaseName": "db" } }, { @@ -414,7 +421,7 @@ "operations": [ { "name": "runCommand", - "object": "stableApiDb", + "object": "dbWithStableApi", "arguments": { "commandName": "ping", "command": { @@ -428,13 +435,13 @@ ], "expectEvents": [ { - "client": "stableApiClient", + "client": "clientWithStableApi", "events": [ { "commandStartedEvent": { "command": { "ping": 1, - "$db": "stableApiDb", + "$db": "dbWithStableApi", "apiVersion": "1", "apiStrict": true, "apiDeprecationErrors": { diff --git a/test/spec/run-command/run-command.yml b/test/spec/run-command/runCommand.yml similarity index 53% rename from test/spec/run-command/run-command.yml rename to test/spec/run-command/runCommand.yml index 5d16b2f882b..4b199dac755 100644 --- a/test/spec/run-command/run-command.yml +++ b/test/spec/run-command/runCommand.yml @@ -1,153 +1,147 @@ -description: runCommand spec requirements +description: runCommand -schemaVersion: "1.5" +schemaVersion: "1.3" createEntities: - client: - id: client + id: &client client + useMultipleMongoses: false observeEvents: [commandStartedEvent] - database: - id: plainDb - client: client - databaseName: plainDb + id: &db db + client: *client + databaseName: *db - collection: - id: collection - database: plainDb - collectionName: collection + id: &collection collection + database: *db + collectionName: *collection - database: - id: dbWithRC - client: client - databaseName: dbWithRC + id: &dbWithRC dbWithRC + client: *client + databaseName: *dbWithRC databaseOptions: - readConcern: - level: 'local' + readConcern: { level: 'local' } - database: - id: dbWithWC - client: client - databaseName: dbWithWC + id: &dbWithWC dbWithWC + client: *client + databaseName: *dbWithWC databaseOptions: - writeConcern: - w: 0 + writeConcern: { w: 0 } - session: - id: session - client: client + id: &session session + client: *client # Stable API test - client: - id: stableApiClient + id: &clientWithStableApi clientWithStableApi observeEvents: [commandStartedEvent] serverApi: version: "1" strict: true - database: - id: stableApiDb - client: stableApiClient - databaseName: stableApiDb + id: &dbWithStableApi dbWithStableApi + client: *clientWithStableApi + databaseName: *dbWithStableApi initialData: -- collectionName: collection - databaseName: plainDb - documents: [ { _id: 0 } ] +- collectionName: *collection + databaseName: *db + documents: [] tests: - description: always attaches $db to given command operations: - name: runCommand - object: plainDb + object: *db arguments: commandName: ping - command: - ping: 1 - expectResult: - ok: 1 + command: { ping: 1 } + expectResult: { ok: 1 } expectEvents: - - client: client + - client: *client events: - - commandStartedEvent: + - commandStartedEvent: command: ping: 1 - $db: plainDb + $db: *db commandName: ping - - description: attaches the provided session lsid to ping command + - description: attaches the provided session lsid to given command operations: - name: runCommand - object: plainDb + object: *db arguments: commandName: ping - command: - ping: 1 - session: session - expectResult: - ok: 1 + command: { ping: 1 } + session: *session + expectResult: { ok: 1 } expectEvents: - - client: client + - client: *client events: - - commandStartedEvent: + - commandStartedEvent: command: ping: 1 - lsid: { $$sessionLsid: session } - $db: plainDb + lsid: { $$sessionLsid: *session } + $db: *db commandName: ping - description: attaches the provided $readPreference to given command operations: - name: runCommand - object: plainDb + object: *db arguments: commandName: ping - command: - ping: 1 - readPreference: 'nearest' - expectResult: - ok: 1 + command: { ping: 1 } + readPreference: &readPreference { mode: 'nearest' } + expectResult: { ok: 1 } expectEvents: - - client: client + - client: *client events: - - commandStartedEvent: + - commandStartedEvent: command: ping: 1 - $readPreference: { mode: 'nearest' } - $db: plainDb + $readPreference: *readPreference + $db: *db commandName: ping - description: does not inherit readConcern specified at the db level operations: - name: runCommand - object: dbWithRC + object: *dbWithRC + # Test with a command that supports a readConcern option. + # expectResult is intentionally omitted because some drivers + # may automatically convert command responses into cursors. arguments: - commandName: ping - command: - ping: 1 - expectResult: - ok: 1 + commandName: aggregate + command: { aggregate: 1, pipeline: [] } expectEvents: - - client: client + - client: *client events: - - commandStartedEvent: + - commandStartedEvent: command: ping: 1 readConcern: { $$exists: false } - $db: dbWithRC - commandName: ping + $db: *dbWithRC + commandName: aggregate - - description: does not inherit writeConcern specified at the db level + - description: does not inherit writeConcern specified at the db level operations: - name: runCommand - object: dbWithWC + object: *dbWithWC arguments: - commandName: ping + commandName: insert command: - ping: 1 - expectResult: - ok: 1 + insert: *collection + documents: [ { _id: 1 } ] + ordered: true + expectResult: { ok: 1 } expectEvents: - - client: client + - client: *client events: - - commandStartedEvent: + - commandStartedEvent: command: - ping: 1 + insert: *collection writeConcern: { $$exists: false } - $db: dbWithWC + $db: *dbWithWC commandName: ping - description: does not retry retryable errors on given command @@ -155,7 +149,7 @@ tests: - name: failPoint object: testRunner arguments: - client: client + client: *client failPoint: configureFailPoint: failCommand mode: { times: 1 } @@ -163,42 +157,43 @@ tests: failCommands: [ping] closeConnection: true - name: runCommand - object: plainDb + object: *db arguments: commandName: ping - command: - ping: 1 + command: { ping: 1 } expectError: - isError: true + isClientError: true - description: attaches transaction fields to given command runOnRequirements: - - topologies: [ replicaset, sharded-replicaset, load-balanced, sharded ] - minServerVersion: '4.4' + - minServerVersion: "4.0" + topologies: [ replicaset ] + - minServerVersion: "4.2" + topologies: [ sharded-replicaset, load-balanced ] operations: - name: withTransaction - object: session + object: *session arguments: callback: - name: runCommand - object: plainDb + object: *db arguments: - session: session + session: *session commandName: insert command: - insert: collection - documents: [ { _id: 1 } ] + insert: *collection + documents: [ { _id: 2 } ] ordered: true expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } } expectEvents: - - client: client + - client: *client events: - commandStartedEvent: command: - insert: collection - documents: [ { _id: 1 } ] + insert: *collection + documents: [ { _id: 2 } ] ordered: true - lsid: { $$sessionLsid: session } + lsid: { $$sessionLsid: *session } txnNumber: 1 startTransaction: true autocommit: false @@ -206,11 +201,11 @@ tests: readConcern: { $$exists: false } writeConcern: { $$exists: false } commandName: insert - databaseName: plainDb + databaseName: *db - commandStartedEvent: command: commitTransaction: 1 - lsid: { $$sessionLsid: session } + lsid: { $$sessionLsid: *session } txnNumber: 1 autocommit: false # omitted fields @@ -222,20 +217,19 @@ tests: - description: attaches apiVersion fields to given command when stableApi is configured on the client operations: - name: runCommand - object: stableApiDb + object: *dbWithStableApi arguments: commandName: ping command: ping: 1 - expectResult: - ok: 1 + expectResult: { ok: 1 } expectEvents: - - client: stableApiClient + - client: *clientWithStableApi events: - - commandStartedEvent: + - commandStartedEvent: command: ping: 1 - $db: stableApiDb + $db: *dbWithStableApi apiVersion: "1" apiStrict: true apiDeprecationErrors: { $$unsetOrMatches: false } From 3ec381d488fc1bc44f31142d5ea0f3976c5e5179 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 20 Apr 2023 14:31:58 -0400 Subject: [PATCH 08/28] fix: sync tests --- test/spec/run-command/runCommand.json | 9 +++++---- test/spec/run-command/runCommand.yml | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/test/spec/run-command/runCommand.json b/test/spec/run-command/runCommand.json index 5d5b62e0a00..45f1a40512a 100644 --- a/test/spec/run-command/runCommand.json +++ b/test/spec/run-command/runCommand.json @@ -204,8 +204,9 @@ "arguments": { "commandName": "aggregate", "command": { - "aggregate": 1, - "pipeline": [] + "aggregate": "collection", + "pipeline": [], + "cursor": {} } } } @@ -217,7 +218,7 @@ { "commandStartedEvent": { "command": { - "ping": 1, + "aggregate": "collection", "readConcern": { "$$exists": false }, @@ -266,7 +267,7 @@ }, "$db": "dbWithWC" }, - "commandName": "ping" + "commandName": "insert" } } ] diff --git a/test/spec/run-command/runCommand.yml b/test/spec/run-command/runCommand.yml index 4b199dac755..38a63ab0bfb 100644 --- a/test/spec/run-command/runCommand.yml +++ b/test/spec/run-command/runCommand.yml @@ -112,13 +112,13 @@ tests: # may automatically convert command responses into cursors. arguments: commandName: aggregate - command: { aggregate: 1, pipeline: [] } + command: { aggregate: *collection, pipeline: [], cursor: {} } expectEvents: - client: *client events: - commandStartedEvent: command: - ping: 1 + aggregate: *collection readConcern: { $$exists: false } $db: *dbWithRC commandName: aggregate @@ -142,7 +142,7 @@ tests: insert: *collection writeConcern: { $$exists: false } $db: *dbWithWC - commandName: ping + commandName: insert - description: does not retry retryable errors on given command operations: From 0ddc18777ee04f92d356444bcc0bc96278b70a8c Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 09:56:03 -0400 Subject: [PATCH 09/28] fix: withTransaction should throw --- test/tools/unified-spec-runner/match.ts | 2 +- test/tools/unified-spec-runner/operations.ts | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/test/tools/unified-spec-runner/match.ts b/test/tools/unified-spec-runner/match.ts index 4d52eaa9dff..13d031187a2 100644 --- a/test/tools/unified-spec-runner/match.ts +++ b/test/tools/unified-spec-runner/match.ts @@ -220,7 +220,7 @@ export function resultCheck( } if (typeof actual !== 'object') { - expect.fail('Expected actual value to be an object' + path.join('.')); + expect.fail('Expected actual value to be an object'); } const expectedEntries = Object.entries(expected); diff --git a/test/tools/unified-spec-runner/operations.ts b/test/tools/unified-spec-runner/operations.ts index 25e20360fe6..e8cc4f0ed73 100644 --- a/test/tools/unified-spec-runner/operations.ts +++ b/test/tools/unified-spec-runner/operations.ts @@ -594,18 +594,14 @@ operations.set('withTransaction', async ({ entities, operation, client, testConf let errorFromOperations = null; const result = await session.withTransaction(async () => { - try { - await (async () => { - for (const callbackOperation of operation.arguments!.callback) { - await executeOperationAndCheck(callbackOperation, entities, client, testConfig); - } - })(); - } catch (error) { - errorFromOperations = error; - } + errorFromOperations = await (async () => { + for (const callbackOperation of operation.arguments!.callback) { + await executeOperationAndCheck(callbackOperation, entities, client, testConfig); + } + })().catch(error => error); }, options); - if (result == null) { + if (result == null || errorFromOperations) { throw errorFromOperations ?? Error('transaction not committed'); } }); From b46fc83f64b4a6b656ed15017ed49546b7ef54ab Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 10:03:02 -0400 Subject: [PATCH 10/28] refactor: improve runCommand operation runner --- test/tools/unified-spec-runner/operations.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/tools/unified-spec-runner/operations.ts b/test/tools/unified-spec-runner/operations.ts index e8cc4f0ed73..478b9dc6e17 100644 --- a/test/tools/unified-spec-runner/operations.ts +++ b/test/tools/unified-spec-runner/operations.ts @@ -631,8 +631,18 @@ operations.set('estimatedDocumentCount', async ({ entities, operation }) => { operations.set('runCommand', async ({ entities, operation }: OperationFunctionParams) => { const db = entities.getEntity('db', operation.object); - const { command, ...opts } = operation.arguments!; - return db.command(command, opts); + + if (operation.arguments?.command == null) throw new Error('runCommand requires a command'); + const { command } = operation.arguments; + + if (operation.arguments.timeoutMS != null) throw new Error('timeoutMS not supported, skip'); + + const options = { + readPreference: operation.arguments.readPreference, + session: entities.getEntity('session', operation.arguments.session, false) + }; + + return db.command(command, options); }); operations.set('updateMany', async ({ entities, operation }) => { From f9d5033e10a8ad3384e33a516a211d756f872784 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 10:07:18 -0400 Subject: [PATCH 11/28] rm only --- test/integration/run-command/run_command.spec.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/run-command/run_command.spec.test.ts b/test/integration/run-command/run_command.spec.test.ts index fd459859c55..c2ca5e91b5f 100644 --- a/test/integration/run-command/run_command.spec.test.ts +++ b/test/integration/run-command/run_command.spec.test.ts @@ -1,6 +1,6 @@ import { loadSpecTests } from '../../spec'; import { runUnifiedSuite } from '../../tools/unified-spec-runner/runner'; -describe.only('RunCommand spec', () => { +describe('RunCommand spec', () => { runUnifiedSuite(loadSpecTests('run-command')); }); From 7d841d0903c2c008568c751085597273826dc733 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 13:25:48 -0400 Subject: [PATCH 12/28] feat: deprecate non spec runCommand options --- src/admin.ts | 14 +++ src/cmap/connection.ts | 24 ++--- src/cmap/wire_protocol/shared.ts | 5 +- src/db.ts | 14 +++ src/operations/run_command.ts | 41 ++++++++- .../run-command/run_command.test.ts | 92 +++++++++++++++++++ 6 files changed, 172 insertions(+), 18 deletions(-) create mode 100644 test/integration/run-command/run_command.test.ts diff --git a/src/admin.ts b/src/admin.ts index 78d7da63e4f..5e85a04591f 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -54,6 +54,20 @@ export class Admin { /** * Execute a command * + * The driver ensure the following fields are attached to a clone + * - `lsid` (session) + * - `$readPreference` + * - `$db` + * - `apiVersion` + * - `apiStrict` + * - `apiDeprecationErrors` + * + * Only when in a transaction: + * - `readConcern` + * - `writeConcern` + * + * Attaching any of the above fields to the command will have no effect as the driver will overwrite the value. + * * @param command - The command to execute * @param options - Optional settings for the command */ diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index 2d5ff5a8f02..3c3853e5ad8 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -484,22 +484,22 @@ export class Connection extends TypedEventEmitter { command( ns: MongoDBNamespace, - cmd: Document, + userCommand: Document, options: CommandOptions | undefined, callback: Callback ): void { - const readPreference = getReadPreference(cmd, options); + const readPreference = getReadPreference(options); const shouldUseOpMsg = supportsOpMsg(this); const session = options?.session; let clusterTime = this.clusterTime; - let finalCmd = Object.assign({}, cmd); + let cmd = { ...userCommand }; if (this.serverApi) { const { version, strict, deprecationErrors } = this.serverApi; - finalCmd.apiVersion = version; - if (strict != null) finalCmd.apiStrict = strict; - if (deprecationErrors != null) finalCmd.apiDeprecationErrors = deprecationErrors; + cmd.apiVersion = version; + if (strict != null) cmd.apiStrict = strict; + if (deprecationErrors != null) cmd.apiDeprecationErrors = deprecationErrors; } if (hasSessionSupport(this) && session) { @@ -511,7 +511,7 @@ export class Connection extends TypedEventEmitter { clusterTime = session.clusterTime; } - const err = applySession(session, finalCmd, options); + const err = applySession(session, cmd, options); if (err) { return callback(err); } @@ -521,12 +521,12 @@ export class Connection extends TypedEventEmitter { // if we have a known cluster time, gossip it if (clusterTime) { - finalCmd.$clusterTime = clusterTime; + cmd.$clusterTime = clusterTime; } if (isSharded(this) && !shouldUseOpMsg && readPreference && readPreference.mode !== 'primary') { - finalCmd = { - $query: finalCmd, + cmd = { + $query: cmd, $readPreference: readPreference.toJSON() }; } @@ -544,8 +544,8 @@ export class Connection extends TypedEventEmitter { const cmdNs = `${ns.db}.$cmd`; const message = shouldUseOpMsg - ? new Msg(cmdNs, finalCmd, commandOptions) - : new Query(cmdNs, finalCmd, commandOptions); + ? new Msg(cmdNs, cmd, commandOptions) + : new Query(cmdNs, cmd, commandOptions); try { write(this, message, commandOptions, callback); diff --git a/src/cmap/wire_protocol/shared.ts b/src/cmap/wire_protocol/shared.ts index bb88a47e6dd..53375d8ec57 100644 --- a/src/cmap/wire_protocol/shared.ts +++ b/src/cmap/wire_protocol/shared.ts @@ -1,4 +1,3 @@ -import type { Document } from '../../bson'; import { MongoInvalidArgumentError } from '../../error'; import type { ReadPreferenceLike } from '../../read_preference'; import { ReadPreference } from '../../read_preference'; @@ -13,9 +12,9 @@ export interface ReadPreferenceOption { readPreference?: ReadPreferenceLike; } -export function getReadPreference(cmd: Document, options?: ReadPreferenceOption): ReadPreference { +export function getReadPreference(options?: ReadPreferenceOption): ReadPreference { // Default to command version of the readPreference - let readPreference = cmd.readPreference || ReadPreference.primary; + let readPreference = options?.readPreference ?? ReadPreference.primary; // If we have an option readPreference override the command one if (options?.readPreference) { readPreference = options.readPreference; diff --git a/src/db.ts b/src/db.ts index 3d60010f3fa..2d2bdb34e60 100644 --- a/src/db.ts +++ b/src/db.ts @@ -232,6 +232,20 @@ export class Db { * @remarks * This command does not inherit options from the MongoClient. * + * The driver is responsible for attaching the following fields + * - `lsid` (session) + * - `$readPreference` + * - `$db` + * - `apiVersion` + * - `apiStrict` + * - `apiDeprecationErrors` + * + * Only when in a transaction: + * - `readConcern` + * - `writeConcern` + * + * Attaching any of the above fields to the command will have no effect as the driver will overwrite the value. + * * @param command - The command to run * @param options - Optional settings for the command */ diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index b92237a9a24..46cf9d48173 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -1,11 +1,46 @@ -import type { Document } from '../bson'; +import type { BSONSerializeOptions, Document } from '../bson'; +import type { ReadPreferenceLike } from '../read_preference'; import type { Server } from '../sdam/server'; import type { ClientSession } from '../sessions'; import { Callback, MongoDBNamespace } from '../utils'; -import { CommandOperation, CommandOperationOptions, OperationParent } from './command'; +import { CommandOperation, OperationParent } from './command'; /** @public */ -export type RunCommandOptions = CommandOperationOptions; +export type RunCommandOptions = { + ///// OperationOptions + /** Specify ClientSession for this command */ + session?: ClientSession; + /** The preferred read preference (ReadPreference.primary, ReadPreference.primary_preferred, ReadPreference.secondary, ReadPreference.secondary_preferred, ReadPreference.nearest). */ + readPreference?: ReadPreferenceLike; + + /// The following options were "accidentally" supported + /// Since the option + + /** @deprecated This is an internal option that has undefined behavior for this API */ + willRetryWrite?: any; + /** @deprecated This is an internal option that has undefined behavior for this API */ + omitReadPreference?: any; + /** @deprecated This is an internal option that has undefined behavior for this API */ + writeConcern?: any; + /** @deprecated This is an internal option that has undefined behavior for this API */ + explain?: any; + /** @deprecated This is an internal option that has undefined behavior for this API */ + readConcern?: any; + /** @deprecated This is an internal option that has undefined behavior for this API */ + collation?: any; + /** @deprecated This is an internal option that has undefined behavior for this API */ + maxTimeMS?: any; + /** @deprecated This is an internal option that has undefined behavior for this API */ + comment?: any; + /** @deprecated This is an internal option that has undefined behavior for this API */ + retryWrites?: any; + /** @deprecated This is an internal option that has undefined behavior for this API */ + dbName?: any; + /** @deprecated This is an internal option that has undefined behavior for this API */ + authdb?: any; + /** @deprecated This is an internal option that has undefined behavior for this API */ + noResponse?: any; +} & BSONSerializeOptions; /** @internal */ export class RunCommandOperation extends CommandOperation { diff --git a/test/integration/run-command/run_command.test.ts b/test/integration/run-command/run_command.test.ts new file mode 100644 index 00000000000..98e1cf797d7 --- /dev/null +++ b/test/integration/run-command/run_command.test.ts @@ -0,0 +1,92 @@ +import { expect } from 'chai'; + +import { + CommandStartedEvent, + Db, + MongoClient, + ReadConcern, + ReadPreference, + WriteConcern +} from '../../mongodb'; + +describe.only('RunCommand API', () => { + let client: MongoClient; + let db: Db; + let commandsStarted: CommandStartedEvent[]; + beforeEach(async function () { + const options = { + serverApi: { version: '1', strict: true, deprecationErrors: false }, + monitorCommands: true + }; + client = this.configuration.newClient({}, options); + db = client.db(); + commandsStarted = []; + client.on('commandStarted', started => commandsStarted.push(started)); + }); + + afterEach(async function () { + commandsStarted = []; + await client.close(); + }); + + context('does not modify user input', () => { + it('for session', async () => { + const command = { ping: 1 }; + const res = await db.command(command); + expect(res).to.have.property('ok', 1); + expect(commandsStarted).to.have.nested.property('[0].command.lsid'); + expect(command).to.not.have.property('lsid'); + }); + + it('for readPreference', async () => { + const command = { ping: 1 }; + const res = await db.command(command, { readPreference: ReadPreference.nearest }); + expect(res).to.have.property('ok', 1); + expect(commandsStarted).to.have.nested.property('[0].command.$readPreference'); + expect(command).to.not.have.property('$readPreference'); + }); + + it('for $db', async () => { + const command = { ping: 1 }; + const res = await db.command(command, { readPreference: ReadPreference.nearest }); + expect(res).to.have.property('ok', 1); + expect(commandsStarted).to.have.nested.property('[0].command.$db'); + expect(command).to.not.have.property('$db'); + }); + + it('for apiVersion, apiStrict, apiDeprecationErrors', async () => { + const command = { ping: 1 }; + const res = await db.command(command, { readPreference: ReadPreference.nearest }); + expect(res).to.have.property('ok', 1); + expect(commandsStarted).to.have.nested.property('[0].command.apiVersion'); + expect(commandsStarted).to.have.nested.property('[0].command.apiStrict'); + expect(commandsStarted).to.have.nested.property('[0].command.apiDeprecationErrors'); + expect(command).to.not.have.property('apiVersion'); + expect(command).to.not.have.property('apiStrict'); + expect(command).to.not.have.property('apiDeprecationErrors'); + }); + + it.skip('for readConcern', async () => { + const command = { find: 'test', filter: {} }; + const res = await db.command(command, { readConcern: ReadConcern.MAJORITY }); + expect(res).to.have.property('ok', 1); + expect(commandsStarted).to.not.have.nested.property('[0].command.readConcern'); + expect(command).to.not.have.property('readConcern'); + }).skipReason = + 'TODO(NODE-4936): Enable this test when readConcern support has been removed from runCommand'; + + it('for writeConcern', async () => { + const command = { insert: 'test', documents: [{ x: 1 }] }; + const res = await db.command(command, { writeConcern: new WriteConcern('majority') }); + expect(res).to.have.property('ok', 1); + expect(commandsStarted).to.not.have.nested.property('[0].command.writeConcern'); + expect(command).to.not.have.property('writeConcern'); + }); + }); + + it('does not modify user input', async () => { + const command = Object.freeze({ ping: 1 }); + const res = await db.command(command); // will throw if it tries to modify command + expect(res).to.have.property('ok', 1); + }); +}); From 80d5c1af21d7880510aa63429e4bea5b43d91500 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 14:04:57 -0400 Subject: [PATCH 13/28] docs: phrasing --- src/admin.ts | 2 +- src/db.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/admin.ts b/src/admin.ts index 5e85a04591f..a4d4dd66eae 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -54,7 +54,7 @@ export class Admin { /** * Execute a command * - * The driver ensure the following fields are attached to a clone + * The driver will ensure the following fields are attached to the command sent to the server: * - `lsid` (session) * - `$readPreference` * - `$db` diff --git a/src/db.ts b/src/db.ts index 2d2bdb34e60..551092f6d96 100644 --- a/src/db.ts +++ b/src/db.ts @@ -232,7 +232,7 @@ export class Db { * @remarks * This command does not inherit options from the MongoClient. * - * The driver is responsible for attaching the following fields + * The driver will ensure the following fields are attached to the command sent to the server: * - `lsid` (session) * - `$readPreference` * - `$db` From 1cf314550d9314ae34f40067efa831c2481b4c75 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 14:11:18 -0400 Subject: [PATCH 14/28] docs: sources of values --- src/admin.ts | 8 +++++--- src/db.ts | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/admin.ts b/src/admin.ts index a4d4dd66eae..c07c2878f6f 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -55,9 +55,11 @@ export class Admin { * Execute a command * * The driver will ensure the following fields are attached to the command sent to the server: - * - `lsid` (session) - * - `$readPreference` - * - `$db` + * - `lsid` - sourced from an implicit session or options.session + * - `$readPreference` - defaults to primary or can be configured by options.readPreference + * - `$db` - sourced from the name of this database + * + * If the client has a serverApi setting: * - `apiVersion` * - `apiStrict` * - `apiDeprecationErrors` diff --git a/src/db.ts b/src/db.ts index 551092f6d96..b7ff6b26d76 100644 --- a/src/db.ts +++ b/src/db.ts @@ -233,9 +233,11 @@ export class Db { * This command does not inherit options from the MongoClient. * * The driver will ensure the following fields are attached to the command sent to the server: - * - `lsid` (session) - * - `$readPreference` - * - `$db` + * - `lsid` - sourced from an implicit session or options.session + * - `$readPreference` - defaults to primary or can be configured by options.readPreference + * - `$db` - sourced from the name of this database + * + * If the client has a serverApi setting: * - `apiVersion` * - `apiStrict` * - `apiDeprecationErrors` From 306ca47037401a7c07b4fb70200e0e4f49a79efc Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 14:12:47 -0400 Subject: [PATCH 15/28] fix: move spread --- src/cmap/connection.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index 3c3853e5ad8..49c33523a49 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -484,16 +484,17 @@ export class Connection extends TypedEventEmitter { command( ns: MongoDBNamespace, - userCommand: Document, + cmd: Document, options: CommandOptions | undefined, callback: Callback ): void { + cmd = { ...cmd }; + const readPreference = getReadPreference(options); const shouldUseOpMsg = supportsOpMsg(this); const session = options?.session; let clusterTime = this.clusterTime; - let cmd = { ...userCommand }; if (this.serverApi) { const { version, strict, deprecationErrors } = this.serverApi; From 67f16b70a2c7d183751eb46dea39d71bb102e31c Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 14:17:47 -0400 Subject: [PATCH 16/28] docs: transaction src --- src/admin.ts | 6 +++--- src/db.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/admin.ts b/src/admin.ts index c07c2878f6f..a6bad5885e0 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -64,9 +64,9 @@ export class Admin { * - `apiStrict` * - `apiDeprecationErrors` * - * Only when in a transaction: - * - `readConcern` - * - `writeConcern` + * When in a transaction: + * - `readConcern` - sourced from readConcern set on the TransactionOptions + * - `writeConcern` - sourced from writeConcern set on the TransactionOptions * * Attaching any of the above fields to the command will have no effect as the driver will overwrite the value. * diff --git a/src/db.ts b/src/db.ts index b7ff6b26d76..4d3ddeba245 100644 --- a/src/db.ts +++ b/src/db.ts @@ -242,9 +242,9 @@ export class Db { * - `apiStrict` * - `apiDeprecationErrors` * - * Only when in a transaction: - * - `readConcern` - * - `writeConcern` + * When in a transaction: + * - `readConcern` - sourced from readConcern set on the TransactionOptions + * - `writeConcern` - sourced from writeConcern set on the TransactionOptions * * Attaching any of the above fields to the command will have no effect as the driver will overwrite the value. * From 6504b25f430198e8faaf3775eb414fb0626d8e7a Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 14:22:34 -0400 Subject: [PATCH 17/28] docs: comment clean up --- src/operations/run_command.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index 46cf9d48173..ccd67d0aac1 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -7,14 +7,13 @@ import { CommandOperation, OperationParent } from './command'; /** @public */ export type RunCommandOptions = { - ///// OperationOptions /** Specify ClientSession for this command */ session?: ClientSession; /** The preferred read preference (ReadPreference.primary, ReadPreference.primary_preferred, ReadPreference.secondary, ReadPreference.secondary_preferred, ReadPreference.nearest). */ readPreference?: ReadPreferenceLike; - /// The following options were "accidentally" supported - /// Since the option + // The following options were "accidentally" supported + // Since the option /** @deprecated This is an internal option that has undefined behavior for this API */ willRetryWrite?: any; From 36a16f0e84ff0ef19163160bb37b72829aed0838 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 14:25:41 -0400 Subject: [PATCH 18/28] docs: options --- src/operations/run_command.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index ccd67d0aac1..a0829c04928 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -13,7 +13,7 @@ export type RunCommandOptions = { readPreference?: ReadPreferenceLike; // The following options were "accidentally" supported - // Since the option + // Since the options are generally supported through inheritance /** @deprecated This is an internal option that has undefined behavior for this API */ willRetryWrite?: any; From 9162f09e547f7b8f571da33e9f83c69cb8a0759a Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 14:29:05 -0400 Subject: [PATCH 19/28] docs: RP --- src/operations/run_command.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index a0829c04928..74f4be7ffd0 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -9,7 +9,7 @@ import { CommandOperation, OperationParent } from './command'; export type RunCommandOptions = { /** Specify ClientSession for this command */ session?: ClientSession; - /** The preferred read preference (ReadPreference.primary, ReadPreference.primary_preferred, ReadPreference.secondary, ReadPreference.secondary_preferred, ReadPreference.nearest). */ + /** The read preference */ readPreference?: ReadPreferenceLike; // The following options were "accidentally" supported From 6bba2240c3b8296940ba71e94d5c218a83582755 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 14:31:32 -0400 Subject: [PATCH 20/28] test: err msg --- test/tools/unified-spec-runner/operations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tools/unified-spec-runner/operations.ts b/test/tools/unified-spec-runner/operations.ts index 478b9dc6e17..e59de121439 100644 --- a/test/tools/unified-spec-runner/operations.ts +++ b/test/tools/unified-spec-runner/operations.ts @@ -635,7 +635,7 @@ operations.set('runCommand', async ({ entities, operation }: OperationFunctionPa if (operation.arguments?.command == null) throw new Error('runCommand requires a command'); const { command } = operation.arguments; - if (operation.arguments.timeoutMS != null) throw new Error('timeoutMS not supported, skip'); + if (operation.arguments.timeoutMS != null) throw new Error('timeoutMS not supported'); const options = { readPreference: operation.arguments.readPreference, From 6ce1100137ccac0cf504e6ceb1cdd890098a7a86 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 15:54:16 -0400 Subject: [PATCH 21/28] fix: tests --- src/operations/run_command.ts | 3 + .../run-command/run_command.test.ts | 76 +++++-------------- 2 files changed, 22 insertions(+), 57 deletions(-) diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index 74f4be7ffd0..f386ae6b9d2 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -39,6 +39,9 @@ export type RunCommandOptions = { authdb?: any; /** @deprecated This is an internal option that has undefined behavior for this API */ noResponse?: any; + + /** @internal Used for transaction commands */ + bypassPinningCheck?: boolean; } & BSONSerializeOptions; /** @internal */ diff --git a/test/integration/run-command/run_command.test.ts b/test/integration/run-command/run_command.test.ts index 98e1cf797d7..c368be9cf26 100644 --- a/test/integration/run-command/run_command.test.ts +++ b/test/integration/run-command/run_command.test.ts @@ -9,7 +9,7 @@ import { WriteConcern } from '../../mongodb'; -describe.only('RunCommand API', () => { +describe('RunCommand API', () => { let client: MongoClient; let db: Db; let commandsStarted: CommandStartedEvent[]; @@ -29,64 +29,26 @@ describe.only('RunCommand API', () => { await client.close(); }); - context('does not modify user input', () => { - it('for session', async () => { - const command = { ping: 1 }; - const res = await db.command(command); - expect(res).to.have.property('ok', 1); - expect(commandsStarted).to.have.nested.property('[0].command.lsid'); - expect(command).to.not.have.property('lsid'); - }); - - it('for readPreference', async () => { - const command = { ping: 1 }; - const res = await db.command(command, { readPreference: ReadPreference.nearest }); - expect(res).to.have.property('ok', 1); - expect(commandsStarted).to.have.nested.property('[0].command.$readPreference'); - expect(command).to.not.have.property('$readPreference'); - }); - - it('for $db', async () => { - const command = { ping: 1 }; - const res = await db.command(command, { readPreference: ReadPreference.nearest }); - expect(res).to.have.property('ok', 1); - expect(commandsStarted).to.have.nested.property('[0].command.$db'); - expect(command).to.not.have.property('$db'); - }); - - it('for apiVersion, apiStrict, apiDeprecationErrors', async () => { - const command = { ping: 1 }; - const res = await db.command(command, { readPreference: ReadPreference.nearest }); - expect(res).to.have.property('ok', 1); - expect(commandsStarted).to.have.nested.property('[0].command.apiVersion'); - expect(commandsStarted).to.have.nested.property('[0].command.apiStrict'); - expect(commandsStarted).to.have.nested.property('[0].command.apiDeprecationErrors'); - expect(command).to.not.have.property('apiVersion'); - expect(command).to.not.have.property('apiStrict'); - expect(command).to.not.have.property('apiDeprecationErrors'); - }); - - it.skip('for readConcern', async () => { - const command = { find: 'test', filter: {} }; - const res = await db.command(command, { readConcern: ReadConcern.MAJORITY }); - expect(res).to.have.property('ok', 1); - expect(commandsStarted).to.not.have.nested.property('[0].command.readConcern'); - expect(command).to.not.have.property('readConcern'); - }).skipReason = - 'TODO(NODE-4936): Enable this test when readConcern support has been removed from runCommand'; + it('does not modify user input', async () => { + const command = Object.freeze({ ping: 1 }); + // will throw if it tries to modify command + await db.command(command, { readPreference: ReadPreference.nearest }); + }); - it('for writeConcern', async () => { - const command = { insert: 'test', documents: [{ x: 1 }] }; - const res = await db.command(command, { writeConcern: new WriteConcern('majority') }); - expect(res).to.have.property('ok', 1); - expect(commandsStarted).to.not.have.nested.property('[0].command.writeConcern'); - expect(command).to.not.have.property('writeConcern'); - }); + it('does not support writeConcern in options', async () => { + const command = { insert: 'test', documents: [{ x: 1 }] }; + await db.command(command, { writeConcern: new WriteConcern('majority') }); + expect(commandsStarted).to.not.have.nested.property('[0].command.writeConcern'); + expect(command).to.not.have.property('writeConcern'); }); - it('does not modify user input', async () => { - const command = Object.freeze({ ping: 1 }); - const res = await db.command(command); // will throw if it tries to modify command + // TODO(NODE-4936): We do support readConcern in options, the spec forbids this + it.skip('does not support readConcern in options', async () => { + const command = { find: 'test', filter: {} }; + const res = await db.command(command, { readConcern: ReadConcern.MAJORITY }); expect(res).to.have.property('ok', 1); - }); + expect(commandsStarted).to.not.have.nested.property('[0].command.readConcern'); + expect(command).to.not.have.property('readConcern'); + }).skipReason = + 'TODO(NODE-4936): Enable this test when readConcern support has been removed from runCommand'; }); From 77f76bec0c1098d6eaa9afe2cf2f9b7c082aca84 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 15:59:50 -0400 Subject: [PATCH 22/28] test: add freeze --- test/integration/run-command/run_command.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/run-command/run_command.test.ts b/test/integration/run-command/run_command.test.ts index c368be9cf26..5469bca5c63 100644 --- a/test/integration/run-command/run_command.test.ts +++ b/test/integration/run-command/run_command.test.ts @@ -36,7 +36,7 @@ describe('RunCommand API', () => { }); it('does not support writeConcern in options', async () => { - const command = { insert: 'test', documents: [{ x: 1 }] }; + const command = Object.freeze({ insert: 'test', documents: [{ x: 1 }] }); await db.command(command, { writeConcern: new WriteConcern('majority') }); expect(commandsStarted).to.not.have.nested.property('[0].command.writeConcern'); expect(command).to.not.have.property('writeConcern'); @@ -44,7 +44,7 @@ describe('RunCommand API', () => { // TODO(NODE-4936): We do support readConcern in options, the spec forbids this it.skip('does not support readConcern in options', async () => { - const command = { find: 'test', filter: {} }; + const command = Object.freeze({ find: 'test', filter: {} }); const res = await db.command(command, { readConcern: ReadConcern.MAJORITY }); expect(res).to.have.property('ok', 1); expect(commandsStarted).to.not.have.nested.property('[0].command.readConcern'); From 755a8e158137d7a8920c5bd20bb35e2f98cbf11c Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 16:34:41 -0400 Subject: [PATCH 23/28] fix runCommand tests --- test/spec/run-command/runCommand.yml | 2 + test/spec/run-command/unified/runCommand.json | 480 ++++++++++++++++++ test/spec/run-command/unified/runCommand.yml | 243 +++++++++ 3 files changed, 725 insertions(+) create mode 100644 test/spec/run-command/unified/runCommand.json create mode 100644 test/spec/run-command/unified/runCommand.yml diff --git a/test/spec/run-command/runCommand.yml b/test/spec/run-command/runCommand.yml index 38a63ab0bfb..d278e9c8613 100644 --- a/test/spec/run-command/runCommand.yml +++ b/test/spec/run-command/runCommand.yml @@ -145,6 +145,8 @@ tests: commandName: insert - description: does not retry retryable errors on given command + runOnRequirements: + - minServerVersion: "4.0" operations: - name: failPoint object: testRunner diff --git a/test/spec/run-command/unified/runCommand.json b/test/spec/run-command/unified/runCommand.json new file mode 100644 index 00000000000..293ded30116 --- /dev/null +++ b/test/spec/run-command/unified/runCommand.json @@ -0,0 +1,480 @@ +{ + "description": "runCommand", + "schemaVersion": "1.3", + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "db", + "client": "client", + "databaseName": "db" + } + }, + { + "collection": { + "id": "collection", + "database": "db", + "collectionName": "collection" + } + }, + { + "database": { + "id": "dbWithRC", + "client": "client", + "databaseName": "dbWithRC", + "databaseOptions": { + "readConcern": { + "level": "local" + } + } + } + }, + { + "database": { + "id": "dbWithWC", + "client": "client", + "databaseName": "dbWithWC", + "databaseOptions": { + "writeConcern": { + "w": 0 + } + } + } + }, + { + "session": { + "id": "session", + "client": "client" + } + }, + { + "client": { + "id": "clientWithStableApi", + "observeEvents": [ + "commandStartedEvent" + ], + "serverApi": { + "version": "1", + "strict": true + } + } + }, + { + "database": { + "id": "dbWithStableApi", + "client": "clientWithStableApi", + "databaseName": "dbWithStableApi" + } + } + ], + "initialData": [ + { + "collectionName": "collection", + "databaseName": "db", + "documents": [] + } + ], + "tests": [ + { + "description": "always attaches $db to given command", + "operations": [ + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "$db": "db", + "lsid": { + "$$exists": true + }, + "$readPreference": { + "$$exists": false + }, + "apiVersion": { + "$$exists": false + }, + "apiStrict": { + "$$exists": false + }, + "apiDeprecationErrors": { + "$$exists": false + } + }, + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "attaches the provided session lsid to given command", + "operations": [ + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + }, + "session": "session" + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "lsid": { + "$$sessionLsid": "session" + }, + "$db": "db" + }, + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "attaches the provided $readPreference to given command", + "operations": [ + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + }, + "readPreference": { + "mode": "nearest" + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "$readPreference": { + "mode": "nearest" + }, + "$db": "db" + }, + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "does not inherit readConcern specified at the db level", + "operations": [ + { + "name": "runCommand", + "object": "dbWithRC", + "arguments": { + "commandName": "aggregate", + "command": { + "aggregate": "collection", + "pipeline": [], + "cursor": {} + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "collection", + "readConcern": { + "$$exists": false + }, + "$db": "dbWithRC" + }, + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "does not inherit writeConcern specified at the db level", + "operations": [ + { + "name": "runCommand", + "object": "dbWithWC", + "arguments": { + "commandName": "insert", + "command": { + "insert": "collection", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "collection", + "writeConcern": { + "$$exists": false + }, + "$db": "dbWithWC" + }, + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "does not retry retryable errors on given command", + "runOnRequirements": [ + { + "minServerVersion": "4.2" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "ping" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isClientError": true + } + } + ] + }, + { + "description": "attaches transaction fields to given command", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.2", + "topologies": [ + "sharded-replicaset", + "load-balanced" + ] + } + ], + "operations": [ + { + "name": "withTransaction", + "object": "session", + "arguments": { + "callback": [ + { + "name": "runCommand", + "object": "db", + "arguments": { + "session": "session", + "commandName": "insert", + "command": { + "insert": "collection", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "collection", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session" + }, + "txnNumber": 1, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "db" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session" + }, + "txnNumber": 1, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "readConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "attaches apiVersion fields to given command when stableApi is configured on the client", + "operations": [ + { + "name": "runCommand", + "object": "dbWithStableApi", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "clientWithStableApi", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "$db": "dbWithStableApi", + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + }, + "commandName": "ping" + } + } + ] + } + ] + } + ] +} diff --git a/test/spec/run-command/unified/runCommand.yml b/test/spec/run-command/unified/runCommand.yml new file mode 100644 index 00000000000..54fd869b17e --- /dev/null +++ b/test/spec/run-command/unified/runCommand.yml @@ -0,0 +1,243 @@ +description: runCommand + +schemaVersion: "1.3" + +createEntities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: [commandStartedEvent] + - database: + id: &db db + client: *client + databaseName: *db + - collection: + id: &collection collection + database: *db + collectionName: *collection + - database: + id: &dbWithRC dbWithRC + client: *client + databaseName: *dbWithRC + databaseOptions: + readConcern: { level: 'local' } + - database: + id: &dbWithWC dbWithWC + client: *client + databaseName: *dbWithWC + databaseOptions: + writeConcern: { w: 0 } + - session: + id: &session session + client: *client + # Stable API test + - client: + id: &clientWithStableApi clientWithStableApi + observeEvents: [commandStartedEvent] + serverApi: + version: "1" + strict: true + - database: + id: &dbWithStableApi dbWithStableApi + client: *clientWithStableApi + databaseName: *dbWithStableApi + +initialData: +- collectionName: *collection + databaseName: *db + documents: [] + +tests: + - description: always attaches $db to given command + operations: + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + $db: *db + lsid: { $$exists: true } + $readPreference: { $$exists: false } + apiVersion: { $$exists: false } + apiStrict: { $$exists: false } + apiDeprecationErrors: { $$exists: false } + commandName: ping + + - description: attaches the provided session lsid to given command + operations: + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + session: *session + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + lsid: { $$sessionLsid: *session } + $db: *db + commandName: ping + + - description: attaches the provided $readPreference to given command + operations: + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + readPreference: &readPreference { mode: 'nearest' } + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + $readPreference: *readPreference + $db: *db + commandName: ping + + - description: does not inherit readConcern specified at the db level + operations: + - name: runCommand + object: *dbWithRC + # Test with a command that supports a readConcern option. + # expectResult is intentionally omitted because some drivers + # may automatically convert command responses into cursors. + arguments: + commandName: aggregate + command: { aggregate: *collection, pipeline: [], cursor: {} } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + aggregate: *collection + readConcern: { $$exists: false } + $db: *dbWithRC + commandName: aggregate + + - description: does not inherit writeConcern specified at the db level + operations: + - name: runCommand + object: *dbWithWC + arguments: + commandName: insert + command: + insert: *collection + documents: [ { _id: 1 } ] + ordered: true + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collection + writeConcern: { $$exists: false } + $db: *dbWithWC + commandName: insert + + - description: does not retry retryable errors on given command + runOnRequirements: + - minServerVersion: "4.2" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ping] + closeConnection: true + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + expectError: + isClientError: true + + - description: attaches transaction fields to given command + runOnRequirements: + - minServerVersion: "4.0" + topologies: [ replicaset ] + - minServerVersion: "4.2" + topologies: [ sharded-replicaset, load-balanced ] + operations: + - name: withTransaction + object: *session + arguments: + callback: + - name: runCommand + object: *db + arguments: + session: *session + commandName: insert + command: + insert: *collection + documents: [ { _id: 2 } ] + ordered: true + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collection + documents: [ { _id: 2 } ] + ordered: true + lsid: { $$sessionLsid: *session } + txnNumber: 1 + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *db + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session } + txnNumber: 1 + autocommit: false + # omitted fields + writeConcern: { $$exists: false } + readConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + + - description: attaches apiVersion fields to given command when stableApi is configured on the client + operations: + - name: runCommand + object: *dbWithStableApi + arguments: + commandName: ping + command: + ping: 1 + expectResult: { ok: 1 } + expectEvents: + - client: *clientWithStableApi + events: + - commandStartedEvent: + command: + ping: 1 + $db: *dbWithStableApi + apiVersion: "1" + apiStrict: true + apiDeprecationErrors: { $$unsetOrMatches: false } + commandName: ping From cd5269a24c8f2af1055687e8301f13d2aec2715a Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 17:09:44 -0400 Subject: [PATCH 24/28] test: fix --- .../run-command/run_command.test.ts | 22 +++++++++++-------- test/tools/unified-spec-runner/operations.ts | 2 +- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/test/integration/run-command/run_command.test.ts b/test/integration/run-command/run_command.test.ts index 5469bca5c63..393c9ad1641 100644 --- a/test/integration/run-command/run_command.test.ts +++ b/test/integration/run-command/run_command.test.ts @@ -29,13 +29,13 @@ describe('RunCommand API', () => { await client.close(); }); - it('does not modify user input', async () => { + it('does not modify user input', { requires: { mongodb: '>=5.0' } }, async () => { const command = Object.freeze({ ping: 1 }); // will throw if it tries to modify command await db.command(command, { readPreference: ReadPreference.nearest }); }); - it('does not support writeConcern in options', async () => { + it('does not support writeConcern in options', { requires: { mongodb: '>=5.0' } }, async () => { const command = Object.freeze({ insert: 'test', documents: [{ x: 1 }] }); await db.command(command, { writeConcern: new WriteConcern('majority') }); expect(commandsStarted).to.not.have.nested.property('[0].command.writeConcern'); @@ -43,12 +43,16 @@ describe('RunCommand API', () => { }); // TODO(NODE-4936): We do support readConcern in options, the spec forbids this - it.skip('does not support readConcern in options', async () => { - const command = Object.freeze({ find: 'test', filter: {} }); - const res = await db.command(command, { readConcern: ReadConcern.MAJORITY }); - expect(res).to.have.property('ok', 1); - expect(commandsStarted).to.not.have.nested.property('[0].command.readConcern'); - expect(command).to.not.have.property('readConcern'); - }).skipReason = + it.skip( + 'does not support readConcern in options', + { requires: { mongodb: '>=5.0' } }, + async () => { + const command = Object.freeze({ find: 'test', filter: {} }); + const res = await db.command(command, { readConcern: ReadConcern.MAJORITY }); + expect(res).to.have.property('ok', 1); + expect(commandsStarted).to.not.have.nested.property('[0].command.readConcern'); + expect(command).to.not.have.property('readConcern'); + } + ).skipReason = 'TODO(NODE-4936): Enable this test when readConcern support has been removed from runCommand'; }); diff --git a/test/tools/unified-spec-runner/operations.ts b/test/tools/unified-spec-runner/operations.ts index e59de121439..20e12ffef3b 100644 --- a/test/tools/unified-spec-runner/operations.ts +++ b/test/tools/unified-spec-runner/operations.ts @@ -639,7 +639,7 @@ operations.set('runCommand', async ({ entities, operation }: OperationFunctionPa const options = { readPreference: operation.arguments.readPreference, - session: entities.getEntity('session', operation.arguments.session, false) + session: operation.arguments.session }; return db.command(command, options); From ef17124a024ae79136305ffa7994189a87a0ee7b Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 21 Apr 2023 17:22:28 -0400 Subject: [PATCH 25/28] fix: cp --- test/spec/run-command/runCommand.json | 27 +- test/spec/run-command/runCommand.yml | 9 +- test/spec/run-command/unified/runCommand.json | 480 ------------------ test/spec/run-command/unified/runCommand.yml | 243 --------- 4 files changed, 34 insertions(+), 725 deletions(-) delete mode 100644 test/spec/run-command/unified/runCommand.json delete mode 100644 test/spec/run-command/unified/runCommand.yml diff --git a/test/spec/run-command/runCommand.json b/test/spec/run-command/runCommand.json index 45f1a40512a..0247756e326 100644 --- a/test/spec/run-command/runCommand.json +++ b/test/spec/run-command/runCommand.json @@ -108,7 +108,22 @@ "commandStartedEvent": { "command": { "ping": 1, - "$db": "db" + "$db": "db", + "lsid": { + "$$exists": true + }, + "$readPreference": { + "$$exists": false + }, + "apiVersion": { + "$$exists": false + }, + "apiStrict": { + "$$exists": false + }, + "apiDeprecationErrors": { + "$$exists": false + } }, "commandName": "ping" } @@ -276,6 +291,11 @@ }, { "description": "does not retry retryable errors on given command", + "runOnRequirements": [ + { + "minServerVersion": "4.2" + } + ], "operations": [ { "name": "failPoint", @@ -419,6 +439,11 @@ }, { "description": "attaches apiVersion fields to given command when stableApi is configured on the client", + "runOnRequirements": [ + { + "minServerVersion": "5.0" + } + ], "operations": [ { "name": "runCommand", diff --git a/test/spec/run-command/runCommand.yml b/test/spec/run-command/runCommand.yml index d278e9c8613..82eddc791f4 100644 --- a/test/spec/run-command/runCommand.yml +++ b/test/spec/run-command/runCommand.yml @@ -63,6 +63,11 @@ tests: command: ping: 1 $db: *db + lsid: { $$exists: true } + $readPreference: { $$exists: false } + apiVersion: { $$exists: false } + apiStrict: { $$exists: false } + apiDeprecationErrors: { $$exists: false } commandName: ping - description: attaches the provided session lsid to given command @@ -146,7 +151,7 @@ tests: - description: does not retry retryable errors on given command runOnRequirements: - - minServerVersion: "4.0" + - minServerVersion: "4.2" operations: - name: failPoint object: testRunner @@ -217,6 +222,8 @@ tests: databaseName: admin - description: attaches apiVersion fields to given command when stableApi is configured on the client + runOnRequirements: + - minServerVersion: "5.0" operations: - name: runCommand object: *dbWithStableApi diff --git a/test/spec/run-command/unified/runCommand.json b/test/spec/run-command/unified/runCommand.json deleted file mode 100644 index 293ded30116..00000000000 --- a/test/spec/run-command/unified/runCommand.json +++ /dev/null @@ -1,480 +0,0 @@ -{ - "description": "runCommand", - "schemaVersion": "1.3", - "createEntities": [ - { - "client": { - "id": "client", - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" - ] - } - }, - { - "database": { - "id": "db", - "client": "client", - "databaseName": "db" - } - }, - { - "collection": { - "id": "collection", - "database": "db", - "collectionName": "collection" - } - }, - { - "database": { - "id": "dbWithRC", - "client": "client", - "databaseName": "dbWithRC", - "databaseOptions": { - "readConcern": { - "level": "local" - } - } - } - }, - { - "database": { - "id": "dbWithWC", - "client": "client", - "databaseName": "dbWithWC", - "databaseOptions": { - "writeConcern": { - "w": 0 - } - } - } - }, - { - "session": { - "id": "session", - "client": "client" - } - }, - { - "client": { - "id": "clientWithStableApi", - "observeEvents": [ - "commandStartedEvent" - ], - "serverApi": { - "version": "1", - "strict": true - } - } - }, - { - "database": { - "id": "dbWithStableApi", - "client": "clientWithStableApi", - "databaseName": "dbWithStableApi" - } - } - ], - "initialData": [ - { - "collectionName": "collection", - "databaseName": "db", - "documents": [] - } - ], - "tests": [ - { - "description": "always attaches $db to given command", - "operations": [ - { - "name": "runCommand", - "object": "db", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectResult": { - "ok": 1 - } - } - ], - "expectEvents": [ - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1, - "$db": "db", - "lsid": { - "$$exists": true - }, - "$readPreference": { - "$$exists": false - }, - "apiVersion": { - "$$exists": false - }, - "apiStrict": { - "$$exists": false - }, - "apiDeprecationErrors": { - "$$exists": false - } - }, - "commandName": "ping" - } - } - ] - } - ] - }, - { - "description": "attaches the provided session lsid to given command", - "operations": [ - { - "name": "runCommand", - "object": "db", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - }, - "session": "session" - }, - "expectResult": { - "ok": 1 - } - } - ], - "expectEvents": [ - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1, - "lsid": { - "$$sessionLsid": "session" - }, - "$db": "db" - }, - "commandName": "ping" - } - } - ] - } - ] - }, - { - "description": "attaches the provided $readPreference to given command", - "operations": [ - { - "name": "runCommand", - "object": "db", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - }, - "readPreference": { - "mode": "nearest" - } - }, - "expectResult": { - "ok": 1 - } - } - ], - "expectEvents": [ - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1, - "$readPreference": { - "mode": "nearest" - }, - "$db": "db" - }, - "commandName": "ping" - } - } - ] - } - ] - }, - { - "description": "does not inherit readConcern specified at the db level", - "operations": [ - { - "name": "runCommand", - "object": "dbWithRC", - "arguments": { - "commandName": "aggregate", - "command": { - "aggregate": "collection", - "pipeline": [], - "cursor": {} - } - } - } - ], - "expectEvents": [ - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "aggregate": "collection", - "readConcern": { - "$$exists": false - }, - "$db": "dbWithRC" - }, - "commandName": "aggregate" - } - } - ] - } - ] - }, - { - "description": "does not inherit writeConcern specified at the db level", - "operations": [ - { - "name": "runCommand", - "object": "dbWithWC", - "arguments": { - "commandName": "insert", - "command": { - "insert": "collection", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true - } - }, - "expectResult": { - "ok": 1 - } - } - ], - "expectEvents": [ - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "insert": "collection", - "writeConcern": { - "$$exists": false - }, - "$db": "dbWithWC" - }, - "commandName": "insert" - } - } - ] - } - ] - }, - { - "description": "does not retry retryable errors on given command", - "runOnRequirements": [ - { - "minServerVersion": "4.2" - } - ], - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "ping" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "db", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectError": { - "isClientError": true - } - } - ] - }, - { - "description": "attaches transaction fields to given command", - "runOnRequirements": [ - { - "minServerVersion": "4.0", - "topologies": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.2", - "topologies": [ - "sharded-replicaset", - "load-balanced" - ] - } - ], - "operations": [ - { - "name": "withTransaction", - "object": "session", - "arguments": { - "callback": [ - { - "name": "runCommand", - "object": "db", - "arguments": { - "session": "session", - "commandName": "insert", - "command": { - "insert": "collection", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 1 - } - } - } - } - ] - } - } - ], - "expectEvents": [ - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "insert": "collection", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "lsid": { - "$$sessionLsid": "session" - }, - "txnNumber": 1, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "$$exists": false - }, - "writeConcern": { - "$$exists": false - } - }, - "commandName": "insert", - "databaseName": "db" - } - }, - { - "commandStartedEvent": { - "command": { - "commitTransaction": 1, - "lsid": { - "$$sessionLsid": "session" - }, - "txnNumber": 1, - "autocommit": false, - "writeConcern": { - "$$exists": false - }, - "readConcern": { - "$$exists": false - } - }, - "commandName": "commitTransaction", - "databaseName": "admin" - } - } - ] - } - ] - }, - { - "description": "attaches apiVersion fields to given command when stableApi is configured on the client", - "operations": [ - { - "name": "runCommand", - "object": "dbWithStableApi", - "arguments": { - "commandName": "ping", - "command": { - "ping": 1 - } - }, - "expectResult": { - "ok": 1 - } - } - ], - "expectEvents": [ - { - "client": "clientWithStableApi", - "events": [ - { - "commandStartedEvent": { - "command": { - "ping": 1, - "$db": "dbWithStableApi", - "apiVersion": "1", - "apiStrict": true, - "apiDeprecationErrors": { - "$$unsetOrMatches": false - } - }, - "commandName": "ping" - } - } - ] - } - ] - } - ] -} diff --git a/test/spec/run-command/unified/runCommand.yml b/test/spec/run-command/unified/runCommand.yml deleted file mode 100644 index 54fd869b17e..00000000000 --- a/test/spec/run-command/unified/runCommand.yml +++ /dev/null @@ -1,243 +0,0 @@ -description: runCommand - -schemaVersion: "1.3" - -createEntities: - - client: - id: &client client - useMultipleMongoses: false - observeEvents: [commandStartedEvent] - - database: - id: &db db - client: *client - databaseName: *db - - collection: - id: &collection collection - database: *db - collectionName: *collection - - database: - id: &dbWithRC dbWithRC - client: *client - databaseName: *dbWithRC - databaseOptions: - readConcern: { level: 'local' } - - database: - id: &dbWithWC dbWithWC - client: *client - databaseName: *dbWithWC - databaseOptions: - writeConcern: { w: 0 } - - session: - id: &session session - client: *client - # Stable API test - - client: - id: &clientWithStableApi clientWithStableApi - observeEvents: [commandStartedEvent] - serverApi: - version: "1" - strict: true - - database: - id: &dbWithStableApi dbWithStableApi - client: *clientWithStableApi - databaseName: *dbWithStableApi - -initialData: -- collectionName: *collection - databaseName: *db - documents: [] - -tests: - - description: always attaches $db to given command - operations: - - name: runCommand - object: *db - arguments: - commandName: ping - command: { ping: 1 } - expectResult: { ok: 1 } - expectEvents: - - client: *client - events: - - commandStartedEvent: - command: - ping: 1 - $db: *db - lsid: { $$exists: true } - $readPreference: { $$exists: false } - apiVersion: { $$exists: false } - apiStrict: { $$exists: false } - apiDeprecationErrors: { $$exists: false } - commandName: ping - - - description: attaches the provided session lsid to given command - operations: - - name: runCommand - object: *db - arguments: - commandName: ping - command: { ping: 1 } - session: *session - expectResult: { ok: 1 } - expectEvents: - - client: *client - events: - - commandStartedEvent: - command: - ping: 1 - lsid: { $$sessionLsid: *session } - $db: *db - commandName: ping - - - description: attaches the provided $readPreference to given command - operations: - - name: runCommand - object: *db - arguments: - commandName: ping - command: { ping: 1 } - readPreference: &readPreference { mode: 'nearest' } - expectResult: { ok: 1 } - expectEvents: - - client: *client - events: - - commandStartedEvent: - command: - ping: 1 - $readPreference: *readPreference - $db: *db - commandName: ping - - - description: does not inherit readConcern specified at the db level - operations: - - name: runCommand - object: *dbWithRC - # Test with a command that supports a readConcern option. - # expectResult is intentionally omitted because some drivers - # may automatically convert command responses into cursors. - arguments: - commandName: aggregate - command: { aggregate: *collection, pipeline: [], cursor: {} } - expectEvents: - - client: *client - events: - - commandStartedEvent: - command: - aggregate: *collection - readConcern: { $$exists: false } - $db: *dbWithRC - commandName: aggregate - - - description: does not inherit writeConcern specified at the db level - operations: - - name: runCommand - object: *dbWithWC - arguments: - commandName: insert - command: - insert: *collection - documents: [ { _id: 1 } ] - ordered: true - expectResult: { ok: 1 } - expectEvents: - - client: *client - events: - - commandStartedEvent: - command: - insert: *collection - writeConcern: { $$exists: false } - $db: *dbWithWC - commandName: insert - - - description: does not retry retryable errors on given command - runOnRequirements: - - minServerVersion: "4.2" - operations: - - name: failPoint - object: testRunner - arguments: - client: *client - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [ping] - closeConnection: true - - name: runCommand - object: *db - arguments: - commandName: ping - command: { ping: 1 } - expectError: - isClientError: true - - - description: attaches transaction fields to given command - runOnRequirements: - - minServerVersion: "4.0" - topologies: [ replicaset ] - - minServerVersion: "4.2" - topologies: [ sharded-replicaset, load-balanced ] - operations: - - name: withTransaction - object: *session - arguments: - callback: - - name: runCommand - object: *db - arguments: - session: *session - commandName: insert - command: - insert: *collection - documents: [ { _id: 2 } ] - ordered: true - expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } } - expectEvents: - - client: *client - events: - - commandStartedEvent: - command: - insert: *collection - documents: [ { _id: 2 } ] - ordered: true - lsid: { $$sessionLsid: *session } - txnNumber: 1 - startTransaction: true - autocommit: false - # omitted fields - readConcern: { $$exists: false } - writeConcern: { $$exists: false } - commandName: insert - databaseName: *db - - commandStartedEvent: - command: - commitTransaction: 1 - lsid: { $$sessionLsid: *session } - txnNumber: 1 - autocommit: false - # omitted fields - writeConcern: { $$exists: false } - readConcern: { $$exists: false } - commandName: commitTransaction - databaseName: admin - - - description: attaches apiVersion fields to given command when stableApi is configured on the client - operations: - - name: runCommand - object: *dbWithStableApi - arguments: - commandName: ping - command: - ping: 1 - expectResult: { ok: 1 } - expectEvents: - - client: *clientWithStableApi - events: - - commandStartedEvent: - command: - ping: 1 - $db: *dbWithStableApi - apiVersion: "1" - apiStrict: true - apiDeprecationErrors: { $$unsetOrMatches: false } - commandName: ping From 41aa7ed824cb6df45d7fb73b3a6af6c070f5fd7b Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 24 Apr 2023 09:46:48 -0400 Subject: [PATCH 26/28] update tests --- test/spec/run-command/runCommand.json | 9 --------- test/spec/run-command/runCommand.yml | 3 --- 2 files changed, 12 deletions(-) diff --git a/test/spec/run-command/runCommand.json b/test/spec/run-command/runCommand.json index 0247756e326..bed65659250 100644 --- a/test/spec/run-command/runCommand.json +++ b/test/spec/run-command/runCommand.json @@ -114,15 +114,6 @@ }, "$readPreference": { "$$exists": false - }, - "apiVersion": { - "$$exists": false - }, - "apiStrict": { - "$$exists": false - }, - "apiDeprecationErrors": { - "$$exists": false } }, "commandName": "ping" diff --git a/test/spec/run-command/runCommand.yml b/test/spec/run-command/runCommand.yml index 82eddc791f4..1b32a84ddce 100644 --- a/test/spec/run-command/runCommand.yml +++ b/test/spec/run-command/runCommand.yml @@ -65,9 +65,6 @@ tests: $db: *db lsid: { $$exists: true } $readPreference: { $$exists: false } - apiVersion: { $$exists: false } - apiStrict: { $$exists: false } - apiDeprecationErrors: { $$exists: false } commandName: ping - description: attaches the provided session lsid to given command From 58a22ef68763b3fbea291c972432db261c94aab3 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 24 Apr 2023 14:05:11 -0400 Subject: [PATCH 27/28] chore: sync title update --- test/spec/run-command/runCommand.json | 2 +- test/spec/run-command/runCommand.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/run-command/runCommand.json b/test/spec/run-command/runCommand.json index bed65659250..0ae0e9d66e0 100644 --- a/test/spec/run-command/runCommand.json +++ b/test/spec/run-command/runCommand.json @@ -84,7 +84,7 @@ ], "tests": [ { - "description": "always attaches $db to given command", + "description": "always attaches $db and implicit lsid to given command and omits default readPreference", "operations": [ { "name": "runCommand", diff --git a/test/spec/run-command/runCommand.yml b/test/spec/run-command/runCommand.yml index 1b32a84ddce..3c5f2313613 100644 --- a/test/spec/run-command/runCommand.yml +++ b/test/spec/run-command/runCommand.yml @@ -48,7 +48,7 @@ initialData: documents: [] tests: - - description: always attaches $db to given command + - description: always attaches $db and implicit lsid to given command and omits default readPreference operations: - name: runCommand object: *db From 37b64b344682d117d906b7efa16fc6fa6cf0fbd0 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 26 Apr 2023 11:27:46 -0400 Subject: [PATCH 28/28] refactor: use new variable name --- src/cmap/connection.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index 49c33523a49..f86e4dc3056 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -484,11 +484,11 @@ export class Connection extends TypedEventEmitter { command( ns: MongoDBNamespace, - cmd: Document, + command: Document, options: CommandOptions | undefined, callback: Callback ): void { - cmd = { ...cmd }; + let cmd = { ...command }; const readPreference = getReadPreference(options); const shouldUseOpMsg = supportsOpMsg(this);