Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/i18n/src/locales/en_US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ const translations: Catalog = {
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.',
example: 'db.collection.aggregate(pipeline, options).map(function)'
},
maxTimeMS: {
link: 'https://docs.mongodb.com/manual/reference/method/cursor.maxTimeMS',
description: 'Specifies a cumulative time limit in milliseconds for processing operations on a cursor.',
example: 'db.collection.aggregate(pipeline, options).maxTimeMS(timeLimit)'
},
next: {
link: 'https://docs.mongodb.com/manual/reference/method/cursor.next',
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',
Expand Down Expand Up @@ -235,6 +240,21 @@ const translations: Catalog = {
batchSize: {
description: 'Specifies the number of documents that mongosh displays at once.',
example: 'db.collection.aggregate(pipeline, options).batchSize(10)'
},
projection: {
link: '',
description: 'Sets a field projection for the query.',
example: 'db.collection.aggregate(pipeline, options).projection(field)'
},
sort: {
link: 'https://docs.mongodb.com/manual/reference/method/cursor.sort',
description: 'Specifies the order in which the query returns matching documents. You must apply sort() to the cursor before retrieving any documents from the database.',
example: 'db.collection.aggregate(pipeline, options).sort(sortDocument)'
},
skip: {
link: 'https://docs.mongodb.com/manual/reference/method/cursor.skip',
description: 'Call the cursor.skip() method on a cursor to control where MongoDB begins returning results. This approach may be useful in implementing paginated results.',
example: 'db.collection.aggregate(pipeline, options).skip(offsetNumber)'
}
}
}
Expand Down
176 changes: 176 additions & 0 deletions packages/shell-api/src/abstract-cursor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import {
shellApiClassNoHelp,
hasAsyncChild,
ShellApiClass,
returnsPromise,
toShellResult,
returnType
} from './decorators';
import type Mongo from './mongo';
import type {
Document,
ExplainVerbosityLike,
FindCursor as ServiceProviderCursor,
AggregationCursor as ServiceProviderAggregationCursor,
} from '@mongosh/service-provider-core';
import { asPrintable } from './enums';
import { CursorIterationResult } from './result';
import { iterate, validateExplainableVerbosity, markAsExplainOutput } from './helpers';

@shellApiClassNoHelp
@hasAsyncChild
export abstract class AbstractCursor extends ShellApiClass {
_mongo: Mongo;
abstract _cursor: ServiceProviderAggregationCursor | ServiceProviderCursor;
_currentIterationResult: CursorIterationResult | null = null;
_batchSize: number | null = null;

constructor(mongo: Mongo) {
super();
this._mongo = mongo;
}

/**
* Internal method to determine what is printed for this class.
*/
async [asPrintable](): Promise<CursorIterationResult> {
return (await toShellResult(this._currentIterationResult ?? await this._it())).printable;
}

async _it(): Promise<CursorIterationResult> {
const results = this._currentIterationResult = new CursorIterationResult();
await iterate(results, this._cursor, this._batchSize ?? await this._mongo._batchSize());
results.cursorHasMore = !this.isExhausted();
return results;
}

@returnType('this')
batchSize(size: number): this {
this._batchSize = size;
return this;
}

@returnsPromise
async close(options: Document): Promise<void> {
await this._cursor.close(options);
}

@returnsPromise
async forEach(f: (doc: Document) => void): Promise<void> {
return this._cursor.forEach(f);
}

@returnsPromise
async hasNext(): Promise<boolean> {
return this._cursor.hasNext();
}

@returnsPromise
async tryNext(): Promise<Document | null> {
return this._cursor.tryNext();
}

async* [Symbol.asyncIterator]() {
let doc;
while ((doc = await this.tryNext()) !== null) {
yield doc;
}
}

isClosed(): boolean {
return this._cursor.closed;
}

isExhausted(): boolean {
return this.isClosed() && this.objsLeftInBatch() === 0;
}

@returnsPromise
async itcount(): Promise<number> {
let count = 0;
while (await this.tryNext()) {
count++;
}
return count;
}

@returnsPromise
async toArray(): Promise<Document[]> {
return this._cursor.toArray();
}

@returnType('this')
pretty(): this {
return this;
}

@returnType('this')
map(f: (doc: Document) => Document): this {
this._cursor.map(f);
return this;
}

@returnType('this')
maxTimeMS(value: number): this {
this._cursor.maxTimeMS(value);
return this;
}

@returnsPromise
async next(): Promise<Document | null> {
return this._cursor.next();
}

@returnType('this')
projection(spec: Document): this {
this._cursor.project(spec);
return this;
}

@returnType('this')
skip(value: number): this {
this._cursor.skip(value);
return this;
}

@returnType('this')
sort(spec: Document): this {
this._cursor.sort(spec);
return this;
}

objsLeftInBatch(): number {
return this._cursor.bufferedCount();
}

@returnsPromise
async explain(verbosity?: ExplainVerbosityLike): Promise<any> {
// TODO: @maurizio we should probably move this in the Explain class?
// NOTE: the node driver always returns the full explain plan
// for Cursor and the queryPlanner explain for AggregationCursor.
if (verbosity !== undefined) {
verbosity = validateExplainableVerbosity(verbosity);
}
const fullExplain: any = await this._cursor.explain(verbosity);

const explain: any = {
...fullExplain
};

if (
verbosity !== 'executionStats' &&
verbosity !== 'allPlansExecution' &&
explain.executionStats
) {
delete explain.executionStats;
}

if (verbosity === 'executionStats' &&
explain.executionStats &&
explain.executionStats.allPlansExecution) {
delete explain.executionStats.allPlansExecution;
}

return markAsExplainOutput(explain);
}
}
2 changes: 1 addition & 1 deletion packages/shell-api/src/aggregation-cursor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ describe('AggregationCursor', () => {

it('returns an ExplainOutput object', async() => {
const explained = await shellApiCursor.explain();
expect(spCursor.explain).to.have.been.calledWith('queryPlanner');
expect(spCursor.explain).to.have.been.calledWith();
expect((await toShellResult(explained)).type).to.equal('ExplainOutput');
expect((await toShellResult(explained)).printable).to.deep.equal({ ok: 1 });
});
Expand Down
119 changes: 6 additions & 113 deletions packages/shell-api/src/aggregation-cursor.ts
Original file line number Diff line number Diff line change
@@ -1,127 +1,20 @@
import Mongo from './mongo';
import type Mongo from './mongo';
import {
shellApiClassDefault,
returnsPromise,
returnType,
hasAsyncChild,
ShellApiClass,
toShellResult
hasAsyncChild
} from './decorators';
import type {
AggregationCursor as ServiceProviderAggregationCursor,
ExplainVerbosityLike,
Document
AggregationCursor as ServiceProviderAggregationCursor
} from '@mongosh/service-provider-core';
import { CursorIterationResult } from './result';
import { asPrintable } from './enums';
import { iterate, validateExplainableVerbosity, markAsExplainOutput } from './helpers';
import { AbstractCursor } from './abstract-cursor';

@shellApiClassDefault
@hasAsyncChild
export default class AggregationCursor extends ShellApiClass {
_mongo: Mongo;
export default class AggregationCursor extends AbstractCursor {
_cursor: ServiceProviderAggregationCursor;
_currentIterationResult: CursorIterationResult | null = null;
_batchSize: number | null = null;

constructor(mongo: Mongo, cursor: ServiceProviderAggregationCursor) {
super();
super(mongo);
this._cursor = cursor;
this._mongo = mongo;
}

async _it(): Promise<CursorIterationResult> {
const results = this._currentIterationResult = new CursorIterationResult();
await iterate(results, this._cursor, this._batchSize ?? await this._mongo._batchSize());
results.cursorHasMore = !this.isExhausted();
return results;
}

/**
* Internal method to determine what is printed for this class.
*/
async [asPrintable](): Promise<CursorIterationResult> {
return (await toShellResult(this._currentIterationResult ?? await this._it())).printable;
}

@returnsPromise
async close(options: Document): Promise<void> {
await this._cursor.close(options);
}

@returnsPromise
async forEach(f: (doc: Document) => void): Promise<void> {
return this._cursor.forEach(f);
}

@returnsPromise
async hasNext(): Promise<boolean> {
return this._cursor.hasNext();
}

@returnsPromise
async tryNext(): Promise<Document | null> {
return this._cursor.tryNext();
}

async* [Symbol.asyncIterator]() {
let doc;
while ((doc = await this.tryNext()) !== null) {
yield doc;
}
}

isClosed(): boolean {
return this._cursor.closed;
}

isExhausted(): boolean {
return this.isClosed() && this.objsLeftInBatch() === 0;
}

objsLeftInBatch(): number {
return this._cursor.bufferedCount();
}

@returnsPromise
async itcount(): Promise<number> {
let count = 0;
while (await this.tryNext()) {
count++;
}
return count;
}

@returnType('AggregationCursor')
map(f: (doc: Document) => Document): AggregationCursor {
this._cursor.map(f);
return this;
}

@returnsPromise
async next(): Promise<Document | null> {
return this._cursor.next();
}

@returnsPromise
async toArray(): Promise<Document[]> {
return this._cursor.toArray();
}

@returnsPromise
async explain(verbosity: ExplainVerbosityLike = 'queryPlanner'): Promise<any> {
verbosity = validateExplainableVerbosity(verbosity);
return markAsExplainOutput(await this._cursor.explain(verbosity));
}

@returnType('AggregationCursor')
pretty(): AggregationCursor {
return this;
}

@returnType('AggregationCursor')
batchSize(size: number): AggregationCursor {
this._batchSize = size;
return this;
}
}
2 changes: 1 addition & 1 deletion packages/shell-api/src/collection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ describe('Collection', () => {
{ explain: true }
);

expect(explainResult).to.equal(expectedExplainResult);
expect(explainResult).to.deep.equal(expectedExplainResult);
expect((await toShellResult(explainResult)).type).to.equal('ExplainOutput');
expect(serviceProviderCursor.explain).to.have.been.calledOnce;
});
Expand Down
Loading