This is an automated email from the ASF dual-hosted git repository.
xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git
The following commit(s) were added to refs/heads/main by this push:
new d53142ba9 feat(services/azdls): add recursive deletion for azdls
(#7142)
d53142ba9 is described below
commit d53142ba94fe997dca366887af2b1f15559e7966
Author: meteorgan <[email protected]>
AuthorDate: Mon Jan 12 22:02:49 2026 +0800
feat(services/azdls): add recursive deletion for azdls (#7142)
* feat(services/azdls): add recursive deletion for azdls
* set paginated=true only when hns is enabled
* fix azdls config default value
---
core/services/azdls/src/backend.rs | 10 +++++++
core/services/azdls/src/config.rs | 5 ++++
core/services/azdls/src/core.rs | 57 ++++++++++++++++++++++++++++++++++++++
core/services/azdls/src/deleter.rs | 8 ++++--
4 files changed, 78 insertions(+), 2 deletions(-)
diff --git a/core/services/azdls/src/backend.rs
b/core/services/azdls/src/backend.rs
index 19ef30188..7f6120a78 100644
--- a/core/services/azdls/src/backend.rs
+++ b/core/services/azdls/src/backend.rs
@@ -218,6 +218,12 @@ impl AzdlsBuilder {
Ok(AzdlsConfig::from(config).into_builder())
}
+
+ /// Enable or disable HNS (Hierarchical Namespace) for this backend.
+ pub fn enable_hns(mut self, enable: bool) -> Self {
+ self.config.enable_hns = enable;
+ self
+ }
}
impl Builder for AzdlsBuilder {
@@ -282,7 +288,10 @@ impl Builder for AzdlsBuilder {
write_with_if_not_exists: true,
create_dir: true,
+
delete: true,
+ delete_with_recursive: true,
+
rename: true,
list: true,
@@ -297,6 +306,7 @@ impl Builder for AzdlsBuilder {
filesystem: self.config.filesystem.clone(),
root,
endpoint,
+ enable_hns: self.config.enable_hns,
loader: cred_loader,
signer,
}),
diff --git a/core/services/azdls/src/config.rs
b/core/services/azdls/src/config.rs
index f05c87705..b2b805132 100644
--- a/core/services/azdls/src/config.rs
+++ b/core/services/azdls/src/config.rs
@@ -25,6 +25,7 @@ use super::backend::AzdlsBuilder;
/// Azure Data Lake Storage Gen2 Support.
#[derive(Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
+#[serde(default)]
pub struct AzdlsConfig {
/// Root of this backend.
pub root: Option<String>,
@@ -58,6 +59,10 @@ pub struct AzdlsConfig {
/// - required for client_credentials authentication
/// - default value: `https://login.microsoftonline.com`
pub authority_host: Option<String>,
+ /// Whether hierarchical namespace (HNS) is enabled for the storage
account.
+ /// When enabled, recursive deletion can use pagination to avoid timeouts
on large directories.
+ /// - default value: `false`
+ pub enable_hns: bool,
}
impl Debug for AzdlsConfig {
diff --git a/core/services/azdls/src/core.rs b/core/services/azdls/src/core.rs
index f40cfc636..cc8d27622 100644
--- a/core/services/azdls/src/core.rs
+++ b/core/services/azdls/src/core.rs
@@ -38,6 +38,7 @@ use opendal_core::*;
const X_MS_RENAME_SOURCE: &str = "x-ms-rename-source";
const X_MS_VERSION: &str = "x-ms-version";
pub const X_MS_VERSION_ID: &str = "x-ms-version-id";
+const X_MS_CONTINUATION: &str = "x-ms-continuation";
pub const DIRECTORY: &str = "directory";
pub const FILE: &str = "file";
@@ -46,6 +47,7 @@ pub struct AzdlsCore {
pub filesystem: String,
pub root: String,
pub endpoint: String,
+ pub enable_hns: bool,
pub loader: AzureStorageLoader,
pub signer: AzureStorageSigner,
@@ -57,6 +59,7 @@ impl Debug for AzdlsCore {
.field("filesystem", &self.filesystem)
.field("root", &self.root)
.field("endpoint", &self.endpoint)
+ .field("enable_hns", &self.enable_hns)
.finish_non_exhaustive()
}
}
@@ -366,6 +369,60 @@ impl AzdlsCore {
self.send(req).await
}
+ pub async fn azdls_recursive_delete(&self, path: &str) ->
Result<Response<Buffer>> {
+ let p = build_abs_path(&self.root, path)
+ .trim_end_matches('/')
+ .to_string();
+
+ let base = format!(
+ "{}/{}/{}",
+ self.endpoint,
+ self.filesystem,
+ percent_encode_path(&p)
+ );
+
+ let mut continuation = String::new();
+
+ loop {
+ let mut url = QueryPairsWriter::new(&base).push("recursive",
"true");
+
+ if self.enable_hns {
+ url = url.push("paginated", "true");
+ }
+
+ if !continuation.is_empty() {
+ url = url.push("continuation",
&percent_encode_path(&continuation));
+ }
+
+ let mut req = Request::delete(url.finish())
+ .extension(Operation::Delete)
+ .body(Buffer::new())
+ .map_err(new_request_build_error)?;
+
+ self.sign(&mut req).await?;
+ let resp = self.send(req).await?;
+
+ let status = resp.status();
+ match status {
+ StatusCode::OK | StatusCode::ACCEPTED | StatusCode::NOT_FOUND
=> {}
+ _ => return Err(parse_error(resp)),
+ }
+
+ let next = resp
+ .headers()
+ .get(X_MS_CONTINUATION)
+ .and_then(|v| v.to_str().ok())
+ .unwrap_or_default()
+ .trim();
+
+ if next.is_empty() {
+ return Ok(resp);
+ }
+
+ continuation = next.to_string();
+ }
+ }
+
pub async fn azdls_list(
&self,
path: &str,
diff --git a/core/services/azdls/src/deleter.rs
b/core/services/azdls/src/deleter.rs
index f90c87c8a..56dc359be 100644
--- a/core/services/azdls/src/deleter.rs
+++ b/core/services/azdls/src/deleter.rs
@@ -35,8 +35,12 @@ impl AzdlsDeleter {
}
impl oio::OneShotDelete for AzdlsDeleter {
- async fn delete_once(&self, path: String, _: OpDelete) -> Result<()> {
- let resp = self.core.azdls_delete(&path).await?;
+ async fn delete_once(&self, path: String, args: OpDelete) -> Result<()> {
+ let resp = if args.recursive() {
+ self.core.azdls_recursive_delete(&path).await?
+ } else {
+ self.core.azdls_delete(&path).await?
+ };
let status = resp.status();