@@ -30,6 +30,14 @@ import (
3030 "github.com/minio/minio-go/v7/pkg/s3utils"
3131)
3232
33+ // useMultiDeleteForBulkDelete returns true if the client should use
34+ // multi-object delete API for bulk delete operations. Returns false
35+ // for endpoints that do not support multi-object delete (e.g., GCS).
36+ func (c * Client ) useMultiDeleteForBulkDelete () bool {
37+ // NOTE: GCS does not support multi-object delete API.
38+ return ! s3utils .IsGoogleEndpoint (* c .endpointURL )
39+ }
40+
3341//revive:disable
3442
3543// Deprecated: BucketOptions will be renamed to RemoveBucketOptions in future versions.
@@ -411,6 +419,12 @@ func hasInvalidXMLChar(str string) bool {
411419
412420// Generate and call MultiDelete S3 requests based on entries received from the iterator.
413421func (c * Client ) removeObjectsIter (ctx context.Context , bucketName string , objectsIter iter.Seq [ObjectInfo ], yield func (RemoveObjectResult ) bool , opts RemoveObjectsOptions ) {
422+ // NOTE: GCS does not support multi-object delete, use single DELETE requests.
423+ if ! c .useMultiDeleteForBulkDelete () {
424+ c .removeObjectsSingleIter (ctx , bucketName , objectsIter , yield , opts )
425+ return
426+ }
427+
414428 maxEntries := 1000
415429 urlValues := make (url.Values )
416430 urlValues .Set ("delete" , "" )
@@ -549,14 +563,20 @@ func (c *Client) removeObjectsIter(ctx context.Context, bucketName string, objec
549563
550564// Generate and call MultiDelete S3 requests based on entries received from objectsCh
551565func (c * Client ) removeObjects (ctx context.Context , bucketName string , objectsCh <- chan ObjectInfo , resultCh chan <- RemoveObjectResult , opts RemoveObjectsOptions ) {
566+ // Close result channel when delete finishes.
567+ defer close (resultCh )
568+
569+ // NOTE: GCS does not support multi-object delete, use single DELETE requests.
570+ if ! c .useMultiDeleteForBulkDelete () {
571+ c .removeObjectsSingle (ctx , bucketName , objectsCh , resultCh , opts )
572+ return
573+ }
574+
552575 maxEntries := 1000
553576 finish := false
554577 urlValues := make (url.Values )
555578 urlValues .Set ("delete" , "" )
556579
557- // Close result channel when Multi delete finishes.
558- defer close (resultCh )
559-
560580 // Loop over entries by 1000 and call MultiDelete requests
561581 for ! finish {
562582 count := 0
@@ -640,6 +660,63 @@ func (c *Client) removeObjects(ctx context.Context, bucketName string, objectsCh
640660 }
641661}
642662
663+ // removeObjectsSingle deletes objects one by one using single DELETE requests.
664+ // This is used for endpoints that do not support multi-object delete (e.g., GCS).
665+ func (c * Client ) removeObjectsSingle (ctx context.Context , bucketName string , objectsCh <- chan ObjectInfo , resultCh chan <- RemoveObjectResult , opts RemoveObjectsOptions ) {
666+ for {
667+ select {
668+ case <- ctx .Done ():
669+ return
670+ case object , ok := <- objectsCh :
671+ if ! ok {
672+ return
673+ }
674+ removeResult := c .removeObject (ctx , bucketName , object .Key , RemoveObjectOptions {
675+ VersionID : object .VersionID ,
676+ GovernanceBypass : opts .GovernanceBypass ,
677+ })
678+ if err := removeResult .Err ; err != nil {
679+ // Version/object does not exist is not an error, ignore and continue.
680+ switch ToErrorResponse (err ).Code {
681+ case NoSuchVersion , NoSuchKey :
682+ continue
683+ }
684+ }
685+ select {
686+ case <- ctx .Done ():
687+ return
688+ case resultCh <- removeResult :
689+ }
690+ }
691+ }
692+ }
693+
694+ // removeObjectsSingleIter deletes objects one by one using single DELETE requests.
695+ // This is used for endpoints that do not support multi-object delete (e.g., GCS).
696+ func (c * Client ) removeObjectsSingleIter (ctx context.Context , bucketName string , objectsIter iter.Seq [ObjectInfo ], yield func (RemoveObjectResult ) bool , opts RemoveObjectsOptions ) {
697+ for object := range objectsIter {
698+ select {
699+ case <- ctx .Done ():
700+ return
701+ default :
702+ }
703+ removeResult := c .removeObject (ctx , bucketName , object .Key , RemoveObjectOptions {
704+ VersionID : object .VersionID ,
705+ GovernanceBypass : opts .GovernanceBypass ,
706+ })
707+ if err := removeResult .Err ; err != nil {
708+ // Version/object does not exist is not an error, ignore and continue.
709+ switch ToErrorResponse (err ).Code {
710+ case NoSuchVersion , NoSuchKey :
711+ continue
712+ }
713+ }
714+ if ! yield (removeResult ) {
715+ return
716+ }
717+ }
718+ }
719+
643720// RemoveIncompleteUpload aborts an partially uploaded object.
644721func (c * Client ) RemoveIncompleteUpload (ctx context.Context , bucketName , objectName string ) error {
645722 // Input validation.
0 commit comments