@@ -189,16 +189,19 @@ describe.skipClassic('Pipeline class', () => {
189189 let beginDocCreation = 0 ;
190190 let endDocCreation = 0 ;
191191
192- async function testCollectionWithDocs ( docs : {
193- [ id : string ] : DocumentData ;
194- } ) : Promise < CollectionReference < DocumentData > > {
192+ async function testCollectionWithDocs (
193+ targetCol : CollectionReference ,
194+ docs : {
195+ [ id : string ] : DocumentData ;
196+ } ,
197+ ) : Promise < CollectionReference < DocumentData > > {
195198 beginDocCreation = new Date ( ) . valueOf ( ) ;
196199 for ( const id in docs ) {
197- const ref = randomCol . doc ( id ) ;
200+ const ref = targetCol . doc ( id ) ;
198201 await ref . set ( docs [ id ] ) ;
199202 }
200203 endDocCreation = new Date ( ) . valueOf ( ) ;
201- return randomCol ;
204+ return targetCol ;
202205 }
203206
204207 function expectResults ( result : PipelineSnapshot , ...docs : string [ ] ) : void ;
@@ -224,7 +227,9 @@ describe.skipClassic('Pipeline class', () => {
224227 }
225228 }
226229
227- async function setupBookDocs ( ) : Promise < CollectionReference < DocumentData > > {
230+ async function setupBookDocs (
231+ targetCol : CollectionReference ,
232+ ) : Promise < CollectionReference < DocumentData > > {
228233 const bookDocs : { [ id : string ] : DocumentData } = {
229234 book1 : {
230235 title : "The Hitchhiker's Guide to the Galaxy" ,
@@ -334,18 +339,153 @@ describe.skipClassic('Pipeline class', () => {
334339 embedding : FieldValue . vector ( [ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 10 ] ) ,
335340 } ,
336341 } ;
337- return testCollectionWithDocs ( bookDocs ) ;
342+ return testCollectionWithDocs ( targetCol , bookDocs ) ;
338343 }
339344
340345 before ( async ( ) => {
341346 randomCol = getTestRoot ( ) ;
342- await setupBookDocs ( ) ;
347+ await setupBookDocs ( randomCol ) ;
343348 firestore = randomCol . firestore ;
344349 } ) ;
345350
346351 afterEach ( ( ) => verifyInstance ( firestore as unknown as InternalFirestore ) ) ;
347352
348353 describe ( 'pipeline results' , ( ) => {
354+ describe ( 'DML stages' , ( ) => {
355+ let dmlCol : CollectionReference ;
356+
357+ beforeEach ( async ( ) => {
358+ dmlCol = getTestRoot ( ) ;
359+ await setupBookDocs ( dmlCol ) ;
360+ } ) ;
361+
362+ it ( 'can execute delete stage multiple documents' , async ( ) => {
363+ const deletePpl = firestore
364+ . pipeline ( )
365+ . collection ( dmlCol . path )
366+ . where ( equal ( field ( 'genre' ) , 'Science Fiction' ) )
367+ . delete ( ) ;
368+
369+ const promise = deletePpl . execute ( ) ;
370+
371+ if ( process . env . FIRESTORE_TARGET_BACKEND ?. toUpperCase ( ) === 'NIGHTLY' ) {
372+ const deleteRes = await promise ;
373+ expectResults ( deleteRes , { documents_modified : 2 } ) ;
374+
375+ const docSnap1 = await dmlCol . doc ( 'book1' ) . get ( ) ;
376+ expect ( docSnap1 . exists ) . to . be . false ;
377+
378+ const docSnap10 = await dmlCol . doc ( 'book10' ) . get ( ) ;
379+ expect ( docSnap10 . exists ) . to . be . false ;
380+ } else {
381+ await expect ( promise ) . to . be . rejected ;
382+ }
383+ } ) ;
384+
385+ it ( 'can execute delete stage within a transaction' , async ( ) => {
386+ const promise = firestore . runTransaction ( async transaction => {
387+ const deletePpl = firestore
388+ . pipeline ( )
389+ . collection ( dmlCol . path )
390+ . where ( equal ( field ( '__name__' ) . documentId ( ) , 'book2' ) )
391+ . delete ( ) ;
392+
393+ const deleteRes = await transaction . execute ( deletePpl ) ;
394+ expectResults ( deleteRes , { documents_modified : 1 } ) ;
395+ } ) ;
396+
397+ if ( process . env . FIRESTORE_TARGET_BACKEND ?. toUpperCase ( ) === 'NIGHTLY' ) {
398+ await promise ;
399+ const docSnap = await dmlCol . doc ( 'book2' ) . get ( ) ;
400+ expect ( docSnap . exists ) . to . be . false ;
401+ } else {
402+ await expect ( promise ) . to . be . rejected ;
403+ }
404+ } ) ;
405+
406+ it ( 'can execute update stage with addFields' , async ( ) => {
407+ const ppl = firestore
408+ . pipeline ( )
409+ . collection ( dmlCol . path )
410+ . where ( equal ( field ( '__name__' ) . documentId ( ) , 'book3' ) )
411+ . addFields ( field ( '__name__' ) . documentId ( ) . as ( 'id' ) )
412+ . update ( [ constant ( 'baz' ) . as ( 'foo' ) ] ) ;
413+
414+ const promise = ppl . execute ( ) ;
415+
416+ if ( process . env . FIRESTORE_TARGET_BACKEND ?. toUpperCase ( ) === 'NIGHTLY' ) {
417+ const res = await promise ;
418+ expectResults ( res , { documents_modified : 1 } ) ;
419+
420+ const docSnap = await dmlCol . doc ( 'book3' ) . get ( ) ;
421+ expect ( docSnap . get ( 'foo' ) ) . to . equal ( 'baz' ) ;
422+ expect ( docSnap . get ( 'id' ) ) . to . equal ( 'book3' ) ;
423+ } else {
424+ await expect ( promise ) . to . be . rejected ;
425+ }
426+ } ) ;
427+
428+ it ( 'can update multiple documents and remove fields' , async ( ) => {
429+ const promise = firestore
430+ . pipeline ( )
431+ . collection ( dmlCol . path )
432+ . where ( equal ( field ( 'genre' ) , 'Science Fiction' ) )
433+ . removeFields ( 'awards' )
434+ . update ( [ constant ( 'Updated' ) . as ( 'status' ) ] )
435+ . execute ( ) ;
436+
437+ if ( process . env . FIRESTORE_TARGET_BACKEND ?. toUpperCase ( ) === 'NIGHTLY' ) {
438+ const res = await promise ;
439+ expectResults ( res , { documents_modified : 2 } ) ;
440+
441+ const docSnap1 = await dmlCol . doc ( 'book1' ) . get ( ) ;
442+ expect ( docSnap1 . get ( 'status' ) ) . to . equal ( 'Updated' ) ;
443+ expect ( docSnap1 . get ( 'awards' ) ) . to . be . undefined ;
444+
445+ const docSnap10 = await dmlCol . doc ( 'book10' ) . get ( ) ;
446+ expect ( docSnap10 . get ( 'status' ) ) . to . equal ( 'Updated' ) ;
447+ expect ( docSnap10 . get ( 'awards' ) ) . to . be . undefined ;
448+ } else {
449+ await expect ( promise ) . to . be . rejected ;
450+ }
451+ } ) ;
452+
453+ it ( 'can update with expressions' , async ( ) => {
454+ const promise = firestore
455+ . pipeline ( )
456+ . collection ( dmlCol . path )
457+ . where ( equal ( field ( '__name__' ) . documentId ( ) , 'book1' ) )
458+ . update ( [ add ( field ( 'rating' ) , constant ( 1.0 ) ) . as ( 'rating' ) ] )
459+ . execute ( ) ;
460+
461+ if ( process . env . FIRESTORE_TARGET_BACKEND ?. toUpperCase ( ) === 'NIGHTLY' ) {
462+ const res = await promise ;
463+ expectResults ( res , { documents_modified : 1 } ) ;
464+
465+ const docSnap = await dmlCol . doc ( 'book1' ) . get ( ) ;
466+ expect ( docSnap . get ( 'rating' ) ) . to . equal ( 5.2 ) ;
467+ } else {
468+ await expect ( promise ) . to . be . rejected ;
469+ }
470+ } ) ;
471+
472+ it ( 'can update non existing document modifies zero documents' , async ( ) => {
473+ const nonExistingId = 'nonExistingId_123' ;
474+ const promise = firestore
475+ . pipeline ( )
476+ . documents ( [ dmlCol . doc ( nonExistingId ) ] )
477+ . update ( [ constant ( 'Updated' ) . as ( 'status' ) ] )
478+ . execute ( ) ;
479+
480+ if ( process . env . FIRESTORE_TARGET_BACKEND ?. toUpperCase ( ) === 'NIGHTLY' ) {
481+ const res = await promise ;
482+ expectResults ( res , { documents_modified : 0 } ) ;
483+ } else {
484+ await expect ( promise ) . to . be . rejected ;
485+ }
486+ } ) ;
487+ } ) ;
488+
349489 it ( 'empty snapshot as expected' , async ( ) => {
350490 const snapshot = await firestore
351491 . pipeline ( )
0 commit comments