Skip to content

Commit c7c55f8

Browse files
authored
feat(shell-api): add shell helpers for checkMetadataConsistency MONGOSH-1432 (#1489)
* Add shell helpers foir checkMetadataConsistency * properly typed cursors * fixes * missing help & autocomplete tests
1 parent b6e1fe1 commit c7c55f8

21 files changed

+391
-70
lines changed

packages/autocomplete/src/index.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,10 @@ describe('completer.completer', () => {
281281
.filter(c =>
282282
![
283283
'count', 'update', 'remove', 'insert', 'save', 'findAndModify', 'reIndex', 'mapReduce',
284-
// search index helpers are 6.0+
285-
'getSearchIndexes', 'createSearchIndex', 'createSearchIndexes', 'dropSearchIndex', 'updateSearchIndex'
284+
// 6.0+
285+
'getSearchIndexes', 'createSearchIndex', 'createSearchIndexes', 'dropSearchIndex', 'updateSearchIndex',
286+
// 7.0+
287+
'checkMetadataConsistency'
286288
].includes(c)
287289
)
288290
.map(c => `${i}${c}`);

packages/i18n/src/locales/en_US.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,11 @@ const translations: Catalog = {
699699
description: 'Starts or stops collecting metrics about reads and writes against an unsharded or sharded collection.',
700700
example: 'db.coll.configureQueryAnalyzer(options)'
701701
},
702+
checkMetadataConsistency: {
703+
link: 'https://docs.mongodb.com/manual/reference/method/db.collection.checkMetadataConsistency',
704+
description: 'Returns a cursor with information about metadata inconsistencies',
705+
example: 'db.coll.checkMetadataConsistency(<options>)'
706+
},
702707
getSearchIndexes: {
703708
link: 'https://docs.mongodb.com/manual/reference/method/db.collection.getSearchIndexes',
704709
description: 'Returns an array that holds a list of documents that identify and describe the existing search indexes on the collection.',
@@ -1346,6 +1351,11 @@ const translations: Catalog = {
13461351
},
13471352
setSecondaryOk: {
13481353
description: 'This method is deprecated. Use db.getMongo().setReadPref() instead'
1354+
},
1355+
checkMetadataConsistency: {
1356+
link: 'https://docs.mongodb.com/manual/reference/method/db.checkMetadataConsistency',
1357+
description: 'Returns a cursor with information about metadata inconsistencies',
1358+
example: 'db.checkMetadataConsistency(<options>)'
13491359
}
13501360
}
13511361
}
@@ -1441,6 +1451,66 @@ const translations: Catalog = {
14411451
description: 'Explainable Cursor. See the Cursor class help methods for more information'
14421452
}
14431453
},
1454+
RunCommandCursor: {
1455+
help: {
1456+
description: 'RunCommandCursor Class',
1457+
attributes: {
1458+
batchSize: {
1459+
description: 'Specifies the number of documents to return in each batch of the response from the MongoDB instance.',
1460+
},
1461+
close: {
1462+
link: 'https://docs.mongodb.com/manual/reference/method/cursor.close',
1463+
description: 'Instructs the server to close a cursor and free associated server resources. The server will automatically close cursors that have no remaining results, as well as cursors that have been idle for a period of time and lack the cursor.noCursorTimeout() option.',
1464+
},
1465+
forEach: {
1466+
link: 'https://docs.mongodb.com/manual/reference/method/cursor.forEach',
1467+
description: 'Iterates the cursor to apply a JavaScript function to each document from the cursor.',
1468+
},
1469+
hasNext: {
1470+
link: 'https://docs.mongodb.com/manual/reference/method/cursor.hasNext',
1471+
description: 'cursor.hasNext() returns true if the cursor returned by the db.collection.aggregate() can iterate further to return more documents. NOTE: if the cursor is tailable with awaitData then hasNext will block until a document is returned. To check if a document is in the cursor\'s batch without waiting, use tryNext instead',
1472+
},
1473+
isClosed: {
1474+
link: 'https://docs.mongodb.com/manual/reference/method/cursor.isClosed',
1475+
description: 'Returns true if the cursor is closed.',
1476+
},
1477+
isExhausted: {
1478+
link: 'https://docs.mongodb.com/manual/reference/method/cursor.isExhausted',
1479+
description: 'cursor.isExhausted() returns true if the cursor is closed and there are no remaining objects in the batch.',
1480+
},
1481+
itcount: {
1482+
link: 'https://docs.mongodb.com/manual/reference/method/cursor.itcount',
1483+
description: 'Counts the number of documents remaining in a cursor. itcount() is similar to cursor.count(), but actually executes the query on an existing iterator, exhausting its contents in the process.',
1484+
},
1485+
map: {
1486+
link: 'https://docs.mongodb.com/manual/reference/method/cursor.map',
1487+
description: 'Applies the first argument, a function, to each document visited by the cursor and collects the return values from successive application into an array.',
1488+
},
1489+
maxTimeMS: {
1490+
link: 'https://docs.mongodb.com/manual/reference/method/cursor.maxTimeMS',
1491+
description: 'Specifies a cumulative time limit in milliseconds for processing operations on a cursor.',
1492+
},
1493+
next: {
1494+
link: 'https://docs.mongodb.com/manual/reference/method/cursor.next',
1495+
description: 'The next document in the cursor returned by the db.collection.aggregate() method. NOTE: if the cursor is tailable with awaitData then next will block until a document is returned. To check if a document is in the cursor\'s batch without waiting, use tryNext instead',
1496+
},
1497+
objsLeftInBatch: {
1498+
link: 'https://docs.mongodb.com/manual/reference/method/cursor.objsLeftInBatch',
1499+
description: 'cursor.objsLeftInBatch() returns the number of documents remaining in the current batch.',
1500+
},
1501+
pretty: {
1502+
description: 'Deprecated. The shell provides auto-formatting so this method is no longer useful'
1503+
},
1504+
toArray: {
1505+
link: 'https://docs.mongodb.com/manual/reference/method/cursor.toArray',
1506+
description: 'The toArray() method returns an array that contains all the documents from a cursor. The method iterates completely the cursor, loading all the documents into RAM and exhausting the cursor.',
1507+
},
1508+
tryNext: {
1509+
description: 'If a document is in the cursor\'s batch it will be returned, otherwise null will be returned'
1510+
}
1511+
}
1512+
}
1513+
},
14441514
ReplicaSet: {
14451515
help: {
14461516
description: 'Replica Set Class',
@@ -1710,6 +1780,11 @@ const translations: Catalog = {
17101780
description: 'Re-enable auto-merge on one collection',
17111781
example: 'sh.enableAutoMerger(ns)',
17121782
},
1783+
checkMetadataConsistency: {
1784+
link: 'https://docs.mongodb.com/manual/reference/method/sh.checkMetadataConsistency',
1785+
description: 'Returns a cursor with information about metadata inconsistencies',
1786+
example: 'sh.checkMetadataConsistency(<options>)'
1787+
}
17131788
}
17141789
}
17151790
},

packages/service-provider-core/src/admin.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ export interface CreateEncryptedCollectionOptions {
3030
masterKey?: AWSEncryptionKeyOptions | AzureEncryptionKeyOptions | GCPEncryptionKeyOptions;
3131
}
3232

33+
export interface CheckMetadataConsistencyOptions {
34+
cursor?: {
35+
batchSize: number
36+
},
37+
checkIndexes?: 1
38+
}
39+
3340
export default interface Admin {
3441
/**
3542
* What platform (Compass/CLI/Browser)

packages/service-provider-core/src/all-transport-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type {
2121
ClusterTime,
2222
FindCursor,
2323
CursorFlag,
24+
RunCommandCursor,
2425
Db,
2526
DbOptions,
2627
DeleteOptions,

packages/service-provider-core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export * from './all-fle-types';
2727

2828
export { MapReduceOptions, FinalizeFunction } from './map-reduce-options';
2929

30-
export { CreateEncryptedCollectionOptions } from './admin';
30+
export { CreateEncryptedCollectionOptions, CheckMetadataConsistencyOptions } from './admin';
3131

3232
const bson = {
3333
ObjectId,

packages/service-provider-core/src/writable.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { RunCommandCursor, RunCursorCommandOptions } from 'mongodb';
12
import type {
23
Document,
34
InsertOneOptions,
@@ -58,6 +59,20 @@ export default interface Writable {
5859
dbOptions?: DbOptions
5960
): Promise<Document>;
6061

62+
/**
63+
* @param {String} db - the db name
64+
* @param spec
65+
* @param options
66+
* @param {DbOptions} dbOptions - The database options
67+
* @return {Promise<Document>}
68+
*/
69+
runCursorCommand(
70+
db: string,
71+
spec: Document,
72+
options: RunCursorCommandOptions,
73+
dbOptions?: DbOptions
74+
): RunCommandCursor;
75+
6176
/**
6277
* Drop a database
6378
*

packages/service-provider-server/src/cli-service-provider.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,32 @@ describe('CliServiceProvider', () => {
370370
});
371371
});
372372

373+
describe('#runCursorCommand', () => {
374+
let clientStub: any;
375+
let dbStub: any;
376+
const commandResult = 'a-cursor';
377+
378+
beforeEach(() => {
379+
dbStub = stubInterface<Db>();
380+
clientStub = stubInterface<MongoClient>();
381+
dbStub.runCursorCommand.returns(commandResult);
382+
clientStub.db.returns(dbStub);
383+
serviceProvider = new CliServiceProvider(clientStub, bus, dummyOptions);
384+
});
385+
386+
afterEach(() => {
387+
dbStub = null;
388+
clientStub = null;
389+
serviceProvider = null;
390+
});
391+
392+
it('executes the command against the database', () => {
393+
const result = serviceProvider.runCursorCommand('admin', { checkMetadataConsistency: 1 });
394+
expect(result).to.deep.equal(commandResult);
395+
expect(dbStub.runCursorCommand).to.have.been.calledWith({ checkMetadataConsistency: 1 });
396+
});
397+
});
398+
373399
describe('#updateOne', () => {
374400
const filter = { name: 'Aphex Twin' };
375401
const update = { $set: { name: 'Richard James' } };

packages/service-provider-server/src/cli-service-provider.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import {
77
ReadPreferenceFromOptions,
88
ReadPreferenceLike,
99
OperationOptions,
10-
BSON
10+
BSON,
11+
RunCommandCursor,
12+
RunCursorCommandOptions
1113
} from 'mongodb';
1214

1315
import {
@@ -830,6 +832,28 @@ class CliServiceProvider extends ServiceProviderCore implements ServiceProvider
830832
return result as { ok: 1 };
831833
}
832834

835+
/**
836+
* Run a command against the database that returns a cursor.
837+
*
838+
* @param {String} database - The database name.
839+
* @param {Object} spec - The command specification.
840+
* @param {Object} options - The command options.
841+
* @param {Object} dbOptions - The connection-wide database options.
842+
*/
843+
runCursorCommand(
844+
database: string,
845+
spec: Document = {},
846+
options: RunCursorCommandOptions = {},
847+
dbOptions?: DbOptions
848+
): RunCommandCursor {
849+
options = { ...this.baseCmdOptions, ...options };
850+
const db = this.db(database, dbOptions);
851+
return db.runCursorCommand(
852+
spec,
853+
options
854+
);
855+
}
856+
833857
/**
834858
* list databases.
835859
*

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

Lines changed: 3 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,20 @@ import {
44
returnType,
55
ShellApiWithMongoClass,
66
returnsPromise,
7-
apiVersions
87
} from './decorators';
98
import type Mongo from './mongo';
109
import type {
1110
Document,
12-
ExplainVerbosityLike,
1311
FindCursor as ServiceProviderCursor,
1412
AggregationCursor as ServiceProviderAggregationCursor,
13+
RunCommandCursor as ServiceProviderRunCommandCursor
1514
} from '@mongosh/service-provider-core';
1615
import { asPrintable } from './enums';
1716
import { CursorIterationResult } from './result';
18-
import { iterate, validateExplainableVerbosity, markAsExplainOutput } from './helpers';
17+
import { iterate } from './helpers';
1918

2019
@shellApiClassNoHelp
21-
export abstract class AbstractCursor<CursorType extends ServiceProviderAggregationCursor | ServiceProviderCursor> extends ShellApiWithMongoClass {
20+
export abstract class AbstractCursor<CursorType extends ServiceProviderAggregationCursor | ServiceProviderCursor | ServiceProviderRunCommandCursor> extends ShellApiWithMongoClass {
2221
_mongo: Mongo;
2322
_cursor: CursorType;
2423
_transform: ((doc: any) => any) | null;
@@ -147,57 +146,7 @@ export abstract class AbstractCursor<CursorType extends ServiceProviderAggregati
147146
return result;
148147
}
149148

150-
@returnType('this')
151-
projection(spec: Document): this {
152-
this._cursor.project(spec);
153-
return this;
154-
}
155-
156-
@returnType('this')
157-
skip(value: number): this {
158-
this._cursor.skip(value);
159-
return this;
160-
}
161-
162-
@returnType('this')
163-
sort(spec: Document): this {
164-
this._cursor.sort(spec);
165-
return this;
166-
}
167-
168149
objsLeftInBatch(): number {
169150
return this._cursor.bufferedCount();
170151
}
171-
172-
@returnsPromise
173-
@apiVersions([1])
174-
async explain(verbosity?: ExplainVerbosityLike): Promise<any> {
175-
// TODO: @maurizio we should probably move this in the Explain class?
176-
// NOTE: the node driver always returns the full explain plan
177-
// for Cursor and the queryPlanner explain for AggregationCursor.
178-
if (verbosity !== undefined) {
179-
verbosity = validateExplainableVerbosity(verbosity);
180-
}
181-
const fullExplain: any = await this._cursor.explain(verbosity);
182-
183-
const explain: any = {
184-
...fullExplain
185-
};
186-
187-
if (
188-
verbosity !== 'executionStats' &&
189-
verbosity !== 'allPlansExecution' &&
190-
explain.executionStats
191-
) {
192-
delete explain.executionStats;
193-
}
194-
195-
if (verbosity === 'executionStats' &&
196-
explain.executionStats &&
197-
explain.executionStats.allPlansExecution) {
198-
delete explain.executionStats.allPlansExecution;
199-
}
200-
201-
return markAsExplainOutput(explain);
202-
}
203152
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {
2+
shellApiClassNoHelp,
3+
returnType,
4+
returnsPromise,
5+
apiVersions
6+
} from './decorators';
7+
import type {
8+
Document,
9+
ExplainVerbosityLike,
10+
FindCursor as ServiceProviderCursor,
11+
AggregationCursor as ServiceProviderAggregationCursor,
12+
} from '@mongosh/service-provider-core';
13+
import { validateExplainableVerbosity, markAsExplainOutput } from './helpers';
14+
import { AbstractCursor } from './abstract-cursor';
15+
16+
@shellApiClassNoHelp
17+
export abstract class AggregateOrFindCursor<CursorType extends ServiceProviderAggregationCursor | ServiceProviderCursor> extends AbstractCursor<CursorType> {
18+
@returnType('this')
19+
projection(spec: Document): this {
20+
this._cursor.project(spec);
21+
return this;
22+
}
23+
24+
@returnType('this')
25+
skip(value: number): this {
26+
this._cursor.skip(value);
27+
return this;
28+
}
29+
30+
@returnType('this')
31+
sort(spec: Document): this {
32+
this._cursor.sort(spec);
33+
return this;
34+
}
35+
36+
@returnsPromise
37+
@apiVersions([1])
38+
async explain(verbosity?: ExplainVerbosityLike): Promise<any> {
39+
// TODO: @maurizio we should probably move this in the Explain class?
40+
// NOTE: the node driver always returns the full explain plan
41+
// for Cursor and the queryPlanner explain for AggregationCursor.
42+
if (verbosity !== undefined) {
43+
verbosity = validateExplainableVerbosity(verbosity);
44+
}
45+
const fullExplain: any = await this._cursor.explain(verbosity);
46+
47+
const explain: any = {
48+
...fullExplain
49+
};
50+
51+
if (
52+
verbosity !== 'executionStats' &&
53+
verbosity !== 'allPlansExecution' &&
54+
explain.executionStats
55+
) {
56+
delete explain.executionStats;
57+
}
58+
59+
if (verbosity === 'executionStats' &&
60+
explain.executionStats &&
61+
explain.executionStats.allPlansExecution) {
62+
delete explain.executionStats.allPlansExecution;
63+
}
64+
65+
return markAsExplainOutput(explain);
66+
}
67+
}

0 commit comments

Comments
 (0)