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 7864bd864 feat: Add simulation for delete with recursive (#6824)
7864bd864 is described below
commit 7864bd8647da76fb673d70a366af12236f8300d7
Author: Xuanwo <[email protected]>
AuthorDate: Thu Nov 27 19:22:09 2025 +0800
feat: Add simulation for delete with recursive (#6824)
* feat: Add simulation for delete with recursive
Signed-off-by: Xuanwo <[email protected]>
* remove not needed pub
Signed-off-by: Xuanwo <[email protected]>
* Remove not needed stat
Signed-off-by: Xuanwo <[email protected]>
* Fix build
Signed-off-by: Xuanwo <[email protected]>
* Make node happy
Signed-off-by: Xuanwo <[email protected]>
---------
Signed-off-by: Xuanwo <[email protected]>
---
bindings/nodejs/generated.d.ts | 2 +
bindings/nodejs/src/options.rs | 3 ++
core/src/layers/correctness_check.rs | 8 +++
core/src/layers/simulate.rs | 83 ++++++++++++++++++++++++++++-
core/src/raw/ops.rs | 13 +++++
core/src/types/capability.rs | 2 +
core/src/types/delete/deleter.rs | 3 ++
core/src/types/delete/input.rs | 6 +++
core/src/types/operator/operator_futures.rs | 6 +++
core/src/types/options.rs | 6 +++
10 files changed, 130 insertions(+), 2 deletions(-)
diff --git a/bindings/nodejs/generated.d.ts b/bindings/nodejs/generated.d.ts
index 68ba74179..77ddf0705 100644
--- a/bindings/nodejs/generated.d.ts
+++ b/bindings/nodejs/generated.d.ts
@@ -1051,6 +1051,8 @@ export declare class Writer {
export interface DeleteOptions {
version?: string
+ /** Whether to delete recursively. */
+ recursive?: boolean
}
export declare const enum EntryMode {
diff --git a/bindings/nodejs/src/options.rs b/bindings/nodejs/src/options.rs
index 8331d393a..34576cf83 100644
--- a/bindings/nodejs/src/options.rs
+++ b/bindings/nodejs/src/options.rs
@@ -508,12 +508,15 @@ impl From<WriteOptions> for
opendal::options::WriteOptions {
#[derive(Default)]
pub struct DeleteOptions {
pub version: Option<String>,
+ /// Whether to delete recursively.
+ pub recursive: Option<bool>,
}
impl From<DeleteOptions> for opendal::options::DeleteOptions {
fn from(value: DeleteOptions) -> Self {
Self {
version: value.version,
+ recursive: value.recursive.unwrap_or_default(),
}
}
}
diff --git a/core/src/layers/correctness_check.rs
b/core/src/layers/correctness_check.rs
index ca24fe11c..57b8dada4 100644
--- a/core/src/layers/correctness_check.rs
+++ b/core/src/layers/correctness_check.rs
@@ -234,6 +234,14 @@ impl<T> CheckWrapper<T> {
));
}
+ if args.recursive() &&
!self.info.full_capability().delete_with_recursive {
+ return Err(new_unsupported_error(
+ &self.info,
+ Operation::Delete,
+ "recursive",
+ ));
+ }
+
Ok(())
}
}
diff --git a/core/src/layers/simulate.rs b/core/src/layers/simulate.rs
index ee340fc5c..3f8139eb8 100644
--- a/core/src/layers/simulate.rs
+++ b/core/src/layers/simulate.rs
@@ -20,6 +20,7 @@ use std::fmt::Formatter;
use std::sync::Arc;
use crate::raw::oio::FlatLister;
+use crate::raw::oio::List;
use crate::raw::oio::PrefixLister;
use crate::raw::*;
use crate::*;
@@ -30,6 +31,7 @@ pub struct SimulateLayer {
list_recursive: bool,
stat_dir: bool,
create_dir: bool,
+ delete_recursive: bool,
}
impl Default for SimulateLayer {
@@ -38,6 +40,7 @@ impl Default for SimulateLayer {
list_recursive: true,
stat_dir: true,
create_dir: true,
+ delete_recursive: true,
}
}
}
@@ -60,6 +63,12 @@ impl SimulateLayer {
self.create_dir = enabled;
self
}
+
+ /// Enable or disable recursive delete simulation. Default: true.
+ pub fn with_delete_recursive(mut self, enabled: bool) -> Self {
+ self.delete_recursive = enabled;
+ self
+ }
}
impl<A: Access> Layer<A> for SimulateLayer {
@@ -71,6 +80,9 @@ impl<A: Access> Layer<A> for SimulateLayer {
if self.create_dir && cap.list && cap.write_can_empty {
cap.create_dir = true;
}
+ if self.delete_recursive && cap.list && cap.delete {
+ cap.delete_with_recursive = true;
+ }
cap
});
@@ -206,6 +218,37 @@ impl<A: Access> SimulateAccessor<A> {
Ok((rp, lister))
}
+
+ pub(crate) async fn simulate_delete_with_recursive<D: oio::Delete>(
+ &self,
+ deleter: &mut D,
+ path: &str,
+ args: OpDelete,
+ ) -> Result<()> {
+ if !self.info.full_capability().delete_with_recursive {
+ return Err(Error::new(
+ ErrorKind::Unsupported,
+ "recursive delete is not supported",
+ ));
+ }
+
+ let non_recursive = args.clone().with_recursive(false);
+
+ let (_rp, mut lister) = self
+ .simulate_list(path, OpList::new().with_recursive(true))
+ .await?;
+
+ while let Some(entry) = lister.next().await? {
+ let entry = entry.into_entry();
+ let mut entry_args = non_recursive.clone();
+ if let Some(version) = entry.metadata().version() {
+ entry_args = entry_args.with_version(version);
+ }
+ deleter.delete(entry.path(), entry_args).await?;
+ }
+
+ Ok(())
+ }
}
impl<A: Access> LayeredAccess for SimulateAccessor<A> {
@@ -213,7 +256,7 @@ impl<A: Access> LayeredAccess for SimulateAccessor<A> {
type Reader = A::Reader;
type Writer = A::Writer;
type Lister = SimulateLister<A, A::Lister>;
- type Deleter = A::Deleter;
+ type Deleter = SimulateDeleter<A, A::Deleter>;
fn inner(&self) -> &Self::Inner {
&self.inner
@@ -240,7 +283,14 @@ impl<A: Access> LayeredAccess for SimulateAccessor<A> {
}
async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
- self.inner().delete().await
+ let (rp, deleter) = self.inner().delete().await?;
+ let accessor = SimulateAccessor {
+ config: self.config.clone(),
+ info: self.info.clone(),
+ inner: self.inner.clone(),
+ };
+
+ Ok((rp, SimulateDeleter::new(accessor, deleter)))
}
async fn list(&self, path: &str, args: OpList) -> Result<(RpList,
Self::Lister)> {
@@ -254,3 +304,32 @@ impl<A: Access> LayeredAccess for SimulateAccessor<A> {
pub type SimulateLister<A, P> =
FourWays<P, FlatLister<Arc<A>, P>, PrefixLister<P>,
PrefixLister<FlatLister<Arc<A>, P>>>;
+
+/// Deleter wrapper that simulates recursive deletion.
+pub struct SimulateDeleter<A: Access, D> {
+ accessor: SimulateAccessor<A>,
+ deleter: D,
+}
+
+impl<A: Access, D> SimulateDeleter<A, D> {
+ pub fn new(accessor: SimulateAccessor<A>, deleter: D) -> Self {
+ Self { accessor, deleter }
+ }
+}
+
+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;
+ }
+
+ self.deleter.delete(path, args).await
+ }
+
+ fn close(&mut self) -> impl Future<Output = Result<()>> + MaybeSend {
+ self.deleter.close()
+ }
+}
diff --git a/core/src/raw/ops.rs b/core/src/raw/ops.rs
index abd89be9e..117cbd586 100644
--- a/core/src/raw/ops.rs
+++ b/core/src/raw/ops.rs
@@ -43,6 +43,7 @@ impl OpCreateDir {
#[derive(Debug, Clone, Default, Eq, Hash, PartialEq)]
pub struct OpDelete {
version: Option<String>,
+ recursive: bool,
}
impl OpDelete {
@@ -59,16 +60,28 @@ impl OpDelete {
self
}
+ /// Change the recursive flag of this delete operation.
+ pub fn with_recursive(mut self, recursive: bool) -> Self {
+ self.recursive = recursive;
+ self
+ }
+
/// Get the version of this delete operation.
pub fn version(&self) -> Option<&str> {
self.version.as_deref()
}
+
+ /// Whether this delete should remove objects recursively.
+ pub fn recursive(&self) -> bool {
+ self.recursive
+ }
}
impl From<options::DeleteOptions> for OpDelete {
fn from(value: options::DeleteOptions) -> Self {
Self {
version: value.version,
+ recursive: value.recursive,
}
}
}
diff --git a/core/src/types/capability.rs b/core/src/types/capability.rs
index 89be64107..7c49eee18 100644
--- a/core/src/types/capability.rs
+++ b/core/src/types/capability.rs
@@ -143,6 +143,8 @@ pub struct Capability {
pub delete: bool,
/// Indicates if versions delete operations are supported.
pub delete_with_version: bool,
+ /// Indicates if recursive delete operations are supported.
+ pub delete_with_recursive: bool,
/// Maximum size supported for single delete operations.
pub delete_max_size: Option<usize>,
diff --git a/core/src/types/delete/deleter.rs b/core/src/types/delete/deleter.rs
index 05621f0f7..cee20eed3 100644
--- a/core/src/types/delete/deleter.rs
+++ b/core/src/types/delete/deleter.rs
@@ -99,6 +99,9 @@ impl Deleter {
if let Some(version) = &input.version {
op = op.with_version(version);
}
+ if input.recursive {
+ op = op.with_recursive(true);
+ }
self.deleter.delete_dyn(&input.path, op).await?;
Ok(())
diff --git a/core/src/types/delete/input.rs b/core/src/types/delete/input.rs
index 3530b29af..62982921a 100644
--- a/core/src/types/delete/input.rs
+++ b/core/src/types/delete/input.rs
@@ -26,6 +26,8 @@ pub struct DeleteInput {
pub path: String,
/// The version of the path to delete.
pub version: Option<String>,
+ /// Whether to perform recursive deletion.
+ pub recursive: bool,
}
/// IntoDeleteInput is a helper trait that makes it easier for users to play
with `Deleter`.
@@ -46,6 +48,7 @@ impl IntoDeleteInput for &str {
fn into_delete_input(self) -> DeleteInput {
DeleteInput {
path: self.to_string(),
+ recursive: false,
..Default::default()
}
}
@@ -56,6 +59,7 @@ impl IntoDeleteInput for String {
fn into_delete_input(self) -> DeleteInput {
DeleteInput {
path: self,
+ recursive: false,
..Default::default()
}
}
@@ -69,6 +73,7 @@ impl IntoDeleteInput for (String, OpDelete) {
let mut input = DeleteInput {
path,
+ recursive: args.recursive(),
..Default::default()
};
@@ -86,6 +91,7 @@ impl IntoDeleteInput for Entry {
let mut input = DeleteInput {
path,
+ recursive: false,
..Default::default()
};
diff --git a/core/src/types/operator/operator_futures.rs
b/core/src/types/operator/operator_futures.rs
index 1a1276bb1..4c67b6a5f 100644
--- a/core/src/types/operator/operator_futures.rs
+++ b/core/src/types/operator/operator_futures.rs
@@ -1258,6 +1258,12 @@ impl<F: Future<Output = Result<()>>> FutureDelete<F> {
self.args.version = Some(v.to_string());
self
}
+
+ /// Enable recursive deletion.
+ pub fn recursive(mut self, recursive: bool) -> Self {
+ self.args.recursive = recursive;
+ self
+ }
}
/// Future that generated by [`Operator::deleter_with`].
diff --git a/core/src/types/options.rs b/core/src/types/options.rs
index d6d81a5a8..f373dd5da 100644
--- a/core/src/types/options.rs
+++ b/core/src/types/options.rs
@@ -25,6 +25,12 @@ use std::collections::HashMap;
pub struct DeleteOptions {
/// The version of the file to delete.
pub version: Option<String>,
+ /// Whether to delete the target recursively.
+ ///
+ /// - If `false`, behaves like the traditional single-object delete.
+ /// - If `true`, all entries under the path (or sharing the prefix for
file-like paths)
+ /// will be removed.
+ pub recursive: bool,
}
/// Options for list operations.