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 20c963945 feat: Add delete with recursive support for fs (#6827)
20c963945 is described below
commit 20c963945c2d2e2063651134e40ffbae49953c08
Author: Xuanwo <[email protected]>
AuthorDate: Thu Nov 27 20:12:19 2025 +0800
feat: Add delete with recursive support for fs (#6827)
Signed-off-by: Xuanwo <[email protected]>
---
core/src/layers/simulate.rs | 18 +++++++++++++-----
core/src/services/fs/backend.rs | 1 +
core/src/services/fs/deleter.rs | 12 ++++++++++--
core/tests/behavior/async_delete.rs | 35 +++++++++++++++++++++++++++++++++++
4 files changed, 59 insertions(+), 7 deletions(-)
diff --git a/core/src/layers/simulate.rs b/core/src/layers/simulate.rs
index 3f8139eb8..1d64d6408 100644
--- a/core/src/layers/simulate.rs
+++ b/core/src/layers/simulate.rs
@@ -319,11 +319,19 @@ impl<A: Access, D> SimulateDeleter<A, D> {
impl<A: Access, D: oio::Delete> oio::Delete for SimulateDeleter<A, D> {
async fn delete(&mut self, path: &str, args: OpDelete) -> Result<()> {
- if args.recursive() && self.accessor.config.delete_recursive {
- return self
- .accessor
- .simulate_delete_with_recursive(&mut self.deleter, path, args)
- .await;
+ if args.recursive() {
+ let cap = self.accessor.info.native_capability();
+
+ if cap.delete_with_recursive {
+ return self.deleter.delete(path, args).await;
+ }
+
+ if self.accessor.config.delete_recursive {
+ return self
+ .accessor
+ .simulate_delete_with_recursive(&mut self.deleter, path,
args)
+ .await;
+ }
}
self.deleter.delete(path, args).await
diff --git a/core/src/services/fs/backend.rs b/core/src/services/fs/backend.rs
index 8f6ddb417..923ce2cda 100644
--- a/core/src/services/fs/backend.rs
+++ b/core/src/services/fs/backend.rs
@@ -153,6 +153,7 @@ impl Builder for FsBuilder {
create_dir: true,
delete: true,
+ delete_with_recursive: true,
list: true,
diff --git a/core/src/services/fs/deleter.rs b/core/src/services/fs/deleter.rs
index cd896aae9..bf8eeecd6 100644
--- a/core/src/services/fs/deleter.rs
+++ b/core/src/services/fs/deleter.rs
@@ -32,15 +32,23 @@ impl FsDeleter {
}
impl oio::OneShotDelete for FsDeleter {
- async fn delete_once(&self, path: String, _: OpDelete) -> Result<()> {
+ async fn delete_once(&self, path: String, args: OpDelete) -> Result<()> {
let p = self.core.root.join(path.trim_end_matches('/'));
+ let recursive = args.recursive();
+
let meta = tokio::fs::metadata(&p).await;
match meta {
Ok(meta) => {
if meta.is_dir() {
- tokio::fs::remove_dir(&p).await.map_err(new_std_io_error)?;
+ if recursive {
+ tokio::fs::remove_dir_all(&p)
+ .await
+ .map_err(new_std_io_error)?;
+ } else {
+
tokio::fs::remove_dir(&p).await.map_err(new_std_io_error)?;
+ }
} else {
tokio::fs::remove_file(&p).await.map_err(new_std_io_error)?;
}
diff --git a/core/tests/behavior/async_delete.rs
b/core/tests/behavior/async_delete.rs
index e2b141727..625496075 100644
--- a/core/tests/behavior/async_delete.rs
+++ b/core/tests/behavior/async_delete.rs
@@ -39,6 +39,9 @@ pub fn tests(op: &Operator, tests: &mut Vec<Trial>) {
test_batch_delete,
test_batch_delete_with_version
));
+ if cap.delete_with_recursive {
+ tests.extend(async_trials!(op, test_delete_with_recursive_basic));
+ }
if cap.list_with_recursive {
tests.extend(async_trials!(op, test_remove_all_basic));
if !cap.create_dir {
@@ -276,6 +279,38 @@ pub async fn test_delete_with_not_existing_version(op:
Operator) -> Result<()> {
Ok(())
}
+pub async fn test_delete_with_recursive_basic(op: Operator) -> Result<()> {
+ if !op.info().full_capability().delete_with_recursive {
+ return Ok(());
+ }
+
+ let base = format!("delete_recursive_{}/", uuid::Uuid::new_v4());
+
+ let files = [
+ format!("{base}file.txt"),
+ format!("{base}dir1/file1.txt"),
+ format!("{base}dir1/dir2/file2.txt"),
+ ];
+
+ for path in &files {
+ op.write(path, "delete recursive").await?;
+ }
+
+ op.delete_with(&base).recursive(true).await?;
+
+ let mut l = op.lister_with(&base).recursive(true).await?;
+ assert!(
+ l.try_next().await?.is_none(),
+ "all entries should be removed"
+ );
+
+ for path in &files {
+ assert!(!op.exists(path).await?, "{path} should be removed");
+ }
+
+ Ok(())
+}
+
pub async fn test_batch_delete(op: Operator) -> Result<()> {
let mut cap = op.info().full_capability();
if cap.delete_max_size.unwrap_or(1) <= 1 {