Skip to content

Commit 631f982

Browse files
committed
add cursor initialization checks
1 parent 83402b5 commit 631f982

File tree

4 files changed

+60
-108
lines changed

4 files changed

+60
-108
lines changed

src/cursor/abstract_cursor.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const kTopology = Symbol('topology');
1717
const kSession = Symbol('session');
1818
const kOptions = Symbol('options');
1919
const kTransform = Symbol('transform');
20+
const kInitialized = Symbol('iniitalized');
2021
const kClosed = Symbol('closed');
2122
const kKilled = Symbol('killed');
2223

@@ -77,6 +78,7 @@ export abstract class AbstractCursor extends EventEmitter {
7778
[kDocuments]: Document[];
7879
[kTopology]: Topology;
7980
[kTransform]?: (doc: Document) => Document;
81+
[kInitialized]: boolean;
8082
[kClosed]: boolean;
8183
[kKilled]: boolean;
8284
[kOptions]: InternalAbstractCursorOptions;
@@ -94,6 +96,7 @@ export abstract class AbstractCursor extends EventEmitter {
9496
this[kTopology] = topology;
9597
this[kNamespace] = namespace;
9698
this[kDocuments] = []; // TODO: https://github.com/microsoft/TypeScript/issues/36230
99+
this[kInitialized] = false;
97100
this[kClosed] = false;
98101
this[kKilled] = false;
99102
this[kOptions] = {
@@ -386,6 +389,7 @@ export abstract class AbstractCursor extends EventEmitter {
386389
* @param value - The flag boolean value.
387390
*/
388391
addCursorFlag(flag: CursorFlag, value: boolean): this {
392+
assertUninitialized(this);
389393
if (!CURSOR_FLAGS.includes(flag)) {
390394
throw new MongoError(`flag ${flag} is not one of ${CURSOR_FLAGS}`);
391395
}
@@ -404,6 +408,7 @@ export abstract class AbstractCursor extends EventEmitter {
404408
* @param transform - The mapping transformation method.
405409
*/
406410
map(transform: (doc: Document) => Document): this {
411+
assertUninitialized(this);
407412
const oldTransform = this[kTransform];
408413
if (oldTransform) {
409414
this[kTransform] = doc => {
@@ -422,6 +427,7 @@ export abstract class AbstractCursor extends EventEmitter {
422427
* @param readPreference - The new read preference for the cursor.
423428
*/
424429
setReadPreference(readPreference: ReadPreferenceLike): this {
430+
assertUninitialized(this);
425431
if (readPreference instanceof ReadPreference) {
426432
this[kOptions].readPreference = readPreference;
427433
} else if (typeof readPreference === 'string') {
@@ -439,6 +445,7 @@ export abstract class AbstractCursor extends EventEmitter {
439445
* @param value - Number of milliseconds to wait before aborting the query.
440446
*/
441447
maxTimeMS(value: number): this {
448+
assertUninitialized(this);
442449
if (typeof value !== 'number') {
443450
throw new TypeError('maxTimeMS must be a number');
444451
}
@@ -453,6 +460,7 @@ export abstract class AbstractCursor extends EventEmitter {
453460
* @param value - The number of documents to return per batch. See {@link https://docs.mongodb.com/manual/reference/command/find/|find command documentation}.
454461
*/
455462
batchSize(value: number): this {
463+
assertUninitialized(this);
456464
if (this[kOptions].tailable) {
457465
throw new MongoError('Tailable cursors do not support batchSize');
458466
}
@@ -568,6 +576,9 @@ function next(cursor: AbstractCursor, callback: Callback<Document | null>): void
568576
}
569577
}
570578

579+
// the cursor is now initialized, even if an error occurred or it is dead
580+
cursor[kInitialized] = true;
581+
571582
if (err || cursorIsDead(cursor)) {
572583
return cleanupCursor(cursor, () => callback(err, nextDocument(cursor)));
573584
}
@@ -622,6 +633,13 @@ function cleanupCursor(cursor: AbstractCursor, callback: Callback): void {
622633
}
623634
}
624635

636+
/** @internal */
637+
export function assertUninitialized(cursor: AbstractCursor): void {
638+
if (cursor[kInitialized]) {
639+
throw new MongoError('Cursor is already initialized');
640+
}
641+
}
642+
625643
function makeCursorStream(cursor: AbstractCursor) {
626644
const readable = new Readable({
627645
objectMode: true,

src/cursor/aggregation_cursor.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { AggregateOperation, AggregateOptions } from '../operations/aggregate';
2-
import { AbstractCursor } from './abstract_cursor';
2+
import { AbstractCursor, assertUninitialized } from './abstract_cursor';
33
import { executeOperation, ExecutionResult } from '../operations/execute_operation';
44
import type { Document } from '../bson';
55
import type { Sort } from '../sort';
@@ -80,68 +80,79 @@ export class AggregationCursor extends AbstractCursor {
8080

8181
/** Add a group stage to the aggregation pipeline */
8282
group($group: Document): this {
83-
this.pipeline.push({ $group });
83+
assertUninitialized(this);
84+
this[kPipeline].push({ $group });
8485
return this;
8586
}
8687

8788
/** Add a limit stage to the aggregation pipeline */
8889
limit($limit: number): this {
89-
this.pipeline.push({ $limit });
90+
assertUninitialized(this);
91+
this[kPipeline].push({ $limit });
9092
return this;
9193
}
9294

9395
/** Add a match stage to the aggregation pipeline */
9496
match($match: Document): this {
95-
this.pipeline.push({ $match });
97+
assertUninitialized(this);
98+
this[kPipeline].push({ $match });
9699
return this;
97100
}
98101

99102
/** Add a out stage to the aggregation pipeline */
100103
out($out: number): this {
101-
this.pipeline.push({ $out });
104+
assertUninitialized(this);
105+
this[kPipeline].push({ $out });
102106
return this;
103107
}
104108

105109
/** Add a project stage to the aggregation pipeline */
106110
project($project: Document): this {
107-
this.pipeline.push({ $project });
111+
assertUninitialized(this);
112+
this[kPipeline].push({ $project });
108113
return this;
109114
}
110115

111116
/** Add a lookup stage to the aggregation pipeline */
112117
lookup($lookup: Document): this {
113-
this.pipeline.push({ $lookup });
118+
assertUninitialized(this);
119+
this[kPipeline].push({ $lookup });
114120
return this;
115121
}
116122

117123
/** Add a redact stage to the aggregation pipeline */
118124
redact($redact: Document): this {
119-
this.pipeline.push({ $redact });
125+
assertUninitialized(this);
126+
this[kPipeline].push({ $redact });
120127
return this;
121128
}
122129

123130
/** Add a skip stage to the aggregation pipeline */
124131
skip($skip: number): this {
125-
this.pipeline.push({ $skip });
132+
assertUninitialized(this);
133+
this[kPipeline].push({ $skip });
126134
return this;
127135
}
128136

129137
/** Add a sort stage to the aggregation pipeline */
130138
sort($sort: Sort): this {
131-
this.pipeline.push({ $sort });
139+
assertUninitialized(this);
140+
this[kPipeline].push({ $sort });
132141
return this;
133142
}
134143

135144
/** Add a unwind stage to the aggregation pipeline */
136145
unwind($unwind: number): this {
137-
this.pipeline.push({ $unwind });
146+
assertUninitialized(this);
147+
this[kPipeline].push({ $unwind });
138148
return this;
139149
}
140150

141151
// deprecated methods
142152
/** @deprecated Add a geoNear stage to the aggregation pipeline */
143153
geoNear($geoNear: Document): this {
144-
this.pipeline.push({ $geoNear });
154+
assertUninitialized(this);
155+
this[kPipeline].push({ $geoNear });
145156
return this;
146157
}
147158
}

src/cursor/find_cursor.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { Topology } from '../sdam/topology';
99
import type { ClientSession } from '../sessions';
1010
import { formatSort, Sort, SortDirection } from '../sort';
1111
import type { Callback, MongoDBNamespace } from '../utils';
12-
import { AbstractCursor } from './abstract_cursor';
12+
import { AbstractCursor, assertUninitialized } from './abstract_cursor';
1313

1414
/** @internal */
1515
const kFilter = Symbol('filter');
@@ -50,7 +50,6 @@ export class FindCursor extends AbstractCursor {
5050

5151
/** @internal */
5252
_initialize(session: ClientSession | undefined, callback: Callback<ExecutionResult>): void {
53-
this[kBuiltOptions] = Object.freeze(this[kBuiltOptions]);
5453
const findOperation = new FindOperation(undefined, this.namespace, this[kFilter], {
5554
...this[kBuiltOptions], // NOTE: order matters here, we may need to refine this
5655
...this.cursorOptions,
@@ -143,6 +142,7 @@ export class FindCursor extends AbstractCursor {
143142

144143
/** Set the cursor query */
145144
filter(filter: Document): this {
145+
assertUninitialized(this);
146146
this[kFilter] = filter;
147147
return this;
148148
}
@@ -153,6 +153,7 @@ export class FindCursor extends AbstractCursor {
153153
* @param hint - If specified, then the query system will only consider plans using the hinted index.
154154
*/
155155
hint(hint: Hint): this {
156+
assertUninitialized(this);
156157
this[kBuiltOptions].hint = hint;
157158
return this;
158159
}
@@ -163,6 +164,7 @@ export class FindCursor extends AbstractCursor {
163164
* @param min - Specify a $min value to specify the inclusive lower bound for a specific index in order to constrain the results of find(). The $min specifies the lower bound for all keys of a specific index in order.
164165
*/
165166
min(min: number): this {
167+
assertUninitialized(this);
166168
this[kBuiltOptions].min = min;
167169
return this;
168170
}
@@ -173,6 +175,7 @@ export class FindCursor extends AbstractCursor {
173175
* @param max - Specify a $max value to specify the exclusive upper bound for a specific index in order to constrain the results of find(). The $max specifies the upper bound for all keys of a specific index in order.
174176
*/
175177
max(max: number): this {
178+
assertUninitialized(this);
176179
this[kBuiltOptions].max = max;
177180
return this;
178181
}
@@ -185,6 +188,7 @@ export class FindCursor extends AbstractCursor {
185188
* @param value - the returnKey value.
186189
*/
187190
returnKey(value: boolean): this {
191+
assertUninitialized(this);
188192
this[kBuiltOptions].returnKey = value;
189193
return this;
190194
}
@@ -195,6 +199,7 @@ export class FindCursor extends AbstractCursor {
195199
* @param value - The $showDiskLoc option has now been deprecated and replaced with the showRecordId field. $showDiskLoc will still be accepted for OP_QUERY stye find.
196200
*/
197201
showRecordId(value: boolean): this {
202+
assertUninitialized(this);
198203
this[kBuiltOptions].showRecordId = value;
199204
return this;
200205
}
@@ -206,6 +211,7 @@ export class FindCursor extends AbstractCursor {
206211
* @param value - The modifier value.
207212
*/
208213
addQueryModifier(name: string, value: string | boolean | number | Document): this {
214+
assertUninitialized(this);
209215
if (name[0] !== '$') {
210216
throw new MongoError(`${name} is not a valid query modifier`);
211217
}
@@ -268,6 +274,7 @@ export class FindCursor extends AbstractCursor {
268274
* @param value - The comment attached to this query.
269275
*/
270276
comment(value: string): this {
277+
assertUninitialized(this);
271278
this[kBuiltOptions].comment = value;
272279
return this;
273280
}
@@ -278,6 +285,7 @@ export class FindCursor extends AbstractCursor {
278285
* @param value - Number of milliseconds to wait before aborting the tailed query.
279286
*/
280287
maxAwaitTimeMS(value: number): this {
288+
assertUninitialized(this);
281289
if (typeof value !== 'number') {
282290
throw new MongoError('maxAwaitTimeMS must be a number');
283291
}
@@ -292,6 +300,7 @@ export class FindCursor extends AbstractCursor {
292300
* @param value - Number of milliseconds to wait before aborting the query.
293301
*/
294302
maxTimeMS(value: number): this {
303+
assertUninitialized(this);
295304
if (typeof value !== 'number') {
296305
throw new MongoError('maxTimeMS must be a number');
297306
}
@@ -306,6 +315,7 @@ export class FindCursor extends AbstractCursor {
306315
* @param value - The field projection object.
307316
*/
308317
project(value: Document): this {
318+
assertUninitialized(this);
309319
this[kBuiltOptions].projection = value;
310320
return this;
311321
}
@@ -317,6 +327,7 @@ export class FindCursor extends AbstractCursor {
317327
* @param direction - The direction of the sorting (1 or -1).
318328
*/
319329
sort(sort: Sort | string, direction?: SortDirection): this {
330+
assertUninitialized(this);
320331
if (this[kBuiltOptions].tailable) {
321332
throw new MongoError('Tailable cursor does not support sorting');
322333
}
@@ -331,6 +342,7 @@ export class FindCursor extends AbstractCursor {
331342
* @param value - The cursor collation options (MongoDB 3.4 or higher) settings for update operation (see 3.4 documentation for available fields).
332343
*/
333344
collation(value: CollationOptions): this {
345+
assertUninitialized(this);
334346
this[kBuiltOptions].collation = value;
335347
return this;
336348
}
@@ -341,12 +353,13 @@ export class FindCursor extends AbstractCursor {
341353
* @param value - The limit for the cursor query.
342354
*/
343355
limit(value: number): this {
356+
assertUninitialized(this);
344357
if (this[kBuiltOptions].tailable) {
345358
throw new MongoError('Tailable cursor does not support limit');
346359
}
347360

348361
if (typeof value !== 'number') {
349-
throw new MongoError('limit requires an integer');
362+
throw new TypeError('limit requires an integer');
350363
}
351364

352365
this[kBuiltOptions].limit = value;
@@ -359,12 +372,13 @@ export class FindCursor extends AbstractCursor {
359372
* @param value - The skip for the cursor query.
360373
*/
361374
skip(value: number): this {
375+
assertUninitialized(this);
362376
if (this[kBuiltOptions].tailable) {
363377
throw new MongoError('Tailable cursor does not support skip');
364378
}
365379

366380
if (typeof value !== 'number') {
367-
throw new MongoError('skip requires an integer');
381+
throw new TypeError('skip requires an integer');
368382
}
369383

370384
this[kBuiltOptions].skip = value;

0 commit comments

Comments
 (0)