@@ -32,15 +32,28 @@ use aws_config::BehaviorVersion;
3232use aws_credential_types:: provider:: error:: CredentialsError ;
3333use aws_credential_types:: provider:: { ProvideCredentials , SharedCredentialsProvider } ;
3434use log:: debug;
35- use object_store:: aws:: { AmazonS3Builder , AwsCredential } ;
35+ use object_store:: aws:: { AmazonS3Builder , AmazonS3ConfigKey , AwsCredential } ;
3636use object_store:: gcp:: GoogleCloudStorageBuilder ;
3737use object_store:: http:: HttpBuilder ;
3838use object_store:: { ClientOptions , CredentialProvider , ObjectStore } ;
3939use url:: Url ;
4040
41+ #[ cfg( not( test) ) ]
42+ use object_store:: aws:: resolve_bucket_region;
43+
44+ // Provide a local mock when running tests so we don't make network calls
45+ #[ cfg( test) ]
46+ async fn resolve_bucket_region (
47+ _bucket : & str ,
48+ _client_options : & ClientOptions ,
49+ ) -> object_store:: Result < String > {
50+ Ok ( "eu-central-1" . to_string ( ) )
51+ }
52+
4153pub async fn get_s3_object_store_builder (
4254 url : & Url ,
4355 aws_options : & AwsOptions ,
56+ resolve_region : bool ,
4457) -> Result < AmazonS3Builder > {
4558 let AwsOptions {
4659 access_key_id,
@@ -88,6 +101,16 @@ pub async fn get_s3_object_store_builder(
88101 builder = builder. with_region ( region) ;
89102 }
90103
104+ // If the region is not set or auto_detect_region is true, resolve the region.
105+ if builder
106+ . get_config_value ( & AmazonS3ConfigKey :: Region )
107+ . is_none ( )
108+ || resolve_region
109+ {
110+ let region = resolve_bucket_region ( bucket_name, & ClientOptions :: new ( ) ) . await ?;
111+ builder = builder. with_region ( region) ;
112+ }
113+
91114 if let Some ( endpoint) = endpoint {
92115 // Make a nicer error if the user hasn't allowed http and the endpoint
93116 // is http as the default message is "URL scheme is not allowed"
@@ -470,6 +493,7 @@ pub(crate) async fn get_object_store(
470493 scheme : & str ,
471494 url : & Url ,
472495 table_options : & TableOptions ,
496+ resolve_region : bool ,
473497) -> Result < Arc < dyn ObjectStore > , DataFusionError > {
474498 let store: Arc < dyn ObjectStore > = match scheme {
475499 "s3" => {
@@ -478,7 +502,8 @@ pub(crate) async fn get_object_store(
478502 "Given table options incompatible with the 's3' scheme"
479503 ) ;
480504 } ;
481- let builder = get_s3_object_store_builder ( url, options) . await ?;
505+ let builder =
506+ get_s3_object_store_builder ( url, options, resolve_region) . await ?;
482507 Arc :: new ( builder. build ( ) ?)
483508 }
484509 "oss" => {
@@ -557,12 +582,14 @@ mod tests {
557582 let table_options = get_table_options ( & ctx, & sql) . await ;
558583 let aws_options = table_options. extensions . get :: < AwsOptions > ( ) . unwrap ( ) ;
559584 let builder =
560- get_s3_object_store_builder ( table_url. as_ref ( ) , aws_options) . await ?;
585+ get_s3_object_store_builder ( table_url. as_ref ( ) , aws_options, false ) . await ?;
561586
562587 // If the environment variables are set (as they are in CI) use them
563588 let expected_access_key_id = std:: env:: var ( "AWS_ACCESS_KEY_ID" ) . ok ( ) ;
564589 let expected_secret_access_key = std:: env:: var ( "AWS_SECRET_ACCESS_KEY" ) . ok ( ) ;
565- let expected_region = std:: env:: var ( "AWS_REGION" ) . ok ( ) ;
590+ let expected_region = Some (
591+ std:: env:: var ( "AWS_REGION" ) . unwrap_or_else ( |_| "eu-central-1" . to_string ( ) ) ,
592+ ) ;
566593 let expected_endpoint = std:: env:: var ( "AWS_ENDPOINT" ) . ok ( ) ;
567594
568595 // get the actual configuration information, then assert_eq!
@@ -624,7 +651,7 @@ mod tests {
624651 let table_options = get_table_options ( & ctx, & sql) . await ;
625652 let aws_options = table_options. extensions . get :: < AwsOptions > ( ) . unwrap ( ) ;
626653 let builder =
627- get_s3_object_store_builder ( table_url. as_ref ( ) , aws_options) . await ?;
654+ get_s3_object_store_builder ( table_url. as_ref ( ) , aws_options, false ) . await ?;
628655 // get the actual configuration information, then assert_eq!
629656 let config = [
630657 ( AmazonS3ConfigKey :: AccessKeyId , access_key_id) ,
@@ -667,7 +694,7 @@ mod tests {
667694
668695 let table_options = get_table_options ( & ctx, & sql) . await ;
669696 let aws_options = table_options. extensions . get :: < AwsOptions > ( ) . unwrap ( ) ;
670- let err = get_s3_object_store_builder ( table_url. as_ref ( ) , aws_options)
697+ let err = get_s3_object_store_builder ( table_url. as_ref ( ) , aws_options, false )
671698 . await
672699 . unwrap_err ( ) ;
673700
@@ -686,7 +713,55 @@ mod tests {
686713
687714 let aws_options = table_options. extensions . get :: < AwsOptions > ( ) . unwrap ( ) ;
688715 // ensure this isn't an error
689- get_s3_object_store_builder ( table_url. as_ref ( ) , aws_options) . await ?;
716+ get_s3_object_store_builder ( table_url. as_ref ( ) , aws_options, false ) . await ?;
717+
718+ Ok ( ( ) )
719+ }
720+
721+ #[ tokio:: test]
722+ async fn s3_object_store_builder_resolves_region_when_none_provided ( ) -> Result < ( ) > {
723+ let expected_region = "eu-central-1" ;
724+ let location = "s3://test-bucket/path/file.parquet" ;
725+
726+ let table_url = ListingTableUrl :: parse ( location) ?;
727+ let aws_options = AwsOptions {
728+ region : None , // No region specified - should auto-detect
729+ ..Default :: default ( )
730+ } ;
731+
732+ let builder =
733+ get_s3_object_store_builder ( table_url. as_ref ( ) , & aws_options, false ) . await ?;
734+
735+ // Verify that the region was auto-detected in test environment
736+ assert_eq ! (
737+ builder. get_config_value( & AmazonS3ConfigKey :: Region ) ,
738+ Some ( expected_region. to_string( ) )
739+ ) ;
740+
741+ Ok ( ( ) )
742+ }
743+
744+ #[ tokio:: test]
745+ async fn s3_object_store_builder_overrides_region_when_resolve_region_enabled (
746+ ) -> Result < ( ) > {
747+ let original_region = "us-east-1" ;
748+ let expected_region = "eu-central-1" ; // This should be the auto-detected region
749+ let location = "s3://test-bucket/path/file.parquet" ;
750+
751+ let table_url = ListingTableUrl :: parse ( location) ?;
752+ let aws_options = AwsOptions {
753+ region : Some ( original_region. to_string ( ) ) , // Explicit region provided
754+ ..Default :: default ( )
755+ } ;
756+
757+ let builder =
758+ get_s3_object_store_builder ( table_url. as_ref ( ) , & aws_options, true ) . await ?;
759+
760+ // Verify that the region was overridden by auto-detection
761+ assert_eq ! (
762+ builder. get_config_value( & AmazonS3ConfigKey :: Region ) ,
763+ Some ( expected_region. to_string( ) )
764+ ) ;
690765
691766 Ok ( ( ) )
692767 }
0 commit comments