Skip to content

Commit 91431bc

Browse files
authored
feat: add strict versioned API autocomplete MONGOSH-770 (#941)
1 parent 1e66c04 commit 91431bc

34 files changed

+360
-34
lines changed

packages/autocomplete/index.spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ let collections: string[];
77
let databases: string[];
88
const standalone440 = {
99
topology: () => Topologies.Standalone,
10+
apiVersionInfo: () => undefined,
1011
connectionInfo: () => ({
1112
is_atlas: false,
1213
is_data_lake: false,
@@ -15,8 +16,16 @@ const standalone440 = {
1516
getCollectionCompletionsForCurrentDb: () => collections,
1617
getDatabaseCompletions: () => databases
1718
};
19+
const apiStrictParams = {
20+
topology: () => Topologies.Standalone,
21+
apiVersionInfo: () => ({ version: '1', strict: true, deprecationErrors: false }),
22+
connectionInfo: () => undefined,
23+
getCollectionCompletionsForCurrentDb: () => collections,
24+
getDatabaseCompletions: () => databases
25+
};
1826
const sharded440 = {
1927
topology: () => Topologies.Sharded,
28+
apiVersionInfo: () => undefined,
2029
connectionInfo: () => ({
2130
is_atlas: false,
2231
is_data_lake: false,
@@ -28,6 +37,7 @@ const sharded440 = {
2837

2938
const standalone300 = {
3039
topology: () => Topologies.Standalone,
40+
apiVersionInfo: () => undefined,
3141
connectionInfo: () => ({
3242
is_atlas: false,
3343
is_data_lake: false,
@@ -38,6 +48,7 @@ const standalone300 = {
3848
};
3949
const datalake440 = {
4050
topology: () => Topologies.Sharded,
51+
apiVersionInfo: () => undefined,
4152
connectionInfo: () => ({
4253
is_atlas: true,
4354
is_data_lake: true,
@@ -49,13 +60,15 @@ const datalake440 = {
4960

5061
const noParams = {
5162
topology: () => Topologies.Standalone,
63+
apiVersionInfo: () => undefined,
5264
connectionInfo: () => undefined,
5365
getCollectionCompletionsForCurrentDb: () => collections,
5466
getDatabaseCompletions: () => databases
5567
};
5668

5769
const emptyConnectionInfoParams = {
5870
topology: () => Topologies.Standalone,
71+
apiVersionInfo: () => undefined,
5972
connectionInfo: () => ({}),
6073
getCollectionCompletionsForCurrentDb: () => collections,
6174
getDatabaseCompletions: () => databases
@@ -558,4 +571,36 @@ describe('completer.completer', () => {
558571
.to.deep.equal([['show databases'], i, 'exclusive']);
559572
});
560573
});
574+
575+
context('with apiStrict', () => {
576+
it('completes supported methods like db.test.findOneAndReplace', async() => {
577+
const i = 'db.test.findOneAndR';
578+
expect(await completer(apiStrictParams, i))
579+
.to.deep.equal([['db.test.findOneAndReplace'], i]);
580+
});
581+
582+
it('completes common methods like db.test.getName', async() => {
583+
const i = 'db.test.getNam';
584+
expect(await completer(apiStrictParams, i))
585+
.to.deep.equal([['db.test.getName'], i]);
586+
});
587+
588+
it('does not complete unsupported methods like db.test.renameCollection', async() => {
589+
const i = 'db.test.renameC';
590+
expect(await completer(apiStrictParams, i))
591+
.to.deep.equal([[], i]);
592+
});
593+
594+
it('completes supported aggregation stages', async() => {
595+
const i = 'db.test.aggregate([{$mat';
596+
expect(await completer(apiStrictParams, i))
597+
.to.deep.equal([['db.test.aggregate([{$match'], i]);
598+
});
599+
600+
it('does not complete unsupported aggregation stages', async() => {
601+
const i = 'db.test.aggregate([{$indexSta';
602+
expect(await completer(apiStrictParams, i))
603+
.to.deep.equal([[], i]);
604+
});
605+
});
561606
});

packages/autocomplete/index.ts

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface AutocompleteParameters {
2323
is_data_lake: boolean;
2424
server_version: string;
2525
},
26+
apiVersionInfo: () => { version: string, strict: boolean } | undefined;
2627
getCollectionCompletionsForCurrentDb: (collName: string) => string[] | Promise<string[]>;
2728
getDatabaseCompletions: (dbName: string) => string[] | Promise<string[]>;
2829
}
@@ -170,14 +171,20 @@ async function completer(params: AutocompleteParameters, line: string): Promise<
170171

171172
function isAcceptable(
172173
params: AutocompleteParameters,
173-
entry: { version?: string; projectVersion?: string; env?: string[]; },
174+
entry: { version?: string; projectVersion?: string; env?: string[]; apiVersions?: number[] },
174175
versionKey: 'version' | 'projectVersion') {
175176
const connectionInfo = params.connectionInfo();
176-
const isAcceptableVersion =
177-
!entry[versionKey] ||
178-
// TODO: when https://jira.mongodb.org/browse/WRITING-8170 is done we can rely on server_version being present
179-
!connectionInfo?.server_version ||
180-
semver.gte(connectionInfo.server_version, entry[versionKey] as string);
177+
const apiVersionInfo = params.apiVersionInfo();
178+
let isAcceptableVersion;
179+
if (apiVersionInfo?.strict && entry.apiVersions) {
180+
isAcceptableVersion = entry.apiVersions.includes(+apiVersionInfo.version);
181+
} else {
182+
isAcceptableVersion =
183+
!entry[versionKey] ||
184+
// TODO: when https://jira.mongodb.org/browse/PM-2327 is done we can rely on server_version being present
185+
!connectionInfo?.server_version ||
186+
semver.gte(connectionInfo.server_version, entry[versionKey] as string);
187+
}
181188
const isAcceptableEnvironment =
182189
!entry.env ||
183190
!connectionInfo ||
@@ -217,14 +224,23 @@ function filterShellAPI(
217224
if (!c.startsWith(prefix)) return false;
218225
if (completions[c].deprecated) return false;
219226

220-
const serverVersion = params.connectionInfo()?.server_version;
221-
if (!serverVersion) return true;
227+
const apiVersionInfo = params.apiVersionInfo();
228+
let isAcceptableVersion;
229+
let acceptableApiVersions;
230+
if (apiVersionInfo?.strict && (acceptableApiVersions = completions[c].apiVersions)) {
231+
isAcceptableVersion =
232+
+apiVersionInfo.version >= acceptableApiVersions[0] &&
233+
+apiVersionInfo.version <= acceptableApiVersions[1];
234+
} else {
235+
const serverVersion = params.connectionInfo()?.server_version;
236+
if (!serverVersion) return true;
222237

223-
const acceptableVersions = completions[c].serverVersions;
224-
const isAcceptableVersion =
225-
!acceptableVersions ||
226-
(semver.gte(serverVersion, acceptableVersions[0]) &&
227-
semver.lte(serverVersion, acceptableVersions[1]));
238+
const acceptableVersions = completions[c].serverVersions;
239+
isAcceptableVersion =
240+
!acceptableVersions ||
241+
(semver.gte(serverVersion, acceptableVersions[0]) &&
242+
semver.lte(serverVersion, acceptableVersions[1]));
243+
}
228244

229245
const acceptableTopologies = completions[c].topologies;
230246
const isAcceptableTopology =

packages/autocomplete/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/autocomplete/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
},
3131
"dependencies": {
3232
"@mongosh/shell-api": "0.0.0-dev.0",
33-
"mongodb-ace-autocompleter": "^0.4.14",
33+
"mongodb-ace-autocompleter": "^0.5.0",
3434
"semver": "^7.3.2"
3535
}
3636
}

packages/browser-runtime-core/src/autocompleter/shell-api-autocompleter.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Topologies } from '@mongosh/shell-api';
44

55
const standalone440 = {
66
topology: () => Topologies.Standalone,
7+
apiVersionInfo: () => undefined,
78
connectionInfo: () => ({
89
is_atlas: false,
910
is_data_lake: false,

packages/shell-api/src/abstract-cursor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import {
33
toShellResult,
44
returnType,
55
ShellApiWithMongoClass,
6-
returnsPromise
6+
returnsPromise,
7+
apiVersions
78
} from './decorators';
89
import type Mongo from './mongo';
910
import type {
@@ -194,6 +195,7 @@ export abstract class AbstractCursor extends ShellApiWithMongoClass {
194195
}
195196

196197
@returnsPromise
198+
@apiVersions([1])
197199
async explain(verbosity?: ExplainVerbosityLike): Promise<any> {
198200
// TODO: @maurizio we should probably move this in the Explain class?
199201
// NOTE: the node driver always returns the full explain plan

packages/shell-api/src/aggregation-cursor.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { expect } from 'chai';
22
import sinon, { StubbedInstance, stubInterface } from 'ts-sinon';
33
import { signatures, toShellResult } from './index';
44
import AggregationCursor from './aggregation-cursor';
5-
import { ALL_PLATFORMS, ALL_SERVER_VERSIONS, ALL_TOPOLOGIES } from './enums';
5+
import { ALL_PLATFORMS, ALL_SERVER_VERSIONS, ALL_TOPOLOGIES, ALL_API_VERSIONS } from './enums';
66
import { ReplPlatform, AggregationCursor as SPAggregationCursor } from '@mongosh/service-provider-core';
77

88
describe('AggregationCursor', () => {
@@ -27,6 +27,7 @@ describe('AggregationCursor', () => {
2727
returnType: 'AggregationCursor',
2828
platforms: ALL_PLATFORMS,
2929
topologies: ALL_TOPOLOGIES,
30+
apiVersions: ALL_API_VERSIONS,
3031
serverVersions: ALL_SERVER_VERSIONS,
3132
isDirectShellCommand: false,
3233
shellCommandCompleter: undefined

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ describe('Bulk API', () => {
3939
returnType: 'BulkFindOp',
4040
platforms: ALL_PLATFORMS,
4141
topologies: ALL_TOPOLOGIES,
42+
apiVersions: [ 1, Infinity ],
4243
serverVersions: ALL_SERVER_VERSIONS,
4344
isDirectShellCommand: false,
4445
shellCommandCompleter: undefined
@@ -240,6 +241,7 @@ describe('Bulk API', () => {
240241
returnType: 'BulkFindOp',
241242
platforms: ALL_PLATFORMS,
242243
topologies: ALL_TOPOLOGIES,
244+
apiVersions: [ 1, Infinity ],
243245
serverVersions: ALL_SERVER_VERSIONS,
244246
isDirectShellCommand: false,
245247
shellCommandCompleter: undefined

packages/shell-api/src/bulk.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { returnsPromise, shellApiClassDefault, returnType, deprecated, ShellApiWithMongoClass } from './decorators';
1+
import { returnsPromise, shellApiClassDefault, returnType, deprecated, apiVersions, ShellApiWithMongoClass } from './decorators';
22
import Mongo from './mongo';
33
import { CommonErrors, MongoshInvalidInputError, MongoshUnimplementedError } from '@mongosh/errors';
44
import {
@@ -37,12 +37,14 @@ export class BulkFindOp extends ShellApiWithMongoClass {
3737
}
3838

3939
@returnType('BulkFindOp')
40+
@apiVersions([1])
4041
collation(spec: CollationOptions): BulkFindOp {
4142
this._serviceProviderBulkFindOp.collation(spec);
4243
return this;
4344
}
4445

4546
// Blocked by NODE-2751, bulk arrayFilters
47+
@apiVersions([1])
4648
arrayFilters(): BulkFindOp {
4749
throw new MongoshUnimplementedError(
4850
'arrayFilters method on fluent Bulk API is not currently supported.',
@@ -52,39 +54,45 @@ export class BulkFindOp extends ShellApiWithMongoClass {
5254
}
5355

5456
@returnType('BulkFindOp')
57+
@apiVersions([1])
5558
hint(hintDoc: Document): BulkFindOp {
5659
assertArgsDefinedType([hintDoc], [true], 'BulkFindOp.hint');
5760
this._hint = hintDoc;
5861
return this;
5962
}
6063

6164
@returnType('Bulk')
65+
@apiVersions([1])
6266
delete(): Bulk {
6367
this._parentBulk._batchCounts.nRemoveOps++;
6468
this._serviceProviderBulkFindOp.delete();
6569
return this._parentBulk;
6670
}
6771

6872
@returnType('Bulk')
73+
@apiVersions([1])
6974
deleteOne(): Bulk {
7075
this._parentBulk._batchCounts.nRemoveOps++;
7176
this._serviceProviderBulkFindOp.deleteOne();
7277
return this._parentBulk;
7378
}
7479

7580
@returnType('Bulk')
81+
@apiVersions([1])
7682
@deprecated
7783
remove(): Bulk {
7884
return this.delete();
7985
}
8086

8187
@returnType('Bulk')
88+
@apiVersions([1])
8289
@deprecated
8390
removeOne(): Bulk {
8491
return this.deleteOne();
8592
}
8693

8794
@returnType('Bulk')
95+
@apiVersions([1])
8896
replaceOne(replacement: Document): Bulk {
8997
this._parentBulk._batchCounts.nUpdateOps++;
9098
assertArgsDefinedType([replacement], [true], 'BulkFindOp.replacement');
@@ -97,6 +105,7 @@ export class BulkFindOp extends ShellApiWithMongoClass {
97105
}
98106

99107
@returnType('Bulk')
108+
@apiVersions([1])
100109
updateOne(update: Document): Bulk {
101110
this._parentBulk._batchCounts.nUpdateOps++;
102111
assertArgsDefinedType([update], [true], 'BulkFindOp.update');
@@ -182,6 +191,7 @@ export default class Bulk extends ShellApiWithMongoClass {
182191
}
183192

184193
@returnsPromise
194+
@apiVersions([1])
185195
async execute(writeConcern?: WriteConcern): Promise<BulkWriteResult> {
186196
const { result } = await this._serviceProviderBulkOp.execute() as any;
187197
this._executed = true;
@@ -199,12 +209,14 @@ export default class Bulk extends ShellApiWithMongoClass {
199209
}
200210

201211
@returnType('BulkFindOp')
212+
@apiVersions([1])
202213
find(query: Document): BulkFindOp {
203214
assertArgsDefinedType([query], [true], 'Bulk.find');
204215
return new BulkFindOp(this._serviceProviderBulkOp.find(query), this);
205216
}
206217

207218
@returnType('Bulk')
219+
@apiVersions([1])
208220
insert(document: Document): Bulk {
209221
this._batchCounts.nInsertOps++;
210222
assertArgsDefinedType([document], [true], 'Bulk.insert');

packages/shell-api/src/change-stream-cursor.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { expect } from 'chai';
22
import sinon, { StubbedInstance, stubInterface } from 'ts-sinon';
33
import { signatures, toShellResult } from './index';
44
import ChangeStreamCursor from './change-stream-cursor';
5-
import { ADMIN_DB, ALL_PLATFORMS, ALL_SERVER_VERSIONS, ALL_TOPOLOGIES } from './enums';
5+
import { ADMIN_DB, ALL_PLATFORMS, ALL_SERVER_VERSIONS, ALL_TOPOLOGIES, ALL_API_VERSIONS } from './enums';
66
import { ChangeStream, Document } from '@mongosh/service-provider-core';
77
import { startTestCluster } from '../../../testing/integration-testing-hooks';
88
import { CliServiceProvider } from '../../service-provider-server/lib';
@@ -33,6 +33,7 @@ describe('ChangeStreamCursor', () => {
3333
returnType: { type: 'unknown', attributes: {} },
3434
platforms: ALL_PLATFORMS,
3535
topologies: ALL_TOPOLOGIES,
36+
apiVersions: ALL_API_VERSIONS,
3637
serverVersions: ALL_SERVER_VERSIONS,
3738
isDirectShellCommand: false,
3839
shellCommandCompleter: undefined

0 commit comments

Comments
 (0)