Skip to content

Commit 28d5728

Browse files
authored
feat(shell-api): add Session.withTransaction MONGOSH-1001 (#1127)
1 parent 5ccc158 commit 28d5728

File tree

3 files changed

+55
-1
lines changed

3 files changed

+55
-1
lines changed

packages/i18n/src/locales/en_US.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1673,6 +1673,11 @@ const translations: Catalog = {
16731673
link: 'https://docs.mongodb.com/manual/reference/method/Session.abortTransaction/#Session.abortTransaction',
16741674
description: 'Aborts the session’s transaction.'
16751675
},
1676+
withTransaction: {
1677+
link: 'https://docs.mongodb.com/manual/reference/method/Session.abortTransaction/#Session.withTransaction',
1678+
description: 'Run a function within a transaction context.',
1679+
example: 'session.withTransaction(() => { session.getDatabase("test").test.insertOne({ doc: 1 }); })'
1680+
},
16761681
advanceClusterTime: {
16771682
description: 'Advances the clusterTime for a Session to the provided clusterTime.'
16781683
}

packages/shell-api/src/session.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ describe('Session', () => {
138138
await session.abortTransaction();
139139
expect(serviceProviderSession.abortTransaction).to.have.been.calledOnceWith();
140140
});
141+
it('withTransaction', async() => {
142+
serviceProviderSession.withTransaction.resolves();
143+
await session.withTransaction(() => {});
144+
expect(serviceProviderSession.withTransaction).to.have.been.calledOnce;
145+
});
141146
});
142147
describe('integration', () => {
143148
const [ srv0 ] = startTestCluster(['--replicaset']);
@@ -286,6 +291,42 @@ describe('Session', () => {
286291
await session.abortTransaction();
287292
expect((await testColl.findOne({ value: 'test' })).count).to.equal(0);
288293
});
294+
it('can run withTransaction in the success case', async() => {
295+
const doc = { value: 'test', count: 0 };
296+
const testColl = mongo.getDB(databaseName).getCollection('coll');
297+
await testColl.drop();
298+
await testColl.insertOne(doc);
299+
expect((await testColl.findOne({ value: 'test' })).count).to.equal(0);
300+
session = mongo.startSession();
301+
await session.withTransaction(async() => {
302+
const sessionColl = session.getDatabase(databaseName).getCollection('coll');
303+
expect((await sessionColl.updateOne(
304+
{ value: 'test' },
305+
{ $inc: { count: 1 } }
306+
)).acknowledged).to.be.true;
307+
expect((await testColl.findOne({ value: 'test' })).count).to.equal(0);
308+
});
309+
expect((await testColl.findOne({ value: 'test' })).count).to.equal(1);
310+
});
311+
it('can run withTransaction in the failure case', async() => {
312+
const doc = { value: 'test', count: 0 };
313+
const testColl = mongo.getDB(databaseName).getCollection('coll');
314+
await testColl.drop();
315+
await testColl.insertOne(doc);
316+
expect((await testColl.findOne({ value: 'test' })).count).to.equal(0);
317+
session = mongo.startSession();
318+
const { err } = await session.withTransaction(async() => {
319+
const sessionColl = session.getDatabase(databaseName).getCollection('coll');
320+
expect((await sessionColl.updateOne(
321+
{ value: 'test' },
322+
{ $inc: { count: 1 } }
323+
)).acknowledged).to.be.true;
324+
expect((await testColl.findOne({ value: 'test' })).count).to.equal(0);
325+
throw new Error('fails');
326+
}).catch(err => ({ err }));
327+
expect(err.message).to.equal('fails');
328+
expect((await testColl.findOne({ value: 'test' })).count).to.equal(0);
329+
});
289330
});
290331
describe('after resetting connection will error with expired session', () => {
291332
skipIfApiStrict();

packages/shell-api/src/session.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { CommonErrors, MongoshInvalidInputError } from '@mongosh/errors';
2121
import { assertArgsDefinedType, isValidDatabaseName } from './helpers';
2222

2323
@shellApiClassDefault
24-
@classPlatforms([ ReplPlatform.CLI ] )
24+
@classPlatforms([ ReplPlatform.CLI ])
2525
export default class Session extends ShellApiWithMongoClass {
2626
public id: ServerSessionId | undefined;
2727
public _session: ClientSession;
@@ -100,4 +100,12 @@ export default class Session extends ShellApiWithMongoClass {
100100
async abortTransaction(): Promise<Document> {
101101
return await this._session.abortTransaction();
102102
}
103+
104+
@returnsPromise
105+
async withTransaction<T extends(...args: any) => any>(fn: T, options: TransactionOptions = {}): Promise<ReturnType<T>> {
106+
assertArgsDefinedType([fn, options], ['function', [undefined, 'object']]);
107+
// The driver doesn't automatically ensure that fn is an async
108+
// function/convert its return type to a Promise, so we do that here.
109+
return await this._session.withTransaction(async() => await fn(), options);
110+
}
103111
}

0 commit comments

Comments
 (0)