1
- import { type Document } from '../../bson' ;
1
+ import { BSON , type Document } from '../../bson' ;
2
2
import { DocumentSequence } from '../../cmap/commands' ;
3
3
import { type PkFactory } from '../../mongo_client' ;
4
4
import type { Filter , OptionalId , UpdateFilter , WithoutId } from '../../mongo_types' ;
@@ -28,6 +28,11 @@ export interface ClientBulkWriteCommand {
28
28
comment ?: any ;
29
29
}
30
30
31
+ /**
32
+ * The bytes overhead for the extra fields added post command generation.
33
+ */
34
+ const MESSAGE_OVERHEAD_BYTES = 1000 ;
35
+
31
36
/** @internal */
32
37
export class ClientBulkWriteCommandBuilder {
33
38
models : AnyClientBulkWriteModel [ ] ;
@@ -62,32 +67,148 @@ export class ClientBulkWriteCommandBuilder {
62
67
/**
63
68
* Build the bulk write commands from the models.
64
69
*/
65
- buildCommands ( ) : ClientBulkWriteCommand [ ] {
70
+ buildCommands ( maxMessageSizeBytes : number , maxWriteBatchSize : number ) : ClientBulkWriteCommand [ ] {
66
71
// 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 ;
68
76
let currentNamespaceIndex = 0 ;
77
+ let currentCommand : ClientBulkWriteCommand = this . baseCommand ( ) ;
69
78
const namespaces = new Map < string , number > ( ) ;
79
+
70
80
for ( const model of this . models ) {
71
81
const ns = model . namespace ;
72
82
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
+
73
96
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
+ }
75
129
} else {
76
130
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.
78
170
currentNamespaceIndex ++ ;
79
171
}
80
172
}
81
173
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
+ }
83
179
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 {
85
206
const command : ClientBulkWriteCommand = {
86
207
bulkWrite : 1 ,
87
208
errorsOnly : this . errorsOnly ,
88
209
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' )
91
212
} ;
92
213
// Add bypassDocumentValidation if it was present in the options.
93
214
if ( this . options . bypassDocumentValidation != null ) {
@@ -103,7 +224,8 @@ export class ClientBulkWriteCommandBuilder {
103
224
if ( this . options . comment !== undefined ) {
104
225
command . comment = this . options . comment ;
105
226
}
106
- return [ command ] ;
227
+
228
+ return command ;
107
229
}
108
230
}
109
231
0 commit comments