1- import { type Document } from '../../bson' ;
1+ import { BSON , type Document } from '../../bson' ;
22import { DocumentSequence } from '../../cmap/commands' ;
33import { type PkFactory } from '../../mongo_client' ;
44import type { Filter , OptionalId , UpdateFilter , WithoutId } from '../../mongo_types' ;
@@ -28,6 +28,11 @@ export interface ClientBulkWriteCommand {
2828 comment ?: any ;
2929}
3030
31+ /**
32+ * The bytes overhead for the extra fields added post command generation.
33+ */
34+ const MESSAGE_OVERHEAD_BYTES = 1000 ;
35+
3136/** @internal */
3237export class ClientBulkWriteCommandBuilder {
3338 models : AnyClientBulkWriteModel [ ] ;
@@ -62,32 +67,148 @@ export class ClientBulkWriteCommandBuilder {
6267 /**
6368 * Build the bulk write commands from the models.
6469 */
65- buildCommands ( ) : ClientBulkWriteCommand [ ] {
70+ buildCommands ( maxMessageSizeBytes : number , maxWriteBatchSize : number ) : ClientBulkWriteCommand [ ] {
6671 // Iterate the models to build the ops and nsInfo fields.
67- const operations = [ ] ;
72+ // We need to do this in a loop which creates one command each up
73+ // to the max bson size or max message size.
74+ const commands : ClientBulkWriteCommand [ ] = [ ] ;
75+ let currentCommandLength = 0 ;
6876 let currentNamespaceIndex = 0 ;
77+ let currentCommand : ClientBulkWriteCommand = this . baseCommand ( ) ;
6978 const namespaces = new Map < string , number > ( ) ;
79+
7080 for ( const model of this . models ) {
7181 const ns = model . namespace ;
7282 const index = namespaces . get ( ns ) ;
83+
84+ /**
85+ * Convenience function for resetting everything when a new batch
86+ * is started.
87+ */
88+ const reset = ( ) => {
89+ commands . push ( currentCommand ) ;
90+ namespaces . clear ( ) ;
91+ currentNamespaceIndex = 0 ;
92+ currentCommand = this . baseCommand ( ) ;
93+ namespaces . set ( ns , currentNamespaceIndex ) ;
94+ } ;
95+
7396 if ( index != null ) {
74- operations . push ( buildOperation ( model , index , this . pkFactory ) ) ;
97+ // Pushing to the ops document sequence returns the bytes length added.
98+ const operation = buildOperation ( model , index , this . pkFactory ) ;
99+ const operationBuffer = BSON . serialize ( operation ) ;
100+
101+ // Check if the operation buffer can fit in the current command. If it can,
102+ // then add the operation to the document sequence and increment the
103+ // current length as long as the ops don't exceed the maxWriteBatchSize.
104+ if (
105+ currentCommandLength + operationBuffer . length < maxMessageSizeBytes &&
106+ currentCommand . ops . documents . length < maxWriteBatchSize
107+ ) {
108+ // Pushing to the ops document sequence returns the bytes length added.
109+ currentCommandLength =
110+ MESSAGE_OVERHEAD_BYTES + this . addOperation ( currentCommand , operation , operationBuffer ) ;
111+ } else {
112+ // We need to batch. Push the current command to the commands
113+ // array and create a new current command. We aslo need to clear the namespaces
114+ // map for the new command.
115+ reset ( ) ;
116+
117+ const nsInfo = { ns : ns } ;
118+ const nsInfoBuffer = BSON . serialize ( nsInfo ) ;
119+ currentCommandLength =
120+ MESSAGE_OVERHEAD_BYTES +
121+ this . addOperationAndNsInfo (
122+ currentCommand ,
123+ operation ,
124+ operationBuffer ,
125+ nsInfo ,
126+ nsInfoBuffer
127+ ) ;
128+ }
75129 } else {
76130 namespaces . set ( ns , currentNamespaceIndex ) ;
77- operations . push ( buildOperation ( model , currentNamespaceIndex , this . pkFactory ) ) ;
131+ const nsInfo = { ns : ns } ;
132+ const nsInfoBuffer = BSON . serialize ( nsInfo ) ;
133+ const operation = buildOperation ( model , currentNamespaceIndex , this . pkFactory ) ;
134+ const operationBuffer = BSON . serialize ( operation ) ;
135+
136+ // Check if the operation and nsInfo buffers can fit in the command. If they
137+ // can, then add the operation and nsInfo to their respective document
138+ // sequences and increment the current length as long as the ops don't exceed
139+ // the maxWriteBatchSize.
140+ if (
141+ currentCommandLength + nsInfoBuffer . length + operationBuffer . length <
142+ maxMessageSizeBytes &&
143+ currentCommand . ops . documents . length < maxWriteBatchSize
144+ ) {
145+ currentCommandLength =
146+ MESSAGE_OVERHEAD_BYTES +
147+ this . addOperationAndNsInfo (
148+ currentCommand ,
149+ operation ,
150+ operationBuffer ,
151+ nsInfo ,
152+ nsInfoBuffer
153+ ) ;
154+ } else {
155+ // We need to batch. Push the current command to the commands
156+ // array and create a new current command. Aslo clear the namespaces map.
157+ reset ( ) ;
158+
159+ currentCommandLength =
160+ MESSAGE_OVERHEAD_BYTES +
161+ this . addOperationAndNsInfo (
162+ currentCommand ,
163+ operation ,
164+ operationBuffer ,
165+ nsInfo ,
166+ nsInfoBuffer
167+ ) ;
168+ }
169+ // We've added a new namespace, increment the namespace index.
78170 currentNamespaceIndex ++ ;
79171 }
80172 }
81173
82- const nsInfo = Array . from ( namespaces . keys ( ) , ns => ( { ns } ) ) ;
174+ // After we've finisihed iterating all the models put the last current command
175+ // only if there are operations in it.
176+ if ( currentCommand . ops . documents . length > 0 ) {
177+ commands . push ( currentCommand ) ;
178+ }
83179
84- // The base command.
180+ return commands ;
181+ }
182+
183+ private addOperation (
184+ command : ClientBulkWriteCommand ,
185+ operation : Document ,
186+ operationBuffer : Uint8Array
187+ ) : number {
188+ // Pushing to the ops document sequence returns the bytes length added.
189+ return command . ops . push ( operation , operationBuffer ) ;
190+ }
191+
192+ private addOperationAndNsInfo (
193+ command : ClientBulkWriteCommand ,
194+ operation : Document ,
195+ operationBuffer : Uint8Array ,
196+ nsInfo : Document ,
197+ nsInfoBuffer : Uint8Array
198+ ) : number {
199+ // Pushing to the nsInfo document sequence returns the bytes length added.
200+ const nsInfoLength = command . nsInfo . push ( nsInfo , nsInfoBuffer ) ;
201+ const opsLength = this . addOperation ( command , operation , operationBuffer ) ;
202+ return nsInfoLength + opsLength ;
203+ }
204+
205+ private baseCommand ( ) : ClientBulkWriteCommand {
85206 const command : ClientBulkWriteCommand = {
86207 bulkWrite : 1 ,
87208 errorsOnly : this . errorsOnly ,
88209 ordered : this . options . ordered ?? true ,
89- ops : new DocumentSequence ( operations ) ,
90- nsInfo : new DocumentSequence ( nsInfo )
210+ ops : new DocumentSequence ( 'ops' ) ,
211+ nsInfo : new DocumentSequence ( ' nsInfo' )
91212 } ;
92213 // Add bypassDocumentValidation if it was present in the options.
93214 if ( this . options . bypassDocumentValidation != null ) {
@@ -103,7 +224,8 @@ export class ClientBulkWriteCommandBuilder {
103224 if ( this . options . comment !== undefined ) {
104225 command . comment = this . options . comment ;
105226 }
106- return [ command ] ;
227+
228+ return command ;
107229 }
108230}
109231
0 commit comments