This is an automated email from the ASF dual-hosted git repository.
kou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new 605f8a792c GH-40028: [C++][FS][Azure] Add AzureFileSystem support to
FileSystemFromUri() (#40325)
605f8a792c is described below
commit 605f8a792c388afb2230b1f19e0f3e4df90d5abe
Author: Sutou Kouhei <[email protected]>
AuthorDate: Tue Mar 12 06:34:58 2024 +0900
GH-40028: [C++][FS][Azure] Add AzureFileSystem support to
FileSystemFromUri() (#40325)
### Rationale for this change
`FileSystemFromUri()` is a common API to create a file system object.
`FileSystemFromUri()` should be able to create an `AzureFileSystem` object.
### What changes are included in this PR?
Add `AzureOptions::FromUri()` and use it from `FileSystemFromUri()`.
See the `AzureOptions::FromUri()`'s docstring about the supported formats.
### Are these changes tested?
Yes.
### Are there any user-facing changes?
Yes.
* GitHub Issue: #40028
Authored-by: Sutou Kouhei <[email protected]>
Signed-off-by: Sutou Kouhei <[email protected]>
---
cpp/src/arrow/filesystem/azurefs.cc | 166 ++++++++++++++++++++++++++
cpp/src/arrow/filesystem/azurefs.h | 56 +++++++++
cpp/src/arrow/filesystem/azurefs_test.cc | 196 +++++++++++++++++++++++++++++++
cpp/src/arrow/filesystem/filesystem.cc | 18 ++-
4 files changed, 433 insertions(+), 3 deletions(-)
diff --git a/cpp/src/arrow/filesystem/azurefs.cc
b/cpp/src/arrow/filesystem/azurefs.cc
index ff078f78ae..dd9fb817b7 100644
--- a/cpp/src/arrow/filesystem/azurefs.cc
+++ b/cpp/src/arrow/filesystem/azurefs.cc
@@ -65,6 +65,172 @@ AzureOptions::AzureOptions() = default;
AzureOptions::~AzureOptions() = default;
+void AzureOptions::ExtractFromUriSchemeAndHierPart(const arrow::internal::Uri&
uri,
+ std::string* out_path) {
+ const auto host = uri.host();
+ std::string path;
+ if (arrow::internal::EndsWith(host, blob_storage_authority)) {
+ account_name = host.substr(0, host.size() - blob_storage_authority.size());
+ path = internal::RemoveLeadingSlash(uri.path());
+ } else if (arrow::internal::EndsWith(host, dfs_storage_authority)) {
+ account_name = host.substr(0, host.size() - dfs_storage_authority.size());
+ path = internal::ConcatAbstractPath(uri.username(), uri.path());
+ } else {
+ account_name = uri.username();
+ const auto port_text = uri.port_text();
+ if (host.find(".") == std::string::npos && port_text.empty()) {
+ // abfs://container/dir/file
+ path = internal::ConcatAbstractPath(host, uri.path());
+ } else {
+ // abfs://host.domain/container/dir/file
+ // abfs://host.domain:port/container/dir/file
+ // abfs://host:port/container/dir/file
+ std::string host_port = host;
+ if (!port_text.empty()) {
+ host_port += ":" + port_text;
+ }
+ blob_storage_authority = host_port;
+ dfs_storage_authority = host_port;
+ path = internal::RemoveLeadingSlash(uri.path());
+ }
+ }
+ if (out_path != nullptr) {
+ *out_path = path;
+ }
+}
+
+Status AzureOptions::ExtractFromUriQuery(const arrow::internal::Uri& uri) {
+ const auto account_key = uri.password();
+ std::optional<CredentialKind> credential_kind;
+ std::optional<std::string> credential_kind_value;
+ std::string tenant_id;
+ std::string client_id;
+ std::string client_secret;
+ ARROW_ASSIGN_OR_RAISE(const auto options_items, uri.query_items());
+ for (const auto& kv : options_items) {
+ if (kv.first == "blob_storage_authority") {
+ blob_storage_authority = kv.second;
+ } else if (kv.first == "dfs_storage_authority") {
+ dfs_storage_authority = kv.second;
+ } else if (kv.first == "credential_kind") {
+ if (kv.second == "default") {
+ credential_kind = CredentialKind::kDefault;
+ } else if (kv.second == "anonymous") {
+ credential_kind = CredentialKind::kAnonymous;
+ } else if (kv.second == "workload_identity") {
+ credential_kind = CredentialKind::kWorkloadIdentity;
+ } else {
+ // Other credential kinds should be inferred from the given
+ // parameters automatically.
+ return Status::Invalid("Unexpected credential_kind: '", kv.second,
"'");
+ }
+ credential_kind_value = kv.second;
+ } else if (kv.first == "tenant_id") {
+ tenant_id = kv.second;
+ } else if (kv.first == "client_id") {
+ client_id = kv.second;
+ } else if (kv.first == "client_secret") {
+ client_secret = kv.second;
+ } else if (kv.first == "enable_tls") {
+ ARROW_ASSIGN_OR_RAISE(auto enable_tls,
::arrow::internal::ParseBoolean(kv.second));
+ if (enable_tls) {
+ blob_storage_scheme = "https";
+ dfs_storage_scheme = "https";
+ } else {
+ blob_storage_scheme = "http";
+ dfs_storage_scheme = "http";
+ }
+ } else {
+ return Status::Invalid(
+ "Unexpected query parameter in Azure Blob File System URI: '",
kv.first, "'");
+ }
+ }
+
+ if (credential_kind) {
+ if (!account_key.empty()) {
+ return Status::Invalid("Password must not be specified with
credential_kind=",
+ *credential_kind_value);
+ }
+ if (!tenant_id.empty()) {
+ return Status::Invalid("tenant_id must not be specified with
credential_kind=",
+ *credential_kind_value);
+ }
+ if (!client_id.empty()) {
+ return Status::Invalid("client_id must not be specified with
credential_kind=",
+ *credential_kind_value);
+ }
+ if (!client_secret.empty()) {
+ return Status::Invalid("client_secret must not be specified with
credential_kind=",
+ *credential_kind_value);
+ }
+
+ switch (*credential_kind) {
+ case CredentialKind::kAnonymous:
+ RETURN_NOT_OK(ConfigureAnonymousCredential());
+ break;
+ case CredentialKind::kWorkloadIdentity:
+ RETURN_NOT_OK(ConfigureWorkloadIdentityCredential());
+ break;
+ default:
+ // Default credential
+ break;
+ }
+ } else {
+ if (!account_key.empty()) {
+ // With password
+ if (!tenant_id.empty()) {
+ return Status::Invalid("tenant_id must not be specified with
password");
+ }
+ if (!client_id.empty()) {
+ return Status::Invalid("client_id must not be specified with
password");
+ }
+ if (!client_secret.empty()) {
+ return Status::Invalid("client_secret must not be specified with
password");
+ }
+ RETURN_NOT_OK(ConfigureAccountKeyCredential(account_key));
+ } else {
+ // Without password
+ if (tenant_id.empty() && client_id.empty() && client_secret.empty()) {
+ // No related parameters
+ if (account_name.empty()) {
+ RETURN_NOT_OK(ConfigureAnonymousCredential());
+ } else {
+ // Default credential
+ }
+ } else {
+ // One or more tenant_id, client_id or client_secret are specified
+ if (client_id.empty()) {
+ return Status::Invalid("client_id must be specified");
+ }
+ if (tenant_id.empty() && client_secret.empty()) {
+ RETURN_NOT_OK(ConfigureManagedIdentityCredential(client_id));
+ } else if (!tenant_id.empty() && !client_secret.empty()) {
+ RETURN_NOT_OK(
+ ConfigureClientSecretCredential(tenant_id, client_id,
client_secret));
+ } else {
+ return Status::Invalid("Both of tenant_id and client_secret must be
specified");
+ }
+ }
+ }
+ }
+ return Status::OK();
+}
+
+Result<AzureOptions> AzureOptions::FromUri(const arrow::internal::Uri& uri,
+ std::string* out_path) {
+ AzureOptions options;
+ options.ExtractFromUriSchemeAndHierPart(uri, out_path);
+ RETURN_NOT_OK(options.ExtractFromUriQuery(uri));
+ return options;
+}
+
+Result<AzureOptions> AzureOptions::FromUri(const std::string& uri_string,
+ std::string* out_path) {
+ arrow::internal::Uri uri;
+ RETURN_NOT_OK(uri.Parse(uri_string));
+ return FromUri(uri, out_path);
+}
+
bool AzureOptions::Equals(const AzureOptions& other) const {
// TODO(GH-38598): update here when more auth methods are added.
const bool equals = blob_storage_authority == other.blob_storage_authority &&
diff --git a/cpp/src/arrow/filesystem/azurefs.h
b/cpp/src/arrow/filesystem/azurefs.h
index 2a131e40c0..6218bf574e 100644
--- a/cpp/src/arrow/filesystem/azurefs.h
+++ b/cpp/src/arrow/filesystem/azurefs.h
@@ -45,6 +45,7 @@ class DataLakeServiceClient;
namespace arrow::fs {
class TestAzureFileSystem;
+class TestAzureOptions;
/// Options for the AzureFileSystem implementation.
///
@@ -59,6 +60,8 @@ class TestAzureFileSystem;
///
/// Functions are provided for explicit configuration of credentials if that
is preferred.
struct ARROW_EXPORT AzureOptions {
+ friend class TestAzureOptions;
+
/// \brief The name of the Azure Storage Account being accessed.
///
/// All service URLs will be constructed using this storage account name.
@@ -123,6 +126,59 @@ struct ARROW_EXPORT AzureOptions {
AzureOptions();
~AzureOptions();
+ private:
+ void ExtractFromUriSchemeAndHierPart(const arrow::internal::Uri& uri,
+ std::string* out_path);
+ Status ExtractFromUriQuery(const arrow::internal::Uri& uri);
+
+ public:
+ /// \brief Construct a new AzureOptions from an URI.
+ ///
+ /// Supported formats:
+ ///
+ /// 1. abfs[s]://[:\<password\>@]\<account\>.blob.core.windows.net
+ /// [/\<container\>[/\<path\>]]
+ /// 2.
abfs[s]://\<container\>[:\<password\>]@\<account\>.dfs.core.windows.net
+ /// [/path]
+ /// 3. abfs[s]://[\<account[:\<password\>]@]\<host[.domain]\>[\<:port\>]
+ /// [/\<container\>[/path]]
+ /// 4. abfs[s]://[\<account[:\<password\>]@]\<container\>[/path]
+ ///
+ /// 1. and 2. are compatible with the Azure Data Lake Storage Gen2 URIs:
+ ///
https://learn.microsoft.com/en-us/azure/storage/blobs/data-lake-storage-introduction-abfs-uri
+ ///
+ /// 3. is for Azure Blob Storage compatible service including Azurite.
+ ///
+ /// 4. is a shorter version of 1. and 2.
+ ///
+ /// Note that there is no difference between abfs and abfss. HTTPS is
+ /// used with abfs by default. You can force to use HTTP by specifying
+ /// "enable_tls=false" query.
+ ///
+ /// Supported query parameters:
+ ///
+ /// * blob_storage_authority: Set AzureOptions::blob_storage_authority
+ /// * dfs_storage_authority: Set AzureOptions::dfs_storage_authority
+ /// * enable_tls: If it's "false" or "0", HTTP not HTTPS is used.
+ /// * credential_kind: One of "default", "anonymous",
+ /// "workload_identity". If "default" is specified, it's just
+ /// ignored. If "anonymous" is specified,
+ /// AzureOptions::ConfigureAnonymousCredential() is called. If
+ /// "workload_identity" is specified,
+ /// AzureOptions::ConfigureWorkloadIdentityCredential() is called.
+ /// * tenant_id: You must specify "client_id" and "client_secret"
+ /// too. AzureOptions::ConfigureClientSecretCredential() is called.
+ /// * client_id: If you don't specify "tenant_id" and
+ /// "client_secret",
+ /// AzureOptions::ConfigureManagedIdentityCredential() is
+ /// called. If you specify "tenant_id" and "client_secret" too,
+ /// AzureOptions::ConfigureClientSecretCredential() is called.
+ /// * client_secret: You must specify "tenant_id" and "client_id"
+ /// too. AzureOptions::ConfigureClientSecretCredential() is called.
+ static Result<AzureOptions> FromUri(const arrow::internal::Uri& uri,
+ std::string* out_path);
+ static Result<AzureOptions> FromUri(const std::string& uri, std::string*
out_path);
+
Status ConfigureDefaultCredential();
Status ConfigureAnonymousCredential();
Status ConfigureAccountKeyCredential(const std::string& account_key);
diff --git a/cpp/src/arrow/filesystem/azurefs_test.cc
b/cpp/src/arrow/filesystem/azurefs_test.cc
index f21876f03c..0ce84043a5 100644
--- a/cpp/src/arrow/filesystem/azurefs_test.cc
+++ b/cpp/src/arrow/filesystem/azurefs_test.cc
@@ -336,6 +336,202 @@ TEST(AzureFileSystem, OptionsCompare) {
EXPECT_TRUE(options.Equals(options));
}
+class TestAzureOptions : public ::testing::Test {
+ public:
+ void TestFromUriBlobStorage() {
+ AzureOptions default_options;
+ std::string path;
+ ASSERT_OK_AND_ASSIGN(
+ auto options,
+
AzureOptions::FromUri("abfs://account.blob.core.windows.net/container/dir/blob",
+ &path));
+ ASSERT_EQ(options.account_name, "account");
+ ASSERT_EQ(options.blob_storage_authority,
default_options.blob_storage_authority);
+ ASSERT_EQ(options.dfs_storage_authority,
default_options.dfs_storage_authority);
+ ASSERT_EQ(options.blob_storage_scheme,
default_options.blob_storage_scheme);
+ ASSERT_EQ(options.dfs_storage_scheme, default_options.dfs_storage_scheme);
+ ASSERT_EQ(options.credential_kind_,
AzureOptions::CredentialKind::kDefault);
+ ASSERT_EQ(path, "container/dir/blob");
+ }
+
+ void TestFromUriDfsStorage() {
+ AzureOptions default_options;
+ std::string path;
+ ASSERT_OK_AND_ASSIGN(
+ auto options,
+
AzureOptions::FromUri("abfs://[email protected]/dir/file",
+ &path));
+ ASSERT_EQ(options.account_name, "account");
+ ASSERT_EQ(options.blob_storage_authority,
default_options.blob_storage_authority);
+ ASSERT_EQ(options.dfs_storage_authority,
default_options.dfs_storage_authority);
+ ASSERT_EQ(options.blob_storage_scheme,
default_options.blob_storage_scheme);
+ ASSERT_EQ(options.dfs_storage_scheme, default_options.dfs_storage_scheme);
+ ASSERT_EQ(options.credential_kind_,
AzureOptions::CredentialKind::kDefault);
+ ASSERT_EQ(path, "file_system/dir/file");
+ }
+
+ void TestFromUriAbfs() {
+ std::string path;
+ ASSERT_OK_AND_ASSIGN(
+ auto options,
+ AzureOptions::FromUri(
+ "abfs://account:[email protected]:10000/container/dir/blob",
&path));
+ ASSERT_EQ(options.account_name, "account");
+ ASSERT_EQ(options.blob_storage_authority, "127.0.0.1:10000");
+ ASSERT_EQ(options.dfs_storage_authority, "127.0.0.1:10000");
+ ASSERT_EQ(options.blob_storage_scheme, "https");
+ ASSERT_EQ(options.dfs_storage_scheme, "https");
+ ASSERT_EQ(options.credential_kind_,
AzureOptions::CredentialKind::kStorageSharedKey);
+ ASSERT_EQ(path, "container/dir/blob");
+ }
+
+ void TestFromUriAbfss() {
+ std::string path;
+ ASSERT_OK_AND_ASSIGN(
+ auto options,
+ AzureOptions::FromUri(
+ "abfss://account:[email protected]:10000/container/dir/blob",
&path));
+ ASSERT_EQ(options.account_name, "account");
+ ASSERT_EQ(options.blob_storage_authority, "127.0.0.1:10000");
+ ASSERT_EQ(options.dfs_storage_authority, "127.0.0.1:10000");
+ ASSERT_EQ(options.blob_storage_scheme, "https");
+ ASSERT_EQ(options.dfs_storage_scheme, "https");
+ ASSERT_EQ(options.credential_kind_,
AzureOptions::CredentialKind::kStorageSharedKey);
+ ASSERT_EQ(path, "container/dir/blob");
+ }
+
+ void TestFromUriEnableTls() {
+ std::string path;
+ ASSERT_OK_AND_ASSIGN(auto options,
+ AzureOptions::FromUri(
+
"abfs://account:[email protected]:10000/container/dir/blob?"
+ "enable_tls=false",
+ &path));
+ ASSERT_EQ(options.account_name, "account");
+ ASSERT_EQ(options.blob_storage_authority, "127.0.0.1:10000");
+ ASSERT_EQ(options.dfs_storage_authority, "127.0.0.1:10000");
+ ASSERT_EQ(options.blob_storage_scheme, "http");
+ ASSERT_EQ(options.dfs_storage_scheme, "http");
+ ASSERT_EQ(options.credential_kind_,
AzureOptions::CredentialKind::kStorageSharedKey);
+ ASSERT_EQ(path, "container/dir/blob");
+ }
+
+ void TestFromUriCredentialDefault() {
+ ASSERT_OK_AND_ASSIGN(
+ auto options,
+
AzureOptions::FromUri("abfs://account.blob.core.windows.net/container/dir/blob?"
+ "credential_kind=default",
+ nullptr));
+ ASSERT_EQ(options.credential_kind_,
AzureOptions::CredentialKind::kDefault);
+ }
+
+ void TestFromUriCredentialAnonymous() {
+ ASSERT_OK_AND_ASSIGN(
+ auto options,
+
AzureOptions::FromUri("abfs://account.blob.core.windows.net/container/dir/blob?"
+ "credential_kind=anonymous",
+ nullptr));
+ ASSERT_EQ(options.credential_kind_,
AzureOptions::CredentialKind::kAnonymous);
+ }
+
+ void TestFromUriCredentialStorageSharedKey() {
+ ASSERT_OK_AND_ASSIGN(
+ auto options,
+ AzureOptions::FromUri(
+
"abfs://:[email protected]/container/dir/blob",
+ nullptr));
+ ASSERT_EQ(options.credential_kind_,
AzureOptions::CredentialKind::kStorageSharedKey);
+ }
+
+ void TestFromUriCredentialClientSecret() {
+ ASSERT_OK_AND_ASSIGN(
+ auto options,
+
AzureOptions::FromUri("abfs://account.blob.core.windows.net/container/dir/blob?"
+ "tenant_id=tenant-id&"
+ "client_id=client-id&"
+ "client_secret=client-secret",
+ nullptr));
+ ASSERT_EQ(options.credential_kind_,
AzureOptions::CredentialKind::kClientSecret);
+ }
+
+ void TestFromUriCredentialManagedIdentity() {
+ ASSERT_OK_AND_ASSIGN(
+ auto options,
+
AzureOptions::FromUri("abfs://account.blob.core.windows.net/container/dir/blob?"
+ "client_id=client-id",
+ nullptr));
+ ASSERT_EQ(options.credential_kind_,
AzureOptions::CredentialKind::kManagedIdentity);
+ }
+
+ void TestFromUriCredentialWorkloadIdentity() {
+ ASSERT_OK_AND_ASSIGN(
+ auto options,
+
AzureOptions::FromUri("abfs://account.blob.core.windows.net/container/dir/blob?"
+ "credential_kind=workload_identity",
+ nullptr));
+ ASSERT_EQ(options.credential_kind_,
AzureOptions::CredentialKind::kWorkloadIdentity);
+ }
+
+ void TestFromUriCredentialInvalid() {
+ ASSERT_RAISES(Invalid, AzureOptions::FromUri(
+
"abfs://[email protected]/dir/file?"
+ "credential_kind=invalid",
+ nullptr));
+ }
+ void TestFromUriBlobStorageAuthority() {
+ ASSERT_OK_AND_ASSIGN(
+ auto options,
+
AzureOptions::FromUri("abfs://account.blob.core.windows.net/container/dir/blob?"
+ "blob_storage_authority=.blob.local",
+ nullptr));
+ ASSERT_EQ(options.blob_storage_authority, ".blob.local");
+ }
+
+ void TestFromUriDfsStorageAuthority() {
+ ASSERT_OK_AND_ASSIGN(
+ auto options,
+
AzureOptions::FromUri("abfs://[email protected]/dir/file?"
+ "dfs_storage_authority=.dfs.local",
+ nullptr));
+ ASSERT_EQ(options.dfs_storage_authority, ".dfs.local");
+ }
+
+ void TestFromUriInvalidQueryParameter() {
+ ASSERT_RAISES(Invalid, AzureOptions::FromUri(
+
"abfs://[email protected]/dir/file?"
+ "unknown=invalid",
+ nullptr));
+ }
+};
+
+TEST_F(TestAzureOptions, FromUriBlobStorage) { TestFromUriBlobStorage(); }
+TEST_F(TestAzureOptions, FromUriDfsStorage) { TestFromUriDfsStorage(); }
+TEST_F(TestAzureOptions, FromUriAbfs) { TestFromUriAbfs(); }
+TEST_F(TestAzureOptions, FromUriAbfss) { TestFromUriAbfss(); }
+TEST_F(TestAzureOptions, FromUriEnableTls) { TestFromUriEnableTls(); }
+TEST_F(TestAzureOptions, FromUriCredentialDefault) {
TestFromUriCredentialDefault(); }
+TEST_F(TestAzureOptions, FromUriCredentialAnonymous) {
TestFromUriCredentialAnonymous(); }
+TEST_F(TestAzureOptions, FromUriCredentialStorageSharedKey) {
+ TestFromUriCredentialStorageSharedKey();
+}
+TEST_F(TestAzureOptions, FromUriCredentialClientSecret) {
+ TestFromUriCredentialClientSecret();
+}
+TEST_F(TestAzureOptions, FromUriCredentialManagedIdentity) {
+ TestFromUriCredentialManagedIdentity();
+}
+TEST_F(TestAzureOptions, FromUriCredentialWorkloadIdentity) {
+ TestFromUriCredentialWorkloadIdentity();
+}
+TEST_F(TestAzureOptions, FromUriCredentialInvalid) {
TestFromUriCredentialInvalid(); }
+TEST_F(TestAzureOptions, FromUriBlobStorageAuthority) {
+ TestFromUriBlobStorageAuthority();
+}
+TEST_F(TestAzureOptions, FromUriDfsStorageAuthority) {
TestFromUriDfsStorageAuthority(); }
+TEST_F(TestAzureOptions, FromUriInvalidQueryParameter) {
+ TestFromUriInvalidQueryParameter();
+}
+
struct PreexistingData {
public:
using RNG = random::pcg32_fast;
diff --git a/cpp/src/arrow/filesystem/filesystem.cc
b/cpp/src/arrow/filesystem/filesystem.cc
index 810e9c179b..1fb74d4129 100644
--- a/cpp/src/arrow/filesystem/filesystem.cc
+++ b/cpp/src/arrow/filesystem/filesystem.cc
@@ -21,12 +21,15 @@
#include "arrow/util/config.h"
#include "arrow/filesystem/filesystem.h"
-#ifdef ARROW_HDFS
-#include "arrow/filesystem/hdfs.h"
+#ifdef ARROW_AZURE
+#include "arrow/filesystem/azurefs.h"
#endif
#ifdef ARROW_GCS
#include "arrow/filesystem/gcsfs.h"
#endif
+#ifdef ARROW_HDFS
+#include "arrow/filesystem/hdfs.h"
+#endif
#ifdef ARROW_S3
#include "arrow/filesystem/s3fs.h"
#endif
@@ -690,6 +693,16 @@ Result<std::shared_ptr<FileSystem>>
FileSystemFromUriReal(const Uri& uri,
}
return std::make_shared<LocalFileSystem>(options, io_context);
}
+ if (scheme == "abfs" || scheme == "abfss") {
+#ifdef ARROW_AZURE
+ ARROW_ASSIGN_OR_RAISE(auto options, AzureOptions::FromUri(uri, out_path));
+ return AzureFileSystem::Make(options, io_context);
+#else
+ return Status::NotImplemented(
+ "Got Azure Blob File System URI but Arrow compiled without Azure Blob
File "
+ "System support");
+#endif
+ }
if (scheme == "gs" || scheme == "gcs") {
#ifdef ARROW_GCS
ARROW_ASSIGN_OR_RAISE(auto options, GcsOptions::FromUri(uri, out_path));
@@ -698,7 +711,6 @@ Result<std::shared_ptr<FileSystem>>
FileSystemFromUriReal(const Uri& uri,
return Status::NotImplemented("Got GCS URI but Arrow compiled without GCS
support");
#endif
}
-
if (scheme == "hdfs" || scheme == "viewfs") {
#ifdef ARROW_HDFS
ARROW_ASSIGN_OR_RAISE(auto options, HdfsOptions::FromUri(uri));