From 30fb18d7343b2bd4e898cb91e585622fc95c934b Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 16 Mar 2022 18:35:19 +0100 Subject: [PATCH 1/4] feat(shell-api): add support for snapshot reads MONGOSH-1151 --- packages/shell-api/src/mongo.ts | 42 +++++++++++++++++--------- packages/shell-api/src/session.spec.ts | 12 ++++++++ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/packages/shell-api/src/mongo.ts b/packages/shell-api/src/mongo.ts index 5fcd655f95..cfbbfc38e3 100644 --- a/packages/shell-api/src/mongo.ts +++ b/packages/shell-api/src/mongo.ts @@ -21,6 +21,8 @@ import { } from './decorators'; import { ChangeStreamOptions, + ClientSessionOptions, + CommandOperationOptions, Document, generateUri, ListDatabasesOptions, @@ -451,22 +453,34 @@ export default class Mongo extends ShellApiClass { @topologies([Topologies.ReplSet]) startSession(options: Document = {}): Session { - const driverOptions = {}; - if (options === undefined) { - return new Session(this, driverOptions, this._serviceProvider.startSession(driverOptions)); + const allTransactionOptions = [ + 'readConcern', 'writeConcern', 'readPreference', 'maxCommitTimeMS' + ] as const; + // These typechecks might look weird, but will tell us if we are missing + // support for a newly introduced driver option when it is being added + // to the driver API. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const __typecheck1: (typeof allTransactionOptions)[number] = '' as Exclude; + const defaultTransactionOptions: TransactionOptions = {}; + for (const key of allTransactionOptions) { + if (typeof options[key] !== 'undefined') { + defaultTransactionOptions[key] = options[key]; + } + } + + const allSessionOptions = [ 'causalConsistency', 'snapshot' ] as const; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const __typecheck2: (typeof allSessionOptions)[number] | 'defaultTransactionOptions' = '' as keyof ClientSessionOptions; + const driverOptions: ClientSessionOptions = {}; + if (Object.keys(defaultTransactionOptions).length > 0) { + driverOptions.defaultTransactionOptions = defaultTransactionOptions; + } + for (const key of allSessionOptions) { + if (typeof options[key] !== 'undefined') { + driverOptions[key] = options[key]; + } } - const defaultTransactionOptions = {} as TransactionOptions; - // Only include option if not undef - Object.assign(defaultTransactionOptions, - options.readConcern && { readConcern: options.readConcern }, - options.writeConcern && { writeConcern: options.writeConcern }, - options.readPreference && { readPreference: options.readPreference } - ); - Object.assign(driverOptions, - Object.keys(defaultTransactionOptions).length > 0 && { defaultTransactionOptions: defaultTransactionOptions }, - options.causalConsistency !== undefined && { causalConsistency: options.causalConsistency } - ); return new Session(this, driverOptions, this._serviceProvider.startSession(driverOptions)); } diff --git a/packages/shell-api/src/session.spec.ts b/packages/shell-api/src/session.spec.ts index 6c1c912b78..b7917e83e3 100644 --- a/packages/shell-api/src/session.spec.ts +++ b/packages/shell-api/src/session.spec.ts @@ -227,6 +227,18 @@ describe('Session', () => { } expect.fail('Error not thrown'); }); + it('starts a session with snapshot reads if requested', async() => { + session = mongo.startSession({ snapshot: true }); + await session.getDatabase(databaseName).getCollection('coll').findOne({}); + try { + await session.getDatabase(databaseName).getCollection('coll').insertOne({}); + expect.fail('missed exception'); + } catch (e) { + expect(e.message).to.include('snapshot'); // Cannot do writes with snapshot: true + } + expect(session._session.snapshotEnabled).to.equal(true); + await session.endSession(); + }); }); describe('transaction methods are called', () => { it('cannot call start transaction twice', () => { From de2f23bf7e49da5768788ea49c29423e877c5d39 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 16 Mar 2022 21:35:29 +0100 Subject: [PATCH 2/4] fixup: snapshot reads are 5.0+ only --- packages/shell-api/src/session.spec.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/shell-api/src/session.spec.ts b/packages/shell-api/src/session.spec.ts index b7917e83e3..65546c26d3 100644 --- a/packages/shell-api/src/session.spec.ts +++ b/packages/shell-api/src/session.spec.ts @@ -227,17 +227,21 @@ describe('Session', () => { } expect.fail('Error not thrown'); }); - it('starts a session with snapshot reads if requested', async() => { - session = mongo.startSession({ snapshot: true }); - await session.getDatabase(databaseName).getCollection('coll').findOne({}); - try { - await session.getDatabase(databaseName).getCollection('coll').insertOne({}); - expect.fail('missed exception'); - } catch (e) { - expect(e.message).to.include('snapshot'); // Cannot do writes with snapshot: true - } - expect(session._session.snapshotEnabled).to.equal(true); - await session.endSession(); + context('with 5.0+ server', () => { + skipIfApiStrict(); + skipIfServerVersion(testServer, '< 5.0'); + it('starts a session with snapshot reads if requested', async() => { + session = mongo.startSession({ snapshot: true }); + await session.getDatabase(databaseName).getCollection('coll').findOne({}); + try { + await session.getDatabase(databaseName).getCollection('coll').insertOne({}); + expect.fail('missed exception'); + } catch (e) { + expect(e.message).to.include('snapshot'); // Cannot do writes with snapshot: true + } + expect(session._session.snapshotEnabled).to.equal(true); + await session.endSession(); + }); }); }); describe('transaction methods are called', () => { From 209b037dd329d1118e2f465e06d4633fbccafd46 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Thu, 17 Mar 2022 12:18:21 +0100 Subject: [PATCH 3/4] fixup: missing import --- packages/shell-api/src/session.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/shell-api/src/session.spec.ts b/packages/shell-api/src/session.spec.ts index 65546c26d3..644e39bd8b 100644 --- a/packages/shell-api/src/session.spec.ts +++ b/packages/shell-api/src/session.spec.ts @@ -13,7 +13,7 @@ import { ALL_TOPOLOGIES } from './enums'; import { CliServiceProvider } from '../../service-provider-server'; -import { startTestCluster, skipIfApiStrict } from '../../../testing/integration-testing-hooks'; +import { startTestCluster, skipIfServerVersion, skipIfApiStrict } from '../../../testing/integration-testing-hooks'; import { ensureMaster, ensureSessionExists } from '../../../testing/helpers'; import Database from './database'; import { CommonErrors, MongoshInvalidInputError } from '@mongosh/errors'; @@ -229,7 +229,7 @@ describe('Session', () => { }); context('with 5.0+ server', () => { skipIfApiStrict(); - skipIfServerVersion(testServer, '< 5.0'); + skipIfServerVersion(srv0, '< 5.0'); it('starts a session with snapshot reads if requested', async() => { session = mongo.startSession({ snapshot: true }); await session.getDatabase(databaseName).getCollection('coll').findOne({}); From 7475b393c6de90cb26fd08f0f50a7345c3009623 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Thu, 17 Mar 2022 12:42:22 +0100 Subject: [PATCH 4/4] fixup: use function call instead of assignment for typechecking --- packages/shell-api/src/mongo.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/shell-api/src/mongo.ts b/packages/shell-api/src/mongo.ts index cfbbfc38e3..7fe52fde84 100644 --- a/packages/shell-api/src/mongo.ts +++ b/packages/shell-api/src/mongo.ts @@ -456,11 +456,13 @@ export default class Mongo extends ShellApiClass { const allTransactionOptions = [ 'readConcern', 'writeConcern', 'readPreference', 'maxCommitTimeMS' ] as const; - // These typechecks might look weird, but will tell us if we are missing - // support for a newly introduced driver option when it is being added - // to the driver API. // eslint-disable-next-line @typescript-eslint/no-unused-vars - const __typecheck1: (typeof allTransactionOptions)[number] = '' as Exclude; + function assertAllTransactionOptionsUsed(_options: (typeof allTransactionOptions)[number]) { + // These typechecks might look weird, but will tell us if we are missing + // support for a newly introduced driver option when it is being added + // to the driver API. + } + assertAllTransactionOptionsUsed('' as Exclude); const defaultTransactionOptions: TransactionOptions = {}; for (const key of allTransactionOptions) { if (typeof options[key] !== 'undefined') { @@ -470,7 +472,8 @@ export default class Mongo extends ShellApiClass { const allSessionOptions = [ 'causalConsistency', 'snapshot' ] as const; // eslint-disable-next-line @typescript-eslint/no-unused-vars - const __typecheck2: (typeof allSessionOptions)[number] | 'defaultTransactionOptions' = '' as keyof ClientSessionOptions; + function assertAllSessionOptionsUsed(_options: (typeof allSessionOptions)[number] | 'defaultTransactionOptions') {} + assertAllSessionOptionsUsed('' as keyof ClientSessionOptions); const driverOptions: ClientSessionOptions = {}; if (Object.keys(defaultTransactionOptions).length > 0) { driverOptions.defaultTransactionOptions = defaultTransactionOptions;