@@ -4,7 +4,13 @@ import { MongoInvalidArgumentError, MongoRuntimeError } from '../error';
4
4
import { ReadPreference } from '../read_preference' ;
5
5
import type { ClientSession } from '../sessions' ;
6
6
import type { CommandOptions } from './connection' ;
7
- import { OP_MSG , OP_QUERY } from './wire_protocol/constants' ;
7
+ import {
8
+ compress ,
9
+ Compressor ,
10
+ type CompressorName ,
11
+ uncompressibleCommands
12
+ } from './wire_protocol/compression' ;
13
+ import { OP_COMPRESSED , OP_MSG , OP_QUERY } from './wire_protocol/constants' ;
8
14
9
15
// Incrementing request id
10
16
let _requestId = 0 ;
@@ -25,7 +31,7 @@ const SHARD_CONFIG_STALE = 4;
25
31
const AWAIT_CAPABLE = 8 ;
26
32
27
33
/** @internal */
28
- export type WriteProtocolMessageType = Query | Msg ;
34
+ export type WriteProtocolMessageType = OpQueryRequest | OpMsgRequest ;
29
35
30
36
/** @internal */
31
37
export interface OpQueryOptions extends CommandOptions {
@@ -52,7 +58,7 @@ export interface OpQueryOptions extends CommandOptions {
52
58
* QUERY
53
59
**************************************************************/
54
60
/** @internal */
55
- export class Query {
61
+ export class OpQueryRequest {
56
62
ns : string ;
57
63
numberToSkip : number ;
58
64
numberToReturn : number ;
@@ -96,7 +102,7 @@ export class Query {
96
102
this . numberToSkip = options . numberToSkip || 0 ;
97
103
this . numberToReturn = options . numberToReturn || 0 ;
98
104
this . returnFieldSelector = options . returnFieldSelector || undefined ;
99
- this . requestId = Query . getRequestId ( ) ;
105
+ this . requestId = options . requestId ?? OpQueryRequest . getRequestId ( ) ;
100
106
101
107
// special case for pre-3.2 find commands, delete ASAP
102
108
this . pre32Limit = options . pre32Limit ;
@@ -285,7 +291,7 @@ export interface OpResponseOptions extends BSONSerializeOptions {
285
291
}
286
292
287
293
/** @internal */
288
- export class Response {
294
+ export class OpQueryResponse {
289
295
parsed : boolean ;
290
296
raw : Buffer ;
291
297
data : Buffer ;
@@ -472,7 +478,7 @@ export interface OpMsgOptions {
472
478
}
473
479
474
480
/** @internal */
475
- export class Msg {
481
+ export class OpMsgRequest {
476
482
requestId : number ;
477
483
serializeFunctions : boolean ;
478
484
ignoreUndefined : boolean ;
@@ -502,7 +508,7 @@ export class Msg {
502
508
this . options = options ?? { } ;
503
509
504
510
// Additional options
505
- this . requestId = options . requestId ? options . requestId : Msg . getRequestId ( ) ;
511
+ this . requestId = options . requestId ? options . requestId : OpMsgRequest . getRequestId ( ) ;
506
512
507
513
// Serialization option
508
514
this . serializeFunctions =
@@ -580,7 +586,7 @@ export class Msg {
580
586
}
581
587
582
588
/** @internal */
583
- export class BinMsg {
589
+ export class OpMsgResponse {
584
590
parsed : boolean ;
585
591
raw : Buffer ;
586
592
data : Buffer ;
@@ -709,3 +715,54 @@ export class BinMsg {
709
715
return { utf8 : { writeErrors : false } } ;
710
716
}
711
717
}
718
+
719
+ const MESSAGE_HEADER_SIZE = 16 ;
720
+ const COMPRESSION_DETAILS_SIZE = 9 ; // originalOpcode + uncompressedSize, compressorID
721
+
722
+ /**
723
+ * @internal
724
+ *
725
+ * An OP_COMPRESSED request wraps either an OP_QUERY or OP_MSG message.
726
+ */
727
+ export class OpCompressedRequest {
728
+ constructor (
729
+ private command : WriteProtocolMessageType ,
730
+ private options : { zlibCompressionLevel : number ; agreedCompressor : CompressorName }
731
+ ) { }
732
+
733
+ // Return whether a command contains an uncompressible command term
734
+ // Will return true if command contains no uncompressible command terms
735
+ static canCompress ( command : WriteProtocolMessageType ) {
736
+ const commandDoc = command instanceof OpMsgRequest ? command . command : command . query ;
737
+ const commandName = Object . keys ( commandDoc ) [ 0 ] ;
738
+ return ! uncompressibleCommands . has ( commandName ) ;
739
+ }
740
+
741
+ async toBin ( ) : Promise < Buffer [ ] > {
742
+ const concatenatedOriginalCommandBuffer = Buffer . concat ( this . command . toBin ( ) ) ;
743
+ // otherwise, compress the message
744
+ const messageToBeCompressed = concatenatedOriginalCommandBuffer . slice ( MESSAGE_HEADER_SIZE ) ;
745
+
746
+ // Extract information needed for OP_COMPRESSED from the uncompressed message
747
+ const originalCommandOpCode = concatenatedOriginalCommandBuffer . readInt32LE ( 12 ) ;
748
+
749
+ // Compress the message body
750
+ const compressedMessage = await compress ( this . options , messageToBeCompressed ) ;
751
+ // Create the msgHeader of OP_COMPRESSED
752
+ const msgHeader = Buffer . alloc ( MESSAGE_HEADER_SIZE ) ;
753
+ msgHeader . writeInt32LE (
754
+ MESSAGE_HEADER_SIZE + COMPRESSION_DETAILS_SIZE + compressedMessage . length ,
755
+ 0
756
+ ) ; // messageLength
757
+ msgHeader . writeInt32LE ( this . command . requestId , 4 ) ; // requestID
758
+ msgHeader . writeInt32LE ( 0 , 8 ) ; // responseTo (zero)
759
+ msgHeader . writeInt32LE ( OP_COMPRESSED , 12 ) ; // opCode
760
+
761
+ // Create the compression details of OP_COMPRESSED
762
+ const compressionDetails = Buffer . alloc ( COMPRESSION_DETAILS_SIZE ) ;
763
+ compressionDetails . writeInt32LE ( originalCommandOpCode , 0 ) ; // originalOpcode
764
+ compressionDetails . writeInt32LE ( messageToBeCompressed . length , 4 ) ; // Size of the uncompressed compressedMessage, excluding the MsgHeader
765
+ compressionDetails . writeUInt8 ( Compressor [ this . options . agreedCompressor ] , 8 ) ; // compressorID
766
+ return [ msgHeader , compressionDetails , compressedMessage ] ;
767
+ }
768
+ }
0 commit comments