diff --git a/src/iceberg/arrow/s3/arrow_s3_file_io.cc b/src/iceberg/arrow/s3/arrow_s3_file_io.cc index a38e86421..7ab182b8c 100644 --- a/src/iceberg/arrow/s3/arrow_s3_file_io.cc +++ b/src/iceberg/arrow/s3/arrow_s3_file_io.cc @@ -454,6 +454,10 @@ class CredentialStore { return ResolveCredential(default_properties_, credential); } + const std::unordered_map& default_properties() const { + return default_properties_; + } + /// \brief Make `credentials` current, and `provider` too when one is given. void Install(std::vector credentials, std::optional> provider) { @@ -754,6 +758,10 @@ class ArrowS3FileIO final : public FileIO, public SupportsStorageCredentials { const std::vector& storage_credentials, std::shared_ptr provider) override; + const std::unordered_map& properties() const override { + return credential_store_->default_properties(); + } + SupportsStorageCredentials* AsSupportsStorageCredentials() override { return this; } private: diff --git a/src/iceberg/file_io.h b/src/iceberg/file_io.h index 4dd96530d..faa7a6253 100644 --- a/src/iceberg/file_io.h +++ b/src/iceberg/file_io.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -181,6 +182,15 @@ class ICEBERG_EXPORT FileIO { /// \brief Return storage-credential support when implemented by this FileIO. virtual SupportsStorageCredentials* AsSupportsStorageCredentials() { return nullptr; } + + /// \brief Return the configuration used to create this FileIO. + /// + /// The returned map may contain credentials. Callers must not log or expose it. + /// Implementations that do not expose their configuration return an empty map. + virtual const std::unordered_map& properties() const { + static const std::unordered_map kEmpty; + return kEmpty; + } }; /// \brief Mix-in for FileIO implementations that route object paths to diff --git a/src/iceberg/resolving_file_io.h b/src/iceberg/resolving_file_io.h index 05e11eb8f..70762c462 100644 --- a/src/iceberg/resolving_file_io.h +++ b/src/iceberg/resolving_file_io.h @@ -73,6 +73,10 @@ class ICEBERG_EXPORT ResolvingFileIO final : public FileIO, const std::vector& storage_credentials, std::shared_ptr provider) override; + const std::unordered_map& properties() const override { + return properties_; + } + SupportsStorageCredentials* AsSupportsStorageCredentials() override { return this; } private: diff --git a/src/iceberg/test/arrow_s3_file_io_test.cc b/src/iceberg/test/arrow_s3_file_io_test.cc index 9a5eace0d..e397fee96 100644 --- a/src/iceberg/test/arrow_s3_file_io_test.cc +++ b/src/iceberg/test/arrow_s3_file_io_test.cc @@ -479,6 +479,18 @@ TEST_F(ArrowS3FileIOTest, StoresCredentials) { EXPECT_EQ(credentialed->credentials(), credentials); } +TEST_F(ArrowS3FileIOTest, PropertiesExposeFileIOConfiguration) { + auto result = MakeS3FileIO({{std::string(S3Properties::kClientRegion), "us-east-1"}, + {std::string(S3Properties::kAccessKeyId), "key"}, + {std::string(S3Properties::kSecretAccessKey), "secret"}}); + ASSERT_THAT(result, IsOk()); + + const auto& properties = result.value()->properties(); + EXPECT_EQ(properties.at(std::string(S3Properties::kClientRegion)), "us-east-1"); + EXPECT_EQ(properties.at(std::string(S3Properties::kAccessKeyId)), "key"); + EXPECT_EQ(properties.at(std::string(S3Properties::kSecretAccessKey)), "secret"); +} + TEST_F(ArrowS3FileIOTest, SkipsNonS3CredentialPrefix) { auto result = MakeS3FileIO({}); ASSERT_THAT(result, IsOk()); diff --git a/src/iceberg/test/rest_file_io_test.cc b/src/iceberg/test/rest_file_io_test.cc index 31a027ab4..9be2cdcf0 100644 --- a/src/iceberg/test/rest_file_io_test.cc +++ b/src/iceberg/test/rest_file_io_test.cc @@ -115,6 +115,18 @@ TEST(RestFileIOTest, DefaultResolverDelegatesThroughRegistry) { EXPECT_THAT(result.value()->DeleteFile("rest-test://file"), IsOk()); } +TEST(RestFileIOTest, DefaultResolverExposesMergedProperties) { + auto result = MakeTableFileIO({{"catalog-only", "catalog"}, {"shared", "catalog"}}, + {{"table-only", "table"}, {"shared", "table"}}, + /*storage_credentials=*/{}); + ASSERT_THAT(result, IsOk()); + + EXPECT_THAT(result.value()->properties(), + ::testing::UnorderedElementsAre(::testing::Pair("catalog-only", "catalog"), + ::testing::Pair("table-only", "table"), + ::testing::Pair("shared", "table"))); +} + TEST(RestFileIOTest, MakeCatalogFileIOPassesThroughCustomImpl) { const std::string custom_impl = "com.mycompany.CustomFileIO"; FileIORegistry::Register(