Skip to content

Commit 30cac05

Browse files
refactor(NODE-6056): implement MongoDBResponse class (#4062)
Co-authored-by: Bailey Pearson <[email protected]>
1 parent 62ea94b commit 30cac05

File tree

19 files changed

+468
-362
lines changed

19 files changed

+468
-362
lines changed

Diff for: src/bson.ts

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export function parseToElementsToArray(bytes: Uint8Array, offset?: number): BSON
3333
const res = BSON.onDemand.parseToElements(bytes, offset);
3434
return Array.isArray(res) ? res : [...res];
3535
}
36+
3637
export const getInt32LE = BSON.onDemand.NumberUtils.getInt32LE;
3738
export const getFloat64LE = BSON.onDemand.NumberUtils.getFloat64LE;
3839
export const getBigInt64LE = BSON.onDemand.NumberUtils.getBigInt64LE;

Diff for: src/cmap/auth/gssapi.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,9 @@ type MechanismProperties = {
2929
async function externalCommand(
3030
connection: Connection,
3131
command: ReturnType<typeof saslStart> | ReturnType<typeof saslContinue>
32-
): Promise<{ payload: string; conversationId: any }> {
33-
return await (connection.command(ns('$external.$cmd'), command, undefined) as Promise<{
34-
payload: string;
35-
conversationId: any;
36-
}>);
32+
): Promise<{ payload: string; conversationId: number }> {
33+
const response = await connection.command(ns('$external.$cmd'), command);
34+
return response as { payload: string; conversationId: number };
3735
}
3836

3937
let krb: Kerberos;

Diff for: src/cmap/commands.ts

+28-132
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ export type WriteProtocolMessageType = OpQueryRequest | OpMsgRequest;
3737
export interface OpQueryOptions extends CommandOptions {
3838
socketTimeoutMS?: number;
3939
session?: ClientSession;
40-
documentsReturnedIn?: string;
4140
numberToSkip?: number;
4241
numberToReturn?: number;
4342
returnFieldSelector?: Document;
@@ -53,9 +52,6 @@ export interface OpQueryOptions extends CommandOptions {
5352
exhaustAllowed?: boolean;
5453
}
5554

56-
/**************************************************************
57-
* QUERY
58-
**************************************************************/
5955
/** @internal */
6056
export class OpQueryRequest {
6157
ns: string;
@@ -284,16 +280,11 @@ export interface MessageHeader {
284280
}
285281

286282
/** @internal */
287-
export interface OpResponseOptions extends BSONSerializeOptions {
288-
documentsReturnedIn?: string | null;
289-
}
290-
291-
/** @internal */
292-
export class OpQueryResponse {
283+
export class OpReply {
293284
parsed: boolean;
294285
raw: Buffer;
295286
data: Buffer;
296-
opts: OpResponseOptions;
287+
opts: BSONSerializeOptions;
297288
length: number;
298289
requestId: number;
299290
responseTo: number;
@@ -303,7 +294,6 @@ export class OpQueryResponse {
303294
cursorId?: Long;
304295
startingFrom?: number;
305296
numberReturned?: number;
306-
documents: (Document | Buffer)[] = new Array(0);
307297
cursorNotFound?: boolean;
308298
queryFailure?: boolean;
309299
shardConfigStale?: boolean;
@@ -313,7 +303,8 @@ export class OpQueryResponse {
313303
promoteValues: boolean;
314304
promoteBuffers: boolean;
315305
bsonRegExp?: boolean;
316-
index?: number;
306+
index = 0;
307+
sections: Uint8Array[] = [];
317308

318309
/** moreToCome is an OP_MSG only concept */
319310
moreToCome = false;
@@ -322,7 +313,7 @@ export class OpQueryResponse {
322313
message: Buffer,
323314
msgHeader: MessageHeader,
324315
msgBody: Buffer,
325-
opts?: OpResponseOptions
316+
opts?: BSONSerializeOptions
326317
) {
327318
this.parsed = false;
328319
this.raw = message;
@@ -356,29 +347,9 @@ export class OpQueryResponse {
356347
return this.parsed;
357348
}
358349

359-
parse(options: OpResponseOptions): void {
350+
parse(): Uint8Array {
360351
// Don't parse again if not needed
361-
if (this.parsed) return;
362-
options = options ?? {};
363-
364-
// Allow the return of raw documents instead of parsing
365-
const raw = options.raw || false;
366-
const documentsReturnedIn = options.documentsReturnedIn || null;
367-
const useBigInt64 = options.useBigInt64 ?? this.opts.useBigInt64;
368-
const promoteLongs = options.promoteLongs ?? this.opts.promoteLongs;
369-
const promoteValues = options.promoteValues ?? this.opts.promoteValues;
370-
const promoteBuffers = options.promoteBuffers ?? this.opts.promoteBuffers;
371-
const bsonRegExp = options.bsonRegExp ?? this.opts.bsonRegExp;
372-
let bsonSize;
373-
374-
// Set up the options
375-
const _options: BSONSerializeOptions = {
376-
useBigInt64,
377-
promoteLongs,
378-
promoteValues,
379-
promoteBuffers,
380-
bsonRegExp
381-
};
352+
if (this.parsed) return this.sections[0];
382353

383354
// Position within OP_REPLY at which documents start
384355
// (See https://www.mongodb.com/docs/manual/reference/mongodb-wire-protocol/#wire-op-reply)
@@ -390,8 +361,11 @@ export class OpQueryResponse {
390361
this.startingFrom = this.data.readInt32LE(12);
391362
this.numberReturned = this.data.readInt32LE(16);
392363

393-
// Preallocate document array
394-
this.documents = new Array(this.numberReturned);
364+
if (this.numberReturned < 0 || this.numberReturned > 2 ** 32 - 1) {
365+
throw new RangeError(
366+
`OP_REPLY numberReturned is an invalid array length ${this.numberReturned}`
367+
);
368+
}
395369

396370
this.cursorNotFound = (this.responseFlags & CURSOR_NOT_FOUND) !== 0;
397371
this.queryFailure = (this.responseFlags & QUERY_FAILURE) !== 0;
@@ -400,67 +374,26 @@ export class OpQueryResponse {
400374

401375
// Parse Body
402376
for (let i = 0; i < this.numberReturned; i++) {
403-
bsonSize =
377+
const bsonSize =
404378
this.data[this.index] |
405379
(this.data[this.index + 1] << 8) |
406380
(this.data[this.index + 2] << 16) |
407381
(this.data[this.index + 3] << 24);
408382

409-
// If we have raw results specified slice the return document
410-
if (raw) {
411-
this.documents[i] = this.data.slice(this.index, this.index + bsonSize);
412-
} else {
413-
this.documents[i] = BSON.deserialize(
414-
this.data.slice(this.index, this.index + bsonSize),
415-
_options
416-
);
417-
}
383+
const section = this.data.subarray(this.index, this.index + bsonSize);
384+
this.sections.push(section);
418385

419386
// Adjust the index
420387
this.index = this.index + bsonSize;
421388
}
422389

423-
if (this.documents.length === 1 && documentsReturnedIn != null && raw) {
424-
const fieldsAsRaw: Document = {};
425-
fieldsAsRaw[documentsReturnedIn] = true;
426-
_options.fieldsAsRaw = fieldsAsRaw;
427-
428-
const doc = BSON.deserialize(this.documents[0] as Buffer, _options);
429-
this.documents = [doc];
430-
}
431-
432390
// Set parsed
433391
this.parsed = true;
392+
393+
return this.sections[0];
434394
}
435395
}
436396

437-
// Implementation of OP_MSG spec:
438-
// https://github.com/mongodb/specifications/blob/master/source/message/OP_MSG.rst
439-
//
440-
// struct Section {
441-
// uint8 payloadType;
442-
// union payload {
443-
// document document; // payloadType == 0
444-
// struct sequence { // payloadType == 1
445-
// int32 size;
446-
// cstring identifier;
447-
// document* documents;
448-
// };
449-
// };
450-
// };
451-
452-
// struct OP_MSG {
453-
// struct MsgHeader {
454-
// int32 messageLength;
455-
// int32 requestID;
456-
// int32 responseTo;
457-
// int32 opCode = 2013;
458-
// };
459-
// uint32 flagBits;
460-
// Section+ sections;
461-
// [uint32 checksum;]
462-
// };
463-
464397
// Msg Flags
465398
const OPTS_CHECKSUM_PRESENT = 1;
466399
const OPTS_MORE_TO_COME = 2;
@@ -587,7 +520,7 @@ export class OpMsgResponse {
587520
parsed: boolean;
588521
raw: Buffer;
589522
data: Buffer;
590-
opts: OpResponseOptions;
523+
opts: BSONSerializeOptions;
591524
length: number;
592525
requestId: number;
593526
responseTo: number;
@@ -603,14 +536,14 @@ export class OpMsgResponse {
603536
promoteValues: boolean;
604537
promoteBuffers: boolean;
605538
bsonRegExp: boolean;
606-
documents: (Document | Buffer)[];
607-
index?: number;
539+
index = 0;
540+
sections: Uint8Array[] = [];
608541

609542
constructor(
610543
message: Buffer,
611544
msgHeader: MessageHeader,
612545
msgBody: Buffer,
613-
opts?: OpResponseOptions
546+
opts?: BSONSerializeOptions
614547
) {
615548
this.parsed = false;
616549
this.raw = message;
@@ -642,47 +575,26 @@ export class OpMsgResponse {
642575
this.promoteBuffers =
643576
typeof this.opts.promoteBuffers === 'boolean' ? this.opts.promoteBuffers : false;
644577
this.bsonRegExp = typeof this.opts.bsonRegExp === 'boolean' ? this.opts.bsonRegExp : false;
645-
646-
this.documents = [];
647578
}
648579

649580
isParsed(): boolean {
650581
return this.parsed;
651582
}
652583

653-
parse(options: OpResponseOptions): void {
584+
parse(): Uint8Array {
654585
// Don't parse again if not needed
655-
if (this.parsed) return;
656-
options = options ?? {};
586+
if (this.parsed) return this.sections[0];
657587

658588
this.index = 4;
659-
// Allow the return of raw documents instead of parsing
660-
const raw = options.raw || false;
661-
const documentsReturnedIn = options.documentsReturnedIn || null;
662-
const useBigInt64 = options.useBigInt64 ?? this.opts.useBigInt64;
663-
const promoteLongs = options.promoteLongs ?? this.opts.promoteLongs;
664-
const promoteValues = options.promoteValues ?? this.opts.promoteValues;
665-
const promoteBuffers = options.promoteBuffers ?? this.opts.promoteBuffers;
666-
const bsonRegExp = options.bsonRegExp ?? this.opts.bsonRegExp;
667-
const validation = this.parseBsonSerializationOptions(options);
668-
669-
// Set up the options
670-
const bsonOptions: BSONSerializeOptions = {
671-
useBigInt64,
672-
promoteLongs,
673-
promoteValues,
674-
promoteBuffers,
675-
bsonRegExp,
676-
validation
677-
// Due to the strictness of the BSON libraries validation option we need this cast
678-
} as BSONSerializeOptions & { validation: { utf8: { writeErrors: boolean } } };
679589

680590
while (this.index < this.data.length) {
681591
const payloadType = this.data.readUInt8(this.index++);
682592
if (payloadType === 0) {
683593
const bsonSize = this.data.readUInt32LE(this.index);
684-
const bin = this.data.slice(this.index, this.index + bsonSize);
685-
this.documents.push(raw ? bin : BSON.deserialize(bin, bsonOptions));
594+
const bin = this.data.subarray(this.index, this.index + bsonSize);
595+
596+
this.sections.push(bin);
597+
686598
this.index += bsonSize;
687599
} else if (payloadType === 1) {
688600
// It was decided that no driver makes use of payload type 1
@@ -692,25 +604,9 @@ export class OpMsgResponse {
692604
}
693605
}
694606

695-
if (this.documents.length === 1 && documentsReturnedIn != null && raw) {
696-
const fieldsAsRaw: Document = {};
697-
fieldsAsRaw[documentsReturnedIn] = true;
698-
bsonOptions.fieldsAsRaw = fieldsAsRaw;
699-
const doc = BSON.deserialize(this.documents[0] as Buffer, bsonOptions);
700-
this.documents = [doc];
701-
}
702-
703607
this.parsed = true;
704-
}
705-
706-
parseBsonSerializationOptions({ enableUtf8Validation }: BSONSerializeOptions): {
707-
utf8: { writeErrors: false } | false;
708-
} {
709-
if (enableUtf8Validation === false) {
710-
return { utf8: false };
711-
}
712608

713-
return { utf8: { writeErrors: false } };
609+
return this.sections[0];
714610
}
715611
}
716612

Diff for: src/cmap/connect.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export async function performInitialHandshake(
103103
const handshakeDoc = await prepareHandshakeDocument(authContext);
104104

105105
// @ts-expect-error: TODO(NODE-5141): The options need to be filtered properly, Connection options differ from Command options
106-
const handshakeOptions: CommandOptions = { ...options };
106+
const handshakeOptions: CommandOptions = { ...options, raw: false };
107107
if (typeof options.connectTimeoutMS === 'number') {
108108
// The handshake technically is a monitoring check, so its socket timeout should be connectTimeoutMS
109109
handshakeOptions.socketTimeoutMS = options.connectTimeoutMS;

0 commit comments

Comments
 (0)