@@ -32,15 +32,28 @@ use aws_config::BehaviorVersion;
32
32
use aws_credential_types:: provider:: error:: CredentialsError ;
33
33
use aws_credential_types:: provider:: { ProvideCredentials , SharedCredentialsProvider } ;
34
34
use log:: debug;
35
- use object_store:: aws:: { AmazonS3Builder , AwsCredential } ;
35
+ use object_store:: aws:: { AmazonS3Builder , AmazonS3ConfigKey , AwsCredential } ;
36
36
use object_store:: gcp:: GoogleCloudStorageBuilder ;
37
37
use object_store:: http:: HttpBuilder ;
38
38
use object_store:: { ClientOptions , CredentialProvider , ObjectStore } ;
39
39
use url:: Url ;
40
40
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
+
41
53
pub async fn get_s3_object_store_builder (
42
54
url : & Url ,
43
55
aws_options : & AwsOptions ,
56
+ resolve_region : bool ,
44
57
) -> Result < AmazonS3Builder > {
45
58
let AwsOptions {
46
59
access_key_id,
@@ -88,6 +101,16 @@ pub async fn get_s3_object_store_builder(
88
101
builder = builder. with_region ( region) ;
89
102
}
90
103
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
+
91
114
if let Some ( endpoint) = endpoint {
92
115
// Make a nicer error if the user hasn't allowed http and the endpoint
93
116
// is http as the default message is "URL scheme is not allowed"
@@ -470,6 +493,7 @@ pub(crate) async fn get_object_store(
470
493
scheme : & str ,
471
494
url : & Url ,
472
495
table_options : & TableOptions ,
496
+ resolve_region : bool ,
473
497
) -> Result < Arc < dyn ObjectStore > , DataFusionError > {
474
498
let store: Arc < dyn ObjectStore > = match scheme {
475
499
"s3" => {
@@ -478,7 +502,8 @@ pub(crate) async fn get_object_store(
478
502
"Given table options incompatible with the 's3' scheme"
479
503
) ;
480
504
} ;
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 ?;
482
507
Arc :: new ( builder. build ( ) ?)
483
508
}
484
509
"oss" => {
@@ -557,12 +582,14 @@ mod tests {
557
582
let table_options = get_table_options ( & ctx, & sql) . await ;
558
583
let aws_options = table_options. extensions . get :: < AwsOptions > ( ) . unwrap ( ) ;
559
584
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 ?;
561
586
562
587
// If the environment variables are set (as they are in CI) use them
563
588
let expected_access_key_id = std:: env:: var ( "AWS_ACCESS_KEY_ID" ) . ok ( ) ;
564
589
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
+ ) ;
566
593
let expected_endpoint = std:: env:: var ( "AWS_ENDPOINT" ) . ok ( ) ;
567
594
568
595
// get the actual configuration information, then assert_eq!
@@ -624,7 +651,7 @@ mod tests {
624
651
let table_options = get_table_options ( & ctx, & sql) . await ;
625
652
let aws_options = table_options. extensions . get :: < AwsOptions > ( ) . unwrap ( ) ;
626
653
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 ?;
628
655
// get the actual configuration information, then assert_eq!
629
656
let config = [
630
657
( AmazonS3ConfigKey :: AccessKeyId , access_key_id) ,
@@ -667,7 +694,7 @@ mod tests {
667
694
668
695
let table_options = get_table_options ( & ctx, & sql) . await ;
669
696
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 )
671
698
. await
672
699
. unwrap_err ( ) ;
673
700
@@ -686,7 +713,55 @@ mod tests {
686
713
687
714
let aws_options = table_options. extensions . get :: < AwsOptions > ( ) . unwrap ( ) ;
688
715
// 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
+ ) ;
690
765
691
766
Ok ( ( ) )
692
767
}
0 commit comments