11import { BSON , type Document } from '../../bson' ;
22import { DocumentSequence } from '../../cmap/commands' ;
3+ import { MongoClientBulkWriteUpdateError , MongoInvalidArgumentError } from '../../error' ;
34import { type PkFactory } from '../../mongo_client' ;
45import type { Filter , OptionalId , UpdateFilter , WithoutId } from '../../mongo_types' ;
56import { DEFAULT_PK_FACTORY } from '../../utils' ;
@@ -82,7 +83,11 @@ export class ClientBulkWriteCommandBuilder {
8283 * @param maxWriteBatchSize - The max write batch size.
8384 * @returns The client bulk write command.
8485 */
85- buildBatch ( maxMessageSizeBytes : number , maxWriteBatchSize : number ) : ClientBulkWriteCommand {
86+ buildBatch (
87+ maxMessageSizeBytes : number ,
88+ maxWriteBatchSize : number ,
89+ maxBsonObjectSize : number
90+ ) : ClientBulkWriteCommand {
8691 let commandLength = 0 ;
8792 let currentNamespaceIndex = 0 ;
8893 const command : ClientBulkWriteCommand = this . baseCommand ( ) ;
@@ -96,7 +101,16 @@ export class ClientBulkWriteCommandBuilder {
96101 if ( nsIndex != null ) {
97102 // Build the operation and serialize it to get the bytes buffer.
98103 const operation = buildOperation ( model , nsIndex , this . pkFactory ) ;
99- const operationBuffer = BSON . serialize ( operation ) ;
104+ let operationBuffer ;
105+ try {
106+ operationBuffer = BSON . serialize ( operation ) ;
107+ } catch ( error ) {
108+ throw new MongoInvalidArgumentError (
109+ `Could not serialize operation to BSON: ${ error . message } .`
110+ ) ;
111+ }
112+
113+ validateBufferSize ( 'ops' , operationBuffer , maxBsonObjectSize , maxMessageSizeBytes ) ;
100114
101115 // Check if the operation buffer can fit in the command. If it can,
102116 // then add the operation to the document sequence and increment the
@@ -119,9 +133,20 @@ export class ClientBulkWriteCommandBuilder {
119133 // construct our nsInfo and ops documents and buffers.
120134 namespaces . set ( ns , currentNamespaceIndex ) ;
121135 const nsInfo = { ns : ns } ;
122- const nsInfoBuffer = BSON . serialize ( nsInfo ) ;
123136 const operation = buildOperation ( model , currentNamespaceIndex , this . pkFactory ) ;
124- const operationBuffer = BSON . serialize ( operation ) ;
137+ let nsInfoBuffer ;
138+ let operationBuffer ;
139+ try {
140+ nsInfoBuffer = BSON . serialize ( nsInfo ) ;
141+ operationBuffer = BSON . serialize ( operation ) ;
142+ } catch ( error ) {
143+ throw new MongoInvalidArgumentError (
144+ `Could not serialize ns info and operation to BSON: ${ error . message } .`
145+ ) ;
146+ }
147+
148+ validateBufferSize ( 'nsInfo' , nsInfoBuffer , maxBsonObjectSize , maxMessageSizeBytes ) ;
149+ validateBufferSize ( 'ops' , operationBuffer , maxBsonObjectSize , maxMessageSizeBytes ) ;
125150
126151 // Check if the operation and nsInfo buffers can fit in the command. If they
127152 // can, then add the operation and nsInfo to their respective document
@@ -179,6 +204,25 @@ export class ClientBulkWriteCommandBuilder {
179204 }
180205}
181206
207+ function validateBufferSize (
208+ name : string ,
209+ buffer : Uint8Array ,
210+ maxBsonObjectSize : number ,
211+ maxMessageSizeBytes : number
212+ ) {
213+ if ( buffer . length > maxBsonObjectSize ) {
214+ throw new MongoInvalidArgumentError (
215+ `Client bulk write operation ${ name } of length ${ buffer . length } exceeds the max bson object size of ${ maxBsonObjectSize } `
216+ ) ;
217+ }
218+
219+ if ( buffer . length > maxMessageSizeBytes ) {
220+ throw new MongoInvalidArgumentError (
221+ `Client bulk write operation ${ name } of length ${ buffer . length } exceeds the max message size size of ${ maxMessageSizeBytes } `
222+ ) ;
223+ }
224+ }
225+
182226/** @internal */
183227interface ClientInsertOperation {
184228 insert : number ;
@@ -293,6 +337,22 @@ export const buildUpdateManyOperation = (
293337 return createUpdateOperation ( model , index , true ) ;
294338} ;
295339
340+ /**
341+ * Validate the update document.
342+ * @param update - The update document.
343+ */
344+ function validateUpdate ( update : Document ) {
345+ const keys = Object . keys ( update ) ;
346+ if ( keys . length === 0 ) {
347+ throw new MongoClientBulkWriteUpdateError ( 'Client bulk write update models may not be empty.' ) ;
348+ }
349+ if ( ! keys [ 0 ] . startsWith ( '$' ) ) {
350+ throw new MongoClientBulkWriteUpdateError (
351+ 'Client bulk write update models must only contain atomic modifiers (start with $).'
352+ ) ;
353+ }
354+ }
355+
296356/**
297357 * Creates a delete operation based on the parameters.
298358 */
@@ -301,6 +361,22 @@ function createUpdateOperation(
301361 index : number ,
302362 multi : boolean
303363) : ClientUpdateOperation {
364+ // Update documents provided in UpdateOne and UpdateMany write models are
365+ // required only to contain atomic modifiers (i.e. keys that start with "$").
366+ // Drivers MUST throw an error if an update document is empty or if the
367+ // document's first key does not start with "$".
368+ if ( Array . isArray ( model . update ) ) {
369+ if ( model . update . length === 0 ) {
370+ throw new MongoClientBulkWriteUpdateError (
371+ 'Client bulk write update model pipelines may not be empty.'
372+ ) ;
373+ }
374+ for ( const update of model . update ) {
375+ validateUpdate ( update ) ;
376+ }
377+ } else {
378+ validateUpdate ( model . update ) ;
379+ }
304380 const document : ClientUpdateOperation = {
305381 update : index ,
306382 multi : multi ,
@@ -343,6 +419,16 @@ export const buildReplaceOneOperation = (
343419 model : ClientReplaceOneModel ,
344420 index : number
345421) : ClientReplaceOneOperation => {
422+ const keys = Object . keys ( model . replacement ) ;
423+ if ( keys . length === 0 ) {
424+ throw new MongoClientBulkWriteUpdateError ( 'Client bulk write replace models may not be empty.' ) ;
425+ }
426+ if ( keys [ 0 ] . startsWith ( '$' ) ) {
427+ throw new MongoClientBulkWriteUpdateError (
428+ 'Client bulk write replace models must not contain atomic modifiers (start with $).'
429+ ) ;
430+ }
431+
346432 const document : ClientReplaceOneOperation = {
347433 update : index ,
348434 multi : false ,
0 commit comments