@@ -42,6 +42,10 @@ use url::Url;
4242/// Default metadata endpoint
4343static DEFAULT_METADATA_ENDPOINT : & str = "http://169.254.169.254" ;
4444
45+ /// AWS S3 does not support copy operations larger than 5 GiB in a single request.
46+ /// https://docs.aws.amazon.com/AmazonS3/latest/userguide/copy-object.html
47+ const MAX_SINGLE_REQUEST_COPY_SIZE : u64 = 5 * 1024 * 1024 * 1024 ;
48+
4549/// A specialized `Error` for object store-related errors
4650#[ derive( Debug , thiserror:: Error ) ]
4751enum Error {
@@ -189,6 +193,10 @@ pub struct AmazonS3Builder {
189193 request_payer : ConfigValue < bool > ,
190194 /// The [`HttpConnector`] to use
191195 http_connector : Option < Arc < dyn HttpConnector > > ,
196+ /// Threshold (bytes) above which copy uses multipart copy. If not set, defaults to 5 GiB.
197+ multipart_copy_threshold : Option < ConfigValue < u64 > > ,
198+ /// Preferred multipart copy part size (bytes). If not set, defaults to 5 GiB.
199+ multipart_copy_part_size : Option < ConfigValue < u64 > > ,
192200}
193201
194202/// Configuration keys for [`AmazonS3Builder`]
@@ -423,6 +431,10 @@ pub enum AmazonS3ConfigKey {
423431
424432 /// Encryption options
425433 Encryption ( S3EncryptionConfigKey ) ,
434+ /// Threshold (bytes) to switch to multipart copy
435+ MultipartCopyThreshold ,
436+ /// Preferred multipart copy part size (bytes)
437+ MultipartCopyPartSize ,
426438}
427439
428440impl AsRef < str > for AmazonS3ConfigKey {
@@ -455,6 +467,8 @@ impl AsRef<str> for AmazonS3ConfigKey {
455467 Self :: RequestPayer => "aws_request_payer" ,
456468 Self :: Client ( opt) => opt. as_ref ( ) ,
457469 Self :: Encryption ( opt) => opt. as_ref ( ) ,
470+ Self :: MultipartCopyThreshold => "aws_multipart_copy_threshold" ,
471+ Self :: MultipartCopyPartSize => "aws_multipart_copy_part_size" ,
458472 }
459473 }
460474}
@@ -499,6 +513,12 @@ impl FromStr for AmazonS3ConfigKey {
499513 "aws_conditional_put" | "conditional_put" => Ok ( Self :: ConditionalPut ) ,
500514 "aws_disable_tagging" | "disable_tagging" => Ok ( Self :: DisableTagging ) ,
501515 "aws_request_payer" | "request_payer" => Ok ( Self :: RequestPayer ) ,
516+ "aws_multipart_copy_threshold" | "multipart_copy_threshold" => {
517+ Ok ( Self :: MultipartCopyThreshold )
518+ }
519+ "aws_multipart_copy_part_size" | "multipart_copy_part_size" => {
520+ Ok ( Self :: MultipartCopyPartSize )
521+ }
502522 // Backwards compatibility
503523 "aws_allow_http" => Ok ( Self :: Client ( ClientConfigKey :: AllowHttp ) ) ,
504524 "aws_server_side_encryption" | "server_side_encryption" => Ok ( Self :: Encryption (
@@ -666,6 +686,12 @@ impl AmazonS3Builder {
666686 self . encryption_customer_key_base64 = Some ( value. into ( ) )
667687 }
668688 } ,
689+ AmazonS3ConfigKey :: MultipartCopyThreshold => {
690+ self . multipart_copy_threshold = Some ( ConfigValue :: Deferred ( value. into ( ) ) )
691+ }
692+ AmazonS3ConfigKey :: MultipartCopyPartSize => {
693+ self . multipart_copy_part_size = Some ( ConfigValue :: Deferred ( value. into ( ) ) )
694+ }
669695 } ;
670696 self
671697 }
@@ -733,6 +759,14 @@ impl AmazonS3Builder {
733759 self . encryption_customer_key_base64 . clone ( )
734760 }
735761 } ,
762+ AmazonS3ConfigKey :: MultipartCopyThreshold => self
763+ . multipart_copy_threshold
764+ . as_ref ( )
765+ . map ( |x| x. to_string ( ) ) ,
766+ AmazonS3ConfigKey :: MultipartCopyPartSize => self
767+ . multipart_copy_part_size
768+ . as_ref ( )
769+ . map ( |x| x. to_string ( ) ) ,
736770 }
737771 }
738772
@@ -1029,6 +1063,18 @@ impl AmazonS3Builder {
10291063 self
10301064 }
10311065
1066+ /// Set threshold (bytes) above which copy uses multipart copy
1067+ pub fn with_multipart_copy_threshold ( mut self , threshold_bytes : u64 ) -> Self {
1068+ self . multipart_copy_threshold = Some ( ConfigValue :: Parsed ( threshold_bytes) ) ;
1069+ self
1070+ }
1071+
1072+ /// Set preferred multipart copy part size (bytes)
1073+ pub fn with_multipart_copy_part_size ( mut self , part_size_bytes : u64 ) -> Self {
1074+ self . multipart_copy_part_size = Some ( ConfigValue :: Parsed ( part_size_bytes) ) ;
1075+ self
1076+ }
1077+
10321078 /// Create a [`AmazonS3`] instance from the provided values,
10331079 /// consuming `self`.
10341080 pub fn build ( mut self ) -> Result < AmazonS3 > {
@@ -1185,6 +1231,17 @@ impl AmazonS3Builder {
11851231 S3EncryptionHeaders :: default ( )
11861232 } ;
11871233
1234+ let multipart_copy_threshold = self
1235+ . multipart_copy_threshold
1236+ . map ( |val| val. get ( ) )
1237+ . transpose ( ) ?
1238+ . unwrap_or ( MAX_SINGLE_REQUEST_COPY_SIZE ) ;
1239+ let multipart_copy_part_size = self
1240+ . multipart_copy_part_size
1241+ . map ( |val| val. get ( ) )
1242+ . transpose ( ) ?
1243+ . unwrap_or ( MAX_SINGLE_REQUEST_COPY_SIZE ) ;
1244+
11881245 let config = S3Config {
11891246 region,
11901247 bucket,
@@ -1201,6 +1258,8 @@ impl AmazonS3Builder {
12011258 conditional_put : self . conditional_put . get ( ) ?,
12021259 encryption_headers,
12031260 request_payer : self . request_payer . get ( ) ?,
1261+ multipart_copy_threshold,
1262+ multipart_copy_part_size,
12041263 } ;
12051264
12061265 let http_client = http. connect ( & config. client_options ) ?;
0 commit comments