diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index 1ed62647a8a..94bf8baa231 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -86,7 +86,6 @@ export interface CommandOptions extends BSONSerializeOptions { /** Session to use for the operation */ session?: ClientSession; documentsReturnedIn?: string; - noResponse?: boolean; omitReadPreference?: boolean; // TODO(NODE-2802): Currently the CommandOptions take a property willRetryWrite which is a hint @@ -99,6 +98,9 @@ export interface CommandOptions extends BSONSerializeOptions { writeConcern?: WriteConcern; directConnection?: boolean; + + /** Triggers fire-and-forget protocol for commands that don't support WriteConcern */ + moreToCome?: boolean; } /** @public */ @@ -446,7 +448,7 @@ export class Connection extends TypedEventEmitter { zlibCompressionLevel: this.description.zlibCompressionLevel }); - if (options.noResponse || message.moreToCome) { + if (message.moreToCome) { yield MongoDBResponse.empty; return; } @@ -534,11 +536,7 @@ export class Connection extends TypedEventEmitter { new CommandSucceededEvent( this, message, - options.noResponse - ? undefined - : message.moreToCome - ? { ok: 1 } - : (object ??= document.toObject(bsonOptions)), + message.moreToCome ? { ok: 1 } : (object ??= document.toObject(bsonOptions)), started, this.description.serverConnectionId ) diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 0bc9165deee..0451769e772 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -656,7 +656,7 @@ export class MongoClient extends TypedEventEmitter implements this, new RunAdminCommandOperation( { endSessions }, - { readPreference: ReadPreference.primaryPreferred, noResponse: true } + { readPreference: ReadPreference.primaryPreferred, moreToCome: true } ) ); } catch (error) { diff --git a/src/operations/command.ts b/src/operations/command.ts index 94ccc6ceafe..3786753bc6e 100644 --- a/src/operations/command.ts +++ b/src/operations/command.ts @@ -55,7 +55,6 @@ export interface CommandOperationOptions // Admin command overrides. dbName?: string; authdb?: string; - noResponse?: boolean; } /** @internal */ diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index ad7d02c044f..289bfed18dd 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -51,7 +51,7 @@ export class RunAdminCommandOperation extends AbstractOperation constructor( public command: Document, public override options: RunCommandOptions & { - noResponse?: boolean; + moreToCome?: boolean; bypassPinningCheck?: boolean; } ) { diff --git a/test/integration/collection-management/collection.test.ts b/test/integration/collection-management/collection.test.ts index 809b4697dea..d73a1f229a4 100644 --- a/test/integration/collection-management/collection.test.ts +++ b/test/integration/collection-management/collection.test.ts @@ -1,6 +1,13 @@ import { expect } from 'chai'; - -import { Collection, type Db, type MongoClient, MongoServerError } from '../../mongodb'; +import * as sinon from 'sinon'; + +import { + Collection, + CreateIndexesOperation, + type Db, + type MongoClient, + MongoServerError +} from '../../mongodb'; import { type FailPoint } from '../../tools/utils'; import { setupDatabase } from '../shared'; @@ -422,6 +429,40 @@ describe('Collection', function () { }); }); + describe('#createIndex', () => { + let client: MongoClient; + let db: Db; + let coll: Collection<{ a: string }>; + const ERROR_RESPONSE = { + ok: 0, + errmsg: + 'WiredTigerIndex::insert: key too large to index, failing 1470 { : "56f37cb8e4b089e98d52ab0e", : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa..." }', + code: 17280 + }; + + beforeEach(async function () { + client = configuration.newClient({ w: 1 }); + db = client.db(configuration.db); + coll = db.collection('test_coll'); + await client.connect(); + sinon.stub(CreateIndexesOperation.prototype, 'execute').callsFake(() => { + throw ERROR_RESPONSE; + }); + }); + + afterEach(async function () { + await client.close(); + sinon.restore(); + }); + + it('should error when createIndex fails', async function () { + const e = await coll.createIndex({ a: 1 }).catch(e => e); + expect(e).to.have.property('ok', ERROR_RESPONSE.ok); + expect(e).to.have.property('errmsg', ERROR_RESPONSE.errmsg); + expect(e).to.have.property('code', ERROR_RESPONSE.code); + }); + }); + describe('countDocuments()', function () { let client: MongoClient; let collection: Collection<{ test: string }>; diff --git a/test/integration/connection-monitoring-and-pooling/connection.test.ts b/test/integration/connection-monitoring-and-pooling/connection.test.ts index 1192dfdbcd4..78433f0665e 100644 --- a/test/integration/connection-monitoring-and-pooling/connection.test.ts +++ b/test/integration/connection-monitoring-and-pooling/connection.test.ts @@ -144,6 +144,25 @@ describe('Connection', function () { } } }); + + it('supports fire-and-forget messages', { + metadata: { requires: { apiVersion: false, topology: '!load-balanced' } }, + test: async function () { + const options: ConnectionOptions = { + ...commonConnectOptions, + connectionType: Connection, + ...this.configuration.options, + metadata: makeClientMetadata({ driverInfo: {} }), + extendedMetadata: addContainerMetadata(makeClientMetadata({ driverInfo: {} })) + }; + + const conn = await connect(options); + const readSpy = sinon.spy(conn, 'readMany'); + await conn.command(ns('$admin.cmd'), { ping: 1 }, { moreToCome: true }); + expect(readSpy).to.not.have.been.called; + conn?.destroy(); + } + }); }); describe('Connection - functional', function () { @@ -278,8 +297,8 @@ describe('Connection', function () { }); afterEach(async function () { - await client.close(); mockServer.destroy(); + await client.close(); sinon.restore(); }); diff --git a/test/integration/node-specific/mongo_client.test.ts b/test/integration/node-specific/mongo_client.test.ts index 634516f8f76..7d3f901fb57 100644 --- a/test/integration/node-specific/mongo_client.test.ts +++ b/test/integration/node-specific/mongo_client.test.ts @@ -682,7 +682,7 @@ describe('class MongoClient', function () { expect(result2).to.have.property('ok', 1); }); - it('sends endSessions with noResponse set', async () => { + it('sends endSessions with writeConcern w = 0 set', async () => { const session = client.startSession(); // make a session to be ended await client.db('test').command({ ping: 1 }, { session }); await session.endSession(); @@ -698,7 +698,7 @@ describe('class MongoClient', function () { expect(startedEvents).to.have.lengthOf(1); expect(startedEvents[0]).to.have.property('commandName', 'endSessions'); expect(endEvents).to.have.lengthOf(1); - expect(endEvents[0]).to.have.property('reply', undefined); // noReponse: true + expect(endEvents[0]).to.containSubset({ reply: { ok: 1 } }); // moreToCome = true }); context('when server selection would return no servers', () => { diff --git a/test/unit/cmap/connection.test.ts b/test/unit/cmap/connection.test.ts index 05e66f3dcfc..344c6e8e997 100644 --- a/test/unit/cmap/connection.test.ts +++ b/test/unit/cmap/connection.test.ts @@ -32,29 +32,6 @@ describe('new Connection()', function () { before(() => mock.createServer().then(s => (server = s))); - it('supports fire-and-forget messages', async function () { - server.setMessageHandler(request => { - const doc = request.document; - if (isHello(doc)) { - request.reply(mock.HELLO); - } - - // black hole all other requests - }); - - const options = { - ...connectionOptionsDefaults, - connectionType: Connection, - hostAddress: server.hostAddress(), - authProviders: new MongoClientAuthProviders() - }; - - const conn = await connect(options); - const readSpy = sinon.spy(conn, 'readMany'); - await conn.command(ns('$admin.cmd'), { ping: 1 }, { noResponse: true }); - expect(readSpy).to.not.have.been.called; - }); - it('destroys streams which time out', async function () { server.setMessageHandler(request => { const doc = request.document; diff --git a/test/unit/collection.test.ts b/test/unit/collection.test.ts index b050e69dc92..c45712abebc 100644 --- a/test/unit/collection.test.ts +++ b/test/unit/collection.test.ts @@ -14,55 +14,6 @@ describe('Collection', function () { await cleanup(); }); - context('#createIndex', () => { - it('should error when createIndex fails', function (done) { - const ERROR_RESPONSE = { - ok: 0, - errmsg: - 'WiredTigerIndex::insert: key too large to index, failing 1470 { : "56f37cb8e4b089e98d52ab0e", : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa..." }', - code: 17280 - }; - - server.setMessageHandler(request => { - const doc = request.document; - - if (isHello(doc)) { - return request.reply(Object.assign({}, HELLO)); - } - - if (doc.createIndexes) { - return request.reply(ERROR_RESPONSE); - } - - if (doc.insert === 'system.indexes') { - return request.reply(ERROR_RESPONSE); - } - }); - - const client = new MongoClient(`mongodb://${server.uri()}`); - - const close = e => client.close().then(() => done(e)); - - client - .connect() - .then(() => client.db('foo').collection('bar')) - .then(coll => coll.createIndex({ a: 1 })) - .then( - () => close('Expected createIndex to fail, but it succeeded'), - e => { - try { - expect(e).to.have.property('ok', ERROR_RESPONSE.ok); - expect(e).to.have.property('errmsg', ERROR_RESPONSE.errmsg); - expect(e).to.have.property('code', ERROR_RESPONSE.code); - close(null); - } catch (err) { - close(err); - } - } - ); - }); - }); - context('#aggregate', () => { // general test for aggregate function function testAggregate(config, done) {