diff --git a/packages/i18n/src/locales/en_US.ts b/packages/i18n/src/locales/en_US.ts index d29e8475c4..8c63a6280b 100644 --- a/packages/i18n/src/locales/en_US.ts +++ b/packages/i18n/src/locales/en_US.ts @@ -1685,9 +1685,14 @@ const translations: Catalog = { description: 'Closes a Mongo object, disposing of related resources and closing the underlying connection.' }, getReadConcern: { - link: 'https://docs.mongodb.com/manual/reference/method/db.getReadConcern', - description: 'Calls the getReadConcern command', - example: 'db.getReadConcern()' + link: 'https://docs.mongodb.com/manual/reference/method/Mongo.getReadConcern', + description: 'Returns the ReadConcern set for the connection.', + example: 'db.getMongo().getReadConcern()' + }, + getWriteConcern: { + link: 'https://docs.mongodb.com/manual/reference/method/Mongo.getWriteConcern', + description: 'Returns the WriteConcern set for the connection.', + example: 'db.getMongo().getWriteConcern()' }, getReadPref: { link: 'https://docs.mongodb.com/manual/reference/method/Mongo.getReadPref', @@ -1714,6 +1719,11 @@ const translations: Catalog = { description: 'Sets the ReadConcern for the connection', example: 'db.getMongo().setReadConcern(level)' }, + setWriteConcern: { + link: 'https://docs.mongodb.com/manual/reference/method/Mongo.setWriteConcern', + description: 'Sets the WriteConcern for the connection', + example: 'db.getMongo().setWriteConcern(\'majority\')' + }, setCausalConsistency: { description: 'This method is deprecated. It is not possible to set causal consistency for an entire connection due to driver limitations, use startSession({causalConsistency: <>}) instead.' }, diff --git a/packages/shell-api/src/integration.spec.ts b/packages/shell-api/src/integration.spec.ts index 1929064588..ccfeef563a 100644 --- a/packages/shell-api/src/integration.spec.ts +++ b/packages/shell-api/src/integration.spec.ts @@ -1736,6 +1736,18 @@ describe('Shell API (integration)', function() { expect(serviceProvider.mongoClient).to.not.equal(oldMC); }); }); + describe('setWriteConcern', () => { + it('reconnects', async() => { + const oldMC = serviceProvider.mongoClient; + expect(mongo.getWriteConcern()).to.equal(undefined); + await mongo.setWriteConcern('majority', 200); + expect(mongo.getWriteConcern()).to.deep.equal({ + w: 'majority', + wtimeout: 200 + }); + expect(serviceProvider.mongoClient).to.not.equal(oldMC); + }); + }); describe('close', () => { it('removes the connection from the set of connections', async() => { // eslint-disable-next-line new-cap diff --git a/packages/shell-api/src/mongo.spec.ts b/packages/shell-api/src/mongo.spec.ts index c9b4b1869d..ca9cc773d5 100644 --- a/packages/shell-api/src/mongo.spec.ts +++ b/packages/shell-api/src/mongo.spec.ts @@ -368,6 +368,33 @@ describe('Mongo', () => { expect.fail(); }); }); + describe('getWriteConcern', () => { + it('calls serviceProvider.getWriteConcern', async() => { + const expectedResult: WriteConcern = { w: 'majority', wtimeout: 200 }; + serviceProvider.getWriteConcern.returns(expectedResult as any); + const res = mongo.getWriteConcern(); + expect(serviceProvider.getWriteConcern).to.have.been.calledWith(); + expect(res).to.equal(expectedResult); + }); + + it('returns undefined if not set', async() => { + serviceProvider.getWriteConcern.returns(undefined); + const res = mongo.getWriteConcern(); + expect(serviceProvider.getWriteConcern).to.have.been.calledWith(); + expect(res).to.equal(undefined); + }); + + it('throws InternalError if getWriteConcern errors', async() => { + const expectedError = new Error(); + serviceProvider.getWriteConcern.throws(expectedError); + try { + mongo.getWriteConcern(); + } catch (catchedError) { + return expect(catchedError).to.be.instanceOf(MongoshInternalError); + } + expect.fail(); + }); + }); describe('setReadPref', () => { it('calls serviceProvider.restConnectionOptions', async() => { serviceProvider.resetConnectionOptions.resolves(); @@ -394,7 +421,7 @@ describe('Mongo', () => { }); }); describe('setReadConcern', () => { - it('calls serviceProvider.restConnectionOptions', async() => { + it('calls serviceProvider.resetConnectionOptions', async() => { serviceProvider.resetConnectionOptions.resolves(); await mongo.setReadConcern('majority'); expect(serviceProvider.resetConnectionOptions).to.have.been.calledWith({ @@ -415,6 +442,32 @@ describe('Mongo', () => { expect.fail(); }); }); + describe('setWriteConcern', () => { + [ + { args: ['majority'], opts: { w: 'majority' } }, + { args: ['majority', 200], opts: { w: 'majority', wtimeoutMS: 200 } }, + { args: ['majority', 200, false], opts: { w: 'majority', wtimeoutMS: 200, journal: false } }, + { args: ['majority', undefined, false], opts: { w: 'majority', journal: false } }, + { args: [{ w: 'majority', wtimeout: 200, fsync: 1 }], opts: { w: 'majority', wtimeoutMS: 200, journal: true } } + ].forEach(({ args, opts }) => { + it(`calls serviceProvider.resetConnectionOptions for args ${JSON.stringify(args)}`, async() => { + serviceProvider.resetConnectionOptions.resolves(); + await mongo.setWriteConcern.call(mongo, ...args); // tricking TS into thinking the arguments are correct + expect(serviceProvider.resetConnectionOptions).to.have.been.calledWith(opts); + }); + }); + + it('throws if resetConnectionOptions errors', async() => { + const expectedError = new Error(); + serviceProvider.resetConnectionOptions.throws(expectedError); + try { + await mongo.setWriteConcern('majority'); + } catch (catchedError) { + return expect(catchedError).to.equal(expectedError); + } + expect.fail(); + }); + }); describe('startSession', () => { beforeEach(() => { serviceProvider.startSession.returns(driverSession as any); diff --git a/packages/shell-api/src/mongo.ts b/packages/shell-api/src/mongo.ts index 47783087e1..274cd3ae8d 100644 --- a/packages/shell-api/src/mongo.ts +++ b/packages/shell-api/src/mongo.ts @@ -34,7 +34,8 @@ import { MongoClientOptions, AutoEncryptionOptions as SPAutoEncryption, ServerApi, - ServerApiVersionId + ServerApiVersionId, + WriteConcern } from '@mongosh/service-provider-core'; import type Collection from './collection'; import Database from './database'; @@ -351,6 +352,14 @@ export default class Mongo extends ShellApiClass { } } + getWriteConcern(): WriteConcern | undefined { + try { + return this._serviceProvider.getWriteConcern(); + } catch { + throw new MongoshInternalError('Error retrieving WriteConcern.'); + } + } + @returnsPromise async setReadPref(mode: ReadPreferenceLike, tagSet?: Record[], hedgeOptions?: Document): Promise { await this._serviceProvider.resetConnectionOptions({ @@ -368,6 +377,59 @@ export default class Mongo extends ShellApiClass { await this._serviceProvider.resetConnectionOptions({ readConcern: { level: level } }); } + async setWriteConcern(concern: WriteConcern): Promise + async setWriteConcern(wValue: string | number, wtimeoutMSValue?: number | undefined, jValue?: boolean | undefined): Promise + @returnsPromise + async setWriteConcern(concernOrWValue: WriteConcern | string | number, wtimeoutMSValue?: number | undefined, jValue?: boolean | undefined): Promise { + const options: MongoClientOptions = {}; + let concern: WriteConcern; + + if (typeof concernOrWValue === 'object') { + if (wtimeoutMSValue !== undefined || jValue !== undefined) { + throw new MongoshInvalidInputError('If concern is given as an object no other arguments must be specified', CommonErrors.InvalidArgument); + } + concern = concernOrWValue; + } else { + concern = {}; + if (typeof concernOrWValue !== 'string' && typeof concernOrWValue !== 'number') { + throw new MongoshInvalidInputError(`w value must be a number or string, got: ${typeof concernOrWValue}`, CommonErrors.InvalidArgument); + } else if (typeof concernOrWValue === 'number' && concernOrWValue < 0) { + throw new MongoshInvalidInputError(`w value must be equal to or greather than 0, got: ${concernOrWValue}`, CommonErrors.InvalidArgument); + } + concern.w = concernOrWValue as any; + + if (wtimeoutMSValue !== undefined) { + if (typeof wtimeoutMSValue !== 'number') { + throw new MongoshInvalidInputError(`wtimeoutMS value must be a number, got: ${typeof wtimeoutMSValue}`, CommonErrors.InvalidArgument); + } else if (wtimeoutMSValue < 0) { + throw new MongoshInvalidInputError(`wtimeoutMS must be equal to or greather than 0, got: ${wtimeoutMSValue}`, CommonErrors.InvalidArgument); + } + concern.wtimeout = wtimeoutMSValue; + } + + if (jValue !== undefined) { + if (typeof jValue !== 'boolean') { + throw new MongoshInvalidInputError(`j value must be a boolean, got: ${typeof jValue}`, CommonErrors.InvalidArgument); + } + concern.j = jValue; + } + } + + if (concern.w !== undefined) { + options.w = concern.w; + } + if (concern.wtimeout !== undefined) { + options.wtimeoutMS = concern.wtimeout; + } + if (concern.j !== undefined) { + options.journal = concern.j; + } + if (concern.fsync !== undefined) { + options.journal = !!concern.fsync; + } + await this._serviceProvider.resetConnectionOptions(options); + } + @topologies([Topologies.ReplSet]) startSession(options: Document = {}): Session { const driverOptions = {};