mongodb 6.5.0-dev.20240424.sha.6abc074 → 6.5.0-dev.20240502.sha.9d73f45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cmap/connection.js +8 -4
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/wire_protocol/on_demand/document.js +27 -16
- package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
- package/lib/cmap/wire_protocol/responses.js +106 -10
- package/lib/cmap/wire_protocol/responses.js.map +1 -1
- package/lib/cursor/abstract_cursor.js +33 -27
- package/lib/cursor/abstract_cursor.js.map +1 -1
- package/lib/cursor/find_cursor.js +14 -8
- package/lib/cursor/find_cursor.js.map +1 -1
- package/lib/cursor/run_command_cursor.js +2 -1
- package/lib/cursor/run_command_cursor.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/operations/execute_operation.js.map +1 -1
- package/lib/operations/find.js +4 -3
- package/lib/operations/find.js.map +1 -1
- package/lib/operations/get_more.js +2 -1
- package/lib/operations/get_more.js.map +1 -1
- package/lib/sdam/server.js +3 -7
- package/lib/sdam/server.js.map +1 -1
- package/lib/utils.js +13 -2
- package/lib/utils.js.map +1 -1
- package/mongodb.d.ts +2 -0
- package/package.json +1 -1
- package/src/cmap/connection.ts +14 -5
- package/src/cmap/wire_protocol/on_demand/document.ts +36 -21
- package/src/cmap/wire_protocol/responses.ts +139 -11
- package/src/cursor/abstract_cursor.ts +61 -21
- package/src/cursor/find_cursor.ts +12 -9
- package/src/cursor/run_command_cursor.ts +2 -1
- package/src/index.ts +5 -1
- package/src/operations/execute_operation.ts +2 -1
- package/src/operations/find.ts +14 -14
- package/src/operations/get_more.ts +9 -1
- package/src/sdam/server.ts +22 -7
- package/src/utils.ts +13 -0
|
@@ -58,7 +58,7 @@ export class OnDemandDocument {
|
|
|
58
58
|
private readonly indexFound: Record<number, boolean> = Object.create(null);
|
|
59
59
|
|
|
60
60
|
/** All bson elements in this document */
|
|
61
|
-
private readonly elements: BSONElement
|
|
61
|
+
private readonly elements: ReadonlyArray<BSONElement>;
|
|
62
62
|
|
|
63
63
|
constructor(
|
|
64
64
|
/** BSON bytes, this document begins at offset */
|
|
@@ -97,7 +97,7 @@ export class OnDemandDocument {
|
|
|
97
97
|
* @param name - a basic latin string name of a BSON element
|
|
98
98
|
* @returns
|
|
99
99
|
*/
|
|
100
|
-
private getElement(name: string): CachedBSONElement | null {
|
|
100
|
+
private getElement(name: string | number): CachedBSONElement | null {
|
|
101
101
|
const cachedElement = this.cache[name];
|
|
102
102
|
if (cachedElement === false) return null;
|
|
103
103
|
|
|
@@ -105,6 +105,22 @@ export class OnDemandDocument {
|
|
|
105
105
|
return cachedElement;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
if (typeof name === 'number') {
|
|
109
|
+
if (this.isArray) {
|
|
110
|
+
if (name < this.elements.length) {
|
|
111
|
+
const element = this.elements[name];
|
|
112
|
+
const cachedElement = { element, value: undefined };
|
|
113
|
+
this.cache[name] = cachedElement;
|
|
114
|
+
this.indexFound[name] = true;
|
|
115
|
+
return cachedElement;
|
|
116
|
+
} else {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
108
124
|
for (let index = 0; index < this.elements.length; index++) {
|
|
109
125
|
const element = this.elements[index];
|
|
110
126
|
|
|
@@ -197,6 +213,13 @@ export class OnDemandDocument {
|
|
|
197
213
|
}
|
|
198
214
|
}
|
|
199
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Returns the number of elements in this BSON document
|
|
218
|
+
*/
|
|
219
|
+
public size() {
|
|
220
|
+
return this.elements.length;
|
|
221
|
+
}
|
|
222
|
+
|
|
200
223
|
/**
|
|
201
224
|
* Checks for the existence of an element by name.
|
|
202
225
|
*
|
|
@@ -222,16 +245,20 @@ export class OnDemandDocument {
|
|
|
222
245
|
* @param required - whether or not the element is expected to exist, if true this function will throw if it is not present
|
|
223
246
|
*/
|
|
224
247
|
public get<const T extends keyof JSTypeOf>(
|
|
225
|
-
name: string,
|
|
248
|
+
name: string | number,
|
|
226
249
|
as: T,
|
|
227
250
|
required?: false | undefined
|
|
228
251
|
): JSTypeOf[T] | null;
|
|
229
252
|
|
|
230
253
|
/** `required` will make `get` throw if name does not exist or is null/undefined */
|
|
231
|
-
public get<const T extends keyof JSTypeOf>(
|
|
254
|
+
public get<const T extends keyof JSTypeOf>(
|
|
255
|
+
name: string | number,
|
|
256
|
+
as: T,
|
|
257
|
+
required: true
|
|
258
|
+
): JSTypeOf[T];
|
|
232
259
|
|
|
233
260
|
public get<const T extends keyof JSTypeOf>(
|
|
234
|
-
name: string,
|
|
261
|
+
name: string | number,
|
|
235
262
|
as: T,
|
|
236
263
|
required?: boolean
|
|
237
264
|
): JSTypeOf[T] | null {
|
|
@@ -303,21 +330,9 @@ export class OnDemandDocument {
|
|
|
303
330
|
});
|
|
304
331
|
}
|
|
305
332
|
|
|
306
|
-
/**
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
*/
|
|
311
|
-
public *valuesAs<const T extends keyof JSTypeOf>(as: T): Generator<JSTypeOf[T]> {
|
|
312
|
-
if (!this.isArray) {
|
|
313
|
-
throw new BSONError('Unexpected conversion of non-array value to array');
|
|
314
|
-
}
|
|
315
|
-
let counter = 0;
|
|
316
|
-
for (const element of this.elements) {
|
|
317
|
-
const value = this.toJSValue<T>(element, as);
|
|
318
|
-
this.cache[counter] = { element, value };
|
|
319
|
-
yield value;
|
|
320
|
-
counter += 1;
|
|
321
|
-
}
|
|
333
|
+
/** Returns this document's bytes only */
|
|
334
|
+
toBytes() {
|
|
335
|
+
const size = getInt32LE(this.bson, this.offset);
|
|
336
|
+
return this.bson.subarray(this.offset, this.offset + size);
|
|
322
337
|
}
|
|
323
338
|
}
|
|
@@ -1,7 +1,62 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type BSONSerializeOptions,
|
|
3
|
+
BSONType,
|
|
4
|
+
type Document,
|
|
5
|
+
Long,
|
|
6
|
+
parseToElementsToArray,
|
|
7
|
+
type Timestamp
|
|
8
|
+
} from '../../bson';
|
|
9
|
+
import { MongoUnexpectedServerResponseError } from '../../error';
|
|
2
10
|
import { type ClusterTime } from '../../sdam/common';
|
|
11
|
+
import { type MongoDBNamespace, ns } from '../../utils';
|
|
3
12
|
import { OnDemandDocument } from './on_demand/document';
|
|
4
13
|
|
|
14
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
15
|
+
const enum BSONElementOffset {
|
|
16
|
+
type = 0,
|
|
17
|
+
nameOffset = 1,
|
|
18
|
+
nameLength = 2,
|
|
19
|
+
offset = 3,
|
|
20
|
+
length = 4
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Accepts a BSON payload and checks for na "ok: 0" element.
|
|
24
|
+
* This utility is intended to prevent calling response class constructors
|
|
25
|
+
* that expect the result to be a success and demand certain properties to exist.
|
|
26
|
+
*
|
|
27
|
+
* For example, a cursor response always expects a cursor embedded document.
|
|
28
|
+
* In order to write the class such that the properties reflect that assertion (non-null)
|
|
29
|
+
* we cannot invoke the subclass constructor if the BSON represents an error.
|
|
30
|
+
*
|
|
31
|
+
* @param bytes - BSON document returned from the server
|
|
32
|
+
*/
|
|
33
|
+
export function isErrorResponse(bson: Uint8Array): boolean {
|
|
34
|
+
const elements = parseToElementsToArray(bson, 0);
|
|
35
|
+
for (let eIdx = 0; eIdx < elements.length; eIdx++) {
|
|
36
|
+
const element = elements[eIdx];
|
|
37
|
+
|
|
38
|
+
if (element[BSONElementOffset.nameLength] === 2) {
|
|
39
|
+
const nameOffset = element[BSONElementOffset.nameOffset];
|
|
40
|
+
|
|
41
|
+
// 111 == "o", 107 == "k"
|
|
42
|
+
if (bson[nameOffset] === 111 && bson[nameOffset + 1] === 107) {
|
|
43
|
+
const valueOffset = element[BSONElementOffset.offset];
|
|
44
|
+
const valueLength = element[BSONElementOffset.length];
|
|
45
|
+
|
|
46
|
+
// If any byte in the length of the ok number (works for any type) is non zero,
|
|
47
|
+
// then it is considered "ok: 1"
|
|
48
|
+
for (let i = valueOffset; i < valueOffset + valueLength; i++) {
|
|
49
|
+
if (bson[i] !== 0x00) return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
5
60
|
/** @internal */
|
|
6
61
|
export type MongoDBResponseConstructor = {
|
|
7
62
|
new (bson: Uint8Array, offset?: number, isArray?: boolean): MongoDBResponse;
|
|
@@ -9,6 +64,10 @@ export type MongoDBResponseConstructor = {
|
|
|
9
64
|
|
|
10
65
|
/** @internal */
|
|
11
66
|
export class MongoDBResponse extends OnDemandDocument {
|
|
67
|
+
static is(value: unknown): value is MongoDBResponse {
|
|
68
|
+
return value instanceof MongoDBResponse;
|
|
69
|
+
}
|
|
70
|
+
|
|
12
71
|
// {ok:1}
|
|
13
72
|
static empty = new MongoDBResponse(new Uint8Array([13, 0, 0, 0, 16, 111, 107, 0, 1, 0, 0, 0, 0]));
|
|
14
73
|
|
|
@@ -83,27 +142,96 @@ export class MongoDBResponse extends OnDemandDocument {
|
|
|
83
142
|
return this.clusterTime ?? null;
|
|
84
143
|
}
|
|
85
144
|
|
|
86
|
-
public override toObject(options
|
|
145
|
+
public override toObject(options?: BSONSerializeOptions): Record<string, any> {
|
|
87
146
|
const exactBSONOptions = {
|
|
88
|
-
useBigInt64: options
|
|
89
|
-
promoteLongs: options
|
|
90
|
-
promoteValues: options
|
|
91
|
-
promoteBuffers: options
|
|
92
|
-
bsonRegExp: options
|
|
93
|
-
raw: options
|
|
94
|
-
fieldsAsRaw: options
|
|
147
|
+
useBigInt64: options?.useBigInt64,
|
|
148
|
+
promoteLongs: options?.promoteLongs,
|
|
149
|
+
promoteValues: options?.promoteValues,
|
|
150
|
+
promoteBuffers: options?.promoteBuffers,
|
|
151
|
+
bsonRegExp: options?.bsonRegExp,
|
|
152
|
+
raw: options?.raw ?? false,
|
|
153
|
+
fieldsAsRaw: options?.fieldsAsRaw ?? {},
|
|
95
154
|
validation: this.parseBsonSerializationOptions(options)
|
|
96
155
|
};
|
|
97
156
|
return super.toObject(exactBSONOptions);
|
|
98
157
|
}
|
|
99
158
|
|
|
100
|
-
private parseBsonSerializationOptions({ enableUtf8Validation }
|
|
159
|
+
private parseBsonSerializationOptions(options?: { enableUtf8Validation?: boolean }): {
|
|
101
160
|
utf8: { writeErrors: false } | false;
|
|
102
161
|
} {
|
|
162
|
+
const enableUtf8Validation = options?.enableUtf8Validation;
|
|
103
163
|
if (enableUtf8Validation === false) {
|
|
104
164
|
return { utf8: false };
|
|
105
165
|
}
|
|
106
|
-
|
|
107
166
|
return { utf8: { writeErrors: false } };
|
|
108
167
|
}
|
|
109
168
|
}
|
|
169
|
+
|
|
170
|
+
/** @internal */
|
|
171
|
+
export class CursorResponse extends MongoDBResponse {
|
|
172
|
+
/**
|
|
173
|
+
* This supports a feature of the FindCursor.
|
|
174
|
+
* It is an optimization to avoid an extra getMore when the limit has been reached
|
|
175
|
+
*/
|
|
176
|
+
static emptyGetMore = { id: new Long(0), length: 0, shift: () => null };
|
|
177
|
+
|
|
178
|
+
static override is(value: unknown): value is CursorResponse {
|
|
179
|
+
return value instanceof CursorResponse || value === CursorResponse.emptyGetMore;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public id: Long;
|
|
183
|
+
public ns: MongoDBNamespace | null = null;
|
|
184
|
+
public batchSize = 0;
|
|
185
|
+
|
|
186
|
+
private batch: OnDemandDocument;
|
|
187
|
+
private iterated = 0;
|
|
188
|
+
|
|
189
|
+
constructor(bytes: Uint8Array, offset?: number, isArray?: boolean) {
|
|
190
|
+
super(bytes, offset, isArray);
|
|
191
|
+
|
|
192
|
+
const cursor = this.get('cursor', BSONType.object, true);
|
|
193
|
+
|
|
194
|
+
const id = cursor.get('id', BSONType.long, true);
|
|
195
|
+
this.id = new Long(Number(id & 0xffff_ffffn), Number((id >> 32n) & 0xffff_ffffn));
|
|
196
|
+
|
|
197
|
+
const namespace = cursor.get('ns', BSONType.string);
|
|
198
|
+
if (namespace != null) this.ns = ns(namespace);
|
|
199
|
+
|
|
200
|
+
if (cursor.has('firstBatch')) this.batch = cursor.get('firstBatch', BSONType.array, true);
|
|
201
|
+
else if (cursor.has('nextBatch')) this.batch = cursor.get('nextBatch', BSONType.array, true);
|
|
202
|
+
else throw new MongoUnexpectedServerResponseError('Cursor document did not contain a batch');
|
|
203
|
+
|
|
204
|
+
this.batchSize = this.batch.size();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
get length() {
|
|
208
|
+
return Math.max(this.batchSize - this.iterated, 0);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
shift(options?: BSONSerializeOptions): any {
|
|
212
|
+
if (this.iterated >= this.batchSize) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const result = this.batch.get(this.iterated, BSONType.object, true) ?? null;
|
|
217
|
+
this.iterated += 1;
|
|
218
|
+
|
|
219
|
+
if (options?.raw) {
|
|
220
|
+
return result.toBytes();
|
|
221
|
+
} else {
|
|
222
|
+
return result.toObject(options);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
clear() {
|
|
227
|
+
this.iterated = this.batchSize;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
pushMany() {
|
|
231
|
+
throw new Error('pushMany Unsupported method');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
push() {
|
|
235
|
+
throw new Error('push Unsupported method');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Readable, Transform } from 'stream';
|
|
2
2
|
|
|
3
3
|
import { type BSONSerializeOptions, type Document, Long, pluckBSONSerializeOptions } from '../bson';
|
|
4
|
+
import { CursorResponse } from '../cmap/wire_protocol/responses';
|
|
4
5
|
import {
|
|
5
6
|
type AnyError,
|
|
6
7
|
MongoAPIError,
|
|
@@ -144,7 +145,13 @@ export abstract class AbstractCursor<
|
|
|
144
145
|
/** @internal */
|
|
145
146
|
[kNamespace]: MongoDBNamespace;
|
|
146
147
|
/** @internal */
|
|
147
|
-
[kDocuments]:
|
|
148
|
+
[kDocuments]: {
|
|
149
|
+
length: number;
|
|
150
|
+
shift(bsonOptions?: any): TSchema | null;
|
|
151
|
+
clear(): void;
|
|
152
|
+
pushMany(many: Iterable<TSchema>): void;
|
|
153
|
+
push(item: TSchema): void;
|
|
154
|
+
};
|
|
148
155
|
/** @internal */
|
|
149
156
|
[kClient]: MongoClient;
|
|
150
157
|
/** @internal */
|
|
@@ -286,7 +293,7 @@ export abstract class AbstractCursor<
|
|
|
286
293
|
const documentsToRead = Math.min(number ?? this[kDocuments].length, this[kDocuments].length);
|
|
287
294
|
|
|
288
295
|
for (let count = 0; count < documentsToRead; count++) {
|
|
289
|
-
const document = this[kDocuments].shift();
|
|
296
|
+
const document = this[kDocuments].shift(this[kOptions]);
|
|
290
297
|
if (document != null) {
|
|
291
298
|
bufferedDocs.push(document);
|
|
292
299
|
}
|
|
@@ -382,14 +389,7 @@ export abstract class AbstractCursor<
|
|
|
382
389
|
return true;
|
|
383
390
|
}
|
|
384
391
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (doc) {
|
|
388
|
-
this[kDocuments].unshift(doc);
|
|
389
|
-
return true;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
return false;
|
|
392
|
+
return await next(this, { blocking: true, transform: false, shift: false });
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
/** Get the next available document from the cursor, returns null if no more documents are available. */
|
|
@@ -398,7 +398,7 @@ export abstract class AbstractCursor<
|
|
|
398
398
|
throw new MongoCursorExhaustedError();
|
|
399
399
|
}
|
|
400
400
|
|
|
401
|
-
return await next(this, { blocking: true, transform: true });
|
|
401
|
+
return await next(this, { blocking: true, transform: true, shift: true });
|
|
402
402
|
}
|
|
403
403
|
|
|
404
404
|
/**
|
|
@@ -409,7 +409,7 @@ export abstract class AbstractCursor<
|
|
|
409
409
|
throw new MongoCursorExhaustedError();
|
|
410
410
|
}
|
|
411
411
|
|
|
412
|
-
return await next(this, { blocking: false, transform: true });
|
|
412
|
+
return await next(this, { blocking: false, transform: true, shift: true });
|
|
413
413
|
}
|
|
414
414
|
|
|
415
415
|
/**
|
|
@@ -633,12 +633,13 @@ export abstract class AbstractCursor<
|
|
|
633
633
|
protected abstract _initialize(session: ClientSession | undefined): Promise<ExecutionResult>;
|
|
634
634
|
|
|
635
635
|
/** @internal */
|
|
636
|
-
async getMore(batchSize: number): Promise<Document | null> {
|
|
636
|
+
async getMore(batchSize: number, useCursorResponse = false): Promise<Document | null> {
|
|
637
637
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
638
638
|
const getMoreOperation = new GetMoreOperation(this[kNamespace], this[kId]!, this[kServer]!, {
|
|
639
639
|
...this[kOptions],
|
|
640
640
|
session: this[kSession],
|
|
641
|
-
batchSize
|
|
641
|
+
batchSize,
|
|
642
|
+
useCursorResponse
|
|
642
643
|
});
|
|
643
644
|
|
|
644
645
|
return await executeOperation(this[kClient], getMoreOperation);
|
|
@@ -656,7 +657,11 @@ export abstract class AbstractCursor<
|
|
|
656
657
|
const state = await this._initialize(this[kSession]);
|
|
657
658
|
const response = state.response;
|
|
658
659
|
this[kServer] = state.server;
|
|
659
|
-
if (response
|
|
660
|
+
if (CursorResponse.is(response)) {
|
|
661
|
+
this[kId] = response.id;
|
|
662
|
+
if (response.ns) this[kNamespace] = response.ns;
|
|
663
|
+
this[kDocuments] = response;
|
|
664
|
+
} else if (response.cursor) {
|
|
660
665
|
// TODO(NODE-2674): Preserve int64 sent from MongoDB
|
|
661
666
|
this[kId] =
|
|
662
667
|
typeof response.cursor.id === 'number'
|
|
@@ -713,13 +718,42 @@ async function next<T>(
|
|
|
713
718
|
cursor: AbstractCursor<T>,
|
|
714
719
|
{
|
|
715
720
|
blocking,
|
|
716
|
-
transform
|
|
721
|
+
transform,
|
|
722
|
+
shift
|
|
717
723
|
}: {
|
|
718
724
|
blocking: boolean;
|
|
719
725
|
transform: boolean;
|
|
726
|
+
shift: false;
|
|
720
727
|
}
|
|
721
|
-
): Promise<
|
|
728
|
+
): Promise<boolean>;
|
|
729
|
+
|
|
730
|
+
async function next<T>(
|
|
731
|
+
cursor: AbstractCursor<T>,
|
|
732
|
+
{
|
|
733
|
+
blocking,
|
|
734
|
+
transform,
|
|
735
|
+
shift
|
|
736
|
+
}: {
|
|
737
|
+
blocking: boolean;
|
|
738
|
+
transform: boolean;
|
|
739
|
+
shift: true;
|
|
740
|
+
}
|
|
741
|
+
): Promise<T | null>;
|
|
742
|
+
|
|
743
|
+
async function next<T>(
|
|
744
|
+
cursor: AbstractCursor<T>,
|
|
745
|
+
{
|
|
746
|
+
blocking,
|
|
747
|
+
transform,
|
|
748
|
+
shift
|
|
749
|
+
}: {
|
|
750
|
+
blocking: boolean;
|
|
751
|
+
transform: boolean;
|
|
752
|
+
shift: boolean;
|
|
753
|
+
}
|
|
754
|
+
): Promise<boolean | T | null> {
|
|
722
755
|
if (cursor.closed) {
|
|
756
|
+
if (!shift) return false;
|
|
723
757
|
return null;
|
|
724
758
|
}
|
|
725
759
|
|
|
@@ -730,7 +764,8 @@ async function next<T>(
|
|
|
730
764
|
}
|
|
731
765
|
|
|
732
766
|
if (cursor[kDocuments].length !== 0) {
|
|
733
|
-
|
|
767
|
+
if (!shift) return true;
|
|
768
|
+
const doc = cursor[kDocuments].shift(cursor[kOptions]);
|
|
734
769
|
|
|
735
770
|
if (doc != null && transform && cursor[kTransform]) {
|
|
736
771
|
try {
|
|
@@ -754,6 +789,7 @@ async function next<T>(
|
|
|
754
789
|
// cleanupCursor should never throw, but if it does it indicates a bug in the driver
|
|
755
790
|
// and we should surface the error
|
|
756
791
|
await cleanupCursor(cursor, {});
|
|
792
|
+
if (!shift) return false;
|
|
757
793
|
return null;
|
|
758
794
|
}
|
|
759
795
|
|
|
@@ -762,8 +798,10 @@ async function next<T>(
|
|
|
762
798
|
|
|
763
799
|
try {
|
|
764
800
|
const response = await cursor.getMore(batchSize);
|
|
765
|
-
|
|
766
|
-
|
|
801
|
+
if (CursorResponse.is(response)) {
|
|
802
|
+
cursor[kId] = response.id;
|
|
803
|
+
cursor[kDocuments] = response;
|
|
804
|
+
} else if (response) {
|
|
767
805
|
const cursorId =
|
|
768
806
|
typeof response.cursor.id === 'number'
|
|
769
807
|
? Long.fromNumber(response.cursor.id)
|
|
@@ -796,10 +834,12 @@ async function next<T>(
|
|
|
796
834
|
}
|
|
797
835
|
|
|
798
836
|
if (cursor[kDocuments].length === 0 && blocking === false) {
|
|
837
|
+
if (!shift) return false;
|
|
799
838
|
return null;
|
|
800
839
|
}
|
|
801
840
|
} while (!cursor.isDead || cursor[kDocuments].length !== 0);
|
|
802
841
|
|
|
842
|
+
if (!shift) return false;
|
|
803
843
|
return null;
|
|
804
844
|
}
|
|
805
845
|
|
|
@@ -921,7 +961,7 @@ class ReadableCursorStream extends Readable {
|
|
|
921
961
|
|
|
922
962
|
private _readNext() {
|
|
923
963
|
// eslint-disable-next-line github/no-then
|
|
924
|
-
next(this._cursor, { blocking: true, transform: true }).then(
|
|
964
|
+
next(this._cursor, { blocking: true, transform: true, shift: true }).then(
|
|
925
965
|
result => {
|
|
926
966
|
if (result == null) {
|
|
927
967
|
this.push(null);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type Document
|
|
1
|
+
import { type Document } from '../bson';
|
|
2
|
+
import { CursorResponse } from '../cmap/wire_protocol/responses';
|
|
2
3
|
import { MongoInvalidArgumentError, MongoTailableCursorError } from '../error';
|
|
3
4
|
import { type ExplainVerbosityLike } from '../explain';
|
|
4
5
|
import type { MongoClient } from '../mongo_client';
|
|
@@ -34,7 +35,7 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
|
|
|
34
35
|
/** @internal */
|
|
35
36
|
[kFilter]: Document;
|
|
36
37
|
/** @internal */
|
|
37
|
-
[kNumReturned]
|
|
38
|
+
[kNumReturned] = 0;
|
|
38
39
|
/** @internal */
|
|
39
40
|
[kBuiltOptions]: FindOptions;
|
|
40
41
|
|
|
@@ -69,7 +70,7 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
|
|
|
69
70
|
|
|
70
71
|
/** @internal */
|
|
71
72
|
async _initialize(session: ClientSession): Promise<ExecutionResult> {
|
|
72
|
-
const findOperation = new FindOperation(
|
|
73
|
+
const findOperation = new FindOperation(this.namespace, this[kFilter], {
|
|
73
74
|
...this[kBuiltOptions], // NOTE: order matters here, we may need to refine this
|
|
74
75
|
...this.cursorOptions,
|
|
75
76
|
session
|
|
@@ -78,7 +79,9 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
|
|
|
78
79
|
const response = await executeOperation(this.client, findOperation);
|
|
79
80
|
|
|
80
81
|
// the response is not a cursor when `explain` is enabled
|
|
81
|
-
|
|
82
|
+
if (CursorResponse.is(response)) {
|
|
83
|
+
this[kNumReturned] = response.batchSize;
|
|
84
|
+
}
|
|
82
85
|
|
|
83
86
|
// TODO: NODE-2882
|
|
84
87
|
return { server: findOperation.server, session, response };
|
|
@@ -107,14 +110,14 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
|
|
|
107
110
|
// instead, if we determine there are no more documents to request from the server, we preemptively
|
|
108
111
|
// close the cursor
|
|
109
112
|
}
|
|
110
|
-
return
|
|
113
|
+
return CursorResponse.emptyGetMore;
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
116
|
|
|
114
|
-
const response = await super.getMore(batchSize);
|
|
117
|
+
const response = await super.getMore(batchSize, this.client.autoEncrypter ? false : true);
|
|
115
118
|
// TODO: wrap this in some logic to prevent it from happening if we don't need this support
|
|
116
|
-
if (response) {
|
|
117
|
-
this[kNumReturned] = this[kNumReturned] + response.
|
|
119
|
+
if (CursorResponse.is(response)) {
|
|
120
|
+
this[kNumReturned] = this[kNumReturned] + response.batchSize;
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
return response;
|
|
@@ -145,7 +148,7 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
|
|
|
145
148
|
async explain(verbosity?: ExplainVerbosityLike): Promise<Document> {
|
|
146
149
|
return await executeOperation(
|
|
147
150
|
this.client,
|
|
148
|
-
new FindOperation(
|
|
151
|
+
new FindOperation(this.namespace, this[kFilter], {
|
|
149
152
|
...this[kBuiltOptions], // NOTE: order matters here, we may need to refine this
|
|
150
153
|
...this.cursorOptions,
|
|
151
154
|
explain: verbosity ?? true
|
|
@@ -125,7 +125,8 @@ export class RunCommandCursor extends AbstractCursor {
|
|
|
125
125
|
const getMoreOperation = new GetMoreOperation(this.namespace, this.id!, this.server!, {
|
|
126
126
|
...this.cursorOptions,
|
|
127
127
|
session: this.session,
|
|
128
|
-
...this.getMoreOptions
|
|
128
|
+
...this.getMoreOptions,
|
|
129
|
+
useCursorResponse: false
|
|
129
130
|
});
|
|
130
131
|
|
|
131
132
|
return await executeOperation(this.client, getMoreOperation);
|
package/src/index.ts
CHANGED
|
@@ -290,7 +290,11 @@ export type { ConnectionPoolMetrics } from './cmap/metrics';
|
|
|
290
290
|
export type { StreamDescription, StreamDescriptionOptions } from './cmap/stream_description';
|
|
291
291
|
export type { CompressorName } from './cmap/wire_protocol/compression';
|
|
292
292
|
export type { JSTypeOf, OnDemandDocument } from './cmap/wire_protocol/on_demand/document';
|
|
293
|
-
export type {
|
|
293
|
+
export type {
|
|
294
|
+
CursorResponse,
|
|
295
|
+
MongoDBResponse,
|
|
296
|
+
MongoDBResponseConstructor
|
|
297
|
+
} from './cmap/wire_protocol/responses';
|
|
294
298
|
export type { CollectionOptions, CollectionPrivate, ModifyResult } from './collection';
|
|
295
299
|
export type {
|
|
296
300
|
COMMAND_FAILED,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Document } from '../bson';
|
|
2
|
+
import { type CursorResponse } from '../cmap/wire_protocol/responses';
|
|
2
3
|
import {
|
|
3
4
|
isRetryableReadError,
|
|
4
5
|
isRetryableWriteError,
|
|
@@ -44,7 +45,7 @@ export interface ExecutionResult {
|
|
|
44
45
|
/** The session used for this operation, may be implicitly created */
|
|
45
46
|
session?: ClientSession;
|
|
46
47
|
/** The raw server response for the operation */
|
|
47
|
-
response: Document;
|
|
48
|
+
response: Document | CursorResponse;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
/**
|
package/src/operations/find.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Document } from '../bson';
|
|
2
|
-
import
|
|
2
|
+
import { CursorResponse } from '../cmap/wire_protocol/responses';
|
|
3
3
|
import { MongoInvalidArgumentError } from '../error';
|
|
4
4
|
import { ReadConcern } from '../read_concern';
|
|
5
5
|
import type { Server } from '../sdam/server';
|
|
@@ -77,13 +77,8 @@ export class FindOperation extends CommandOperation<Document> {
|
|
|
77
77
|
override options: FindOptions & { writeConcern?: never };
|
|
78
78
|
filter: Document;
|
|
79
79
|
|
|
80
|
-
constructor(
|
|
81
|
-
|
|
82
|
-
ns: MongoDBNamespace,
|
|
83
|
-
filter: Document = {},
|
|
84
|
-
options: FindOptions = {}
|
|
85
|
-
) {
|
|
86
|
-
super(collection, options);
|
|
80
|
+
constructor(ns: MongoDBNamespace, filter: Document = {}, options: FindOptions = {}) {
|
|
81
|
+
super(undefined, options);
|
|
87
82
|
|
|
88
83
|
this.options = { ...options };
|
|
89
84
|
delete this.options.writeConcern;
|
|
@@ -111,12 +106,17 @@ export class FindOperation extends CommandOperation<Document> {
|
|
|
111
106
|
findCommand = decorateWithExplain(findCommand, this.explain);
|
|
112
107
|
}
|
|
113
108
|
|
|
114
|
-
return await server.command(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
109
|
+
return await server.command(
|
|
110
|
+
this.ns,
|
|
111
|
+
findCommand,
|
|
112
|
+
{
|
|
113
|
+
...this.options,
|
|
114
|
+
...this.bsonOptions,
|
|
115
|
+
documentsReturnedIn: 'firstBatch',
|
|
116
|
+
session
|
|
117
|
+
},
|
|
118
|
+
this.explain ? undefined : CursorResponse
|
|
119
|
+
);
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Document, Long } from '../bson';
|
|
2
|
+
import { CursorResponse } from '../cmap/wire_protocol/responses';
|
|
2
3
|
import { MongoRuntimeError } from '../error';
|
|
3
4
|
import type { Server } from '../sdam/server';
|
|
4
5
|
import type { ClientSession } from '../sessions';
|
|
@@ -19,6 +20,8 @@ export interface GetMoreOptions extends OperationOptions {
|
|
|
19
20
|
maxTimeMS?: number;
|
|
20
21
|
/** TODO(NODE-4413): Address bug with maxAwaitTimeMS not being passed in from the cursor correctly */
|
|
21
22
|
maxAwaitTimeMS?: number;
|
|
23
|
+
|
|
24
|
+
useCursorResponse: boolean;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
/**
|
|
@@ -96,7 +99,12 @@ export class GetMoreOperation extends AbstractOperation {
|
|
|
96
99
|
...this.options
|
|
97
100
|
};
|
|
98
101
|
|
|
99
|
-
return await server.command(
|
|
102
|
+
return await server.command(
|
|
103
|
+
this.ns,
|
|
104
|
+
getMoreCmd,
|
|
105
|
+
commandOptions,
|
|
106
|
+
this.options.useCursorResponse ? CursorResponse : undefined
|
|
107
|
+
);
|
|
100
108
|
}
|
|
101
109
|
}
|
|
102
110
|
|