数据存储后端【免费下载链接】opendalApache OpenDAL: One Layer, All Storage.项目地址https://gitcode.com/gh_mirrors/op/opendal点击查看免费下载Apache OpenDAL 的listAPI 曾以delimiter控制列表行为这一概念对不熟悉对象存储的用户颇为晦涩。RFC 3526list_recursive正是为解决该问题而提出用布尔参数recursive替代delimiter使列出当前目录与递归列出所有子条目两种语义在 API 层面一目了然。本文以该 RFC 为核心结合当前仓库源码完整梳理这一 API 变更的动机、迁移规则、底层两层 List 架构HierarchyLister/FlatLister、能力声明机制list_with_recursive与行为测试验证读完你可以理解recursive参数从用户 API 到服务后端的完整调用链。动机为什么 delimiter 是个糟糕的抽象RFC 原文3526_list_recursive.md指出OpenDAL 早期在list操作中引入了delimiter参数来区分两种遍历方式delimiter /以/作为路径分隔符行为类似只列出当前目录delimiter 不设置分隔符行为类似列出当前目录及其所有子项即递归。理想情况下用户甚至可以传入|、-、等任意字符作为分隔符但 OpenDAL 从未真正实现这一点。对不熟悉 S3 等对象存储 ListObjects 语义的用户来说传空字符串才能递归这一约定并不直观——用户必须事先知道delimiter 用于递归列出、delimiter /用于列出当前目录。RFC 的结论很直接不如直接使用recursive布尔值让 API 行为自解释。API 变更与迁移映射RFC 给出的变更承诺是默认行为不变继续使用op.list()的用户不受影响只有显式使用delimiter的用户需要迁移。原文给出的映射规则如下旧 API新 APIop.list_with(path).delimiter()op.list_with(path).recursive(true)op.list_with(path).delimiter(/)op.list_with(path).recursive(false)op.list_with(path).delimiter(other_value)不再支持仓库中的升级指南upgrade.md印证了这一迁移已完成并明确记录了 RFC 3526 落地后的 API 对照同时说明其他分隔符取值已被移除。recursive 的实际用法在当前仓库中recursive是ListOptions的一个字段options.rs/// Options for list operations. /// /// # Groups /// - Traversal: recursive. /// - Pagination: limit, start_after. /// - Versioning: versions, deleted (effective on version-aware backends). pub struct ListOptions { /// Maximum results per request (backend hint) to control memory and throttling. pub limit: Optionusize, /// The start_after passes to underlying service to specify the specified key /// to start listing from. pub start_after: OptionString, /// Whether to list recursively under the prefix; default false. pub recursive: bool, /// Include object versions when supported by the backend; default false. pub versions: bool, /// Include delete markers when supported by version-aware backends; default false. pub deleted: bool, }注意recursive的默认值为false即默认只列出当前目录层级的条目这与 RFC 中默认行为不变的承诺一致。用户侧有两种典型用法。其一是链式 builderOperator::list_with/lister_with见 operator.rs 中的文档示例use opendal_core::EntryMode; use opendal_core::Operator; // 递归列出 prefix 下的全部条目 let mut entries op.list_with(path/to/prefix).recursive(true).await?; for entry in entries { match entry.metadata().mode() { EntryMode::FILE println!(Handling file), EntryMode::DIR println!(Handling dir like start a new list via meta.path()), EntryMode::Unknown continue, } } // 流式遍历大结果集下推荐使用 lister 而非一次性 materialize let mut lister op.lister_with(path/to/dir/).recursive(true).await?;其二是显式传ListOptionslist_options/lister_optionsuse opendal_core::options; let mut entries op .list_options( path/to/prefix, options::ListOptions { recursive: true, ..Default::default() }, ) .await?;list_with一次性收集全部条目适合小结果集对大前缀则应使用lister_with流式消费这一点在Operator::list_with的文档注释中被明确标注For large listings, preferOperator::listerto stream entries。底层实现OpList 的位标志RFC 的 Reference-level explanation 承诺在OpList中新增recursive参数并移除所有delimiter相关字段。当前 ops.rs 中的实现与之一致OpList用一个u8位标志编码所有可选项其中OP_LIST_RECURSIVE是第 2 位const OP_LIST_HAS_LIMIT: u8 1; const OP_LIST_RECURSIVE: u8 1 1; const OP_LIST_VERSIONS: u8 1 2; const OP_LIST_DELETED: u8 1 3; impl OpList { /// Return whether the list operation is recursive. pub fn recursive(self) - bool { self.flags OP_LIST_RECURSIVE ! 0 } } impl Fromoptions::ListOptions for OpList { fn from(value: options::ListOptions) - Self { // ... if value.recursive { flags | OP_LIST_RECURSIVE; } // ... } }用户 API 层的ListOptions经由From转换下沉为OpList服务后端只需调用args.recursive()即可获知遍历模式。整个源码树中已搜索不到delimiter字段的残留印证了 RFC移除所有 delimiter 相关字段的落地。两层 List 架构HierarchyLister 与 FlatListerrecursive参数在核心层的意义是把服务只支持扁平前缀列表与用户想要目录树语义之间的鸿沟架起。OpenDAL 的核心实现提供了两个装饰器HierarchyLister非递归recursivefalseHierarchyLister 把一个扁平前缀扫描的 lister 转换成只返回当前层级的目录列表。其next()逻辑非常精炼async fn next(mut self) - ResultOptionoio::Entry { loop { let mut entry match self.lister.next().await? { Some(entry) entry, None return Ok(None), }; if self.recursive { return Ok(Some(entry)); // 递归模式原样透传 } if self.keep_entry(mut entry) { return Ok(Some(entry)); } } }也就是说当recursive true时HierarchyLister直接透传底层扫描结果当recursive false时keep_entry会用一个visited集合把深层条目折叠为一级虚拟目录——例如扫描到a/b/c但用户只列a/时返回a/b/而非a/b/c从而维持目录层级观感。以 memory 后端为例backend.rsfn list(self, _ctx: OperationContext, path: str, args: OpList) - ResultSelf::Lister { let p build_abs_path(self.root, path); let keys self.core.scan(p)?; let lister MemoryLister::new(self.root, keys); let lister oio::HierarchyLister::new(lister, path, args.recursive()); Ok(lister) }FlatLister递归recursivetrue的模拟实现FlatLister 则解决相反的问题对只支持单层目录列表的后端通过自底向上的深度遍历模拟出递归列表。它维护一个active_lister栈遇到目录就压栈、逐层下探输出顺序上保证嵌套目录先于父目录出现给定文件树: ├── dir_x/ │ ├── dir_y/ │ │ ├── dir_z/ │ │ └── file_c │ └── file_b └── file_a FlatLister 输出自底向上: dir_x/dir_y/dir_z/file_c dir_x/dir_y/dir_z/ dir_x/dir_y/file_b dir_x/dir_y/ dir_x/file_a dir_x/值得注意的是FlatLister的健壮性处理遍历过程中若某个子目录返回PermissionDenied或NotFound例如列表过程中目录被删除它会记录日志并跳过该目录而不是让整个递归列表失败。谁来选择哪条路径simulate 层与 Capability从源码结构看当前仓库中显式声明list_with_recursive: true的服务后端仅有 memorymemory/backend.rs这意味着大多数服务后端的递归列表能力由 simulate 层统一补齐。simulate.rs 中的simulate_list展示了三分支决策let lister match ( recursive, // 用户是否要求递归 cap.list_with_recursive, // 后端是否原生支持 self.config.list_recursive, // simulate 层是否启用递归模拟 ) { // 后端原生支持递归直接透传 (_, true, _) SimulateLister::One(self.srv.list(ctx, path, forward)?), // 需要递归但后端不支持用 FlatLister 自底向上模拟 (true, false, true) /* ServicerFlatLister ... */, // 需要递归但模拟被关闭透传并让后端自行报错 (true, false, false) SimulateLister::One(self.srv.list(ctx, path, forward)?), // 非递归列表走前缀处理语义 (false, false, _) /* PrefixLister 前缀过滤 ... */, };能力本身通过Capability声明用户可用op.info().capability().list_with_recursive查询capability.rs/// Indicates if recursive listing is supported. pub list_with_recursive: bool,这一机制让上层应用可以按需选择策略原生支持递归的服务走单次前缀扫描不支持的则由 OpenDAL 透明地以多次单层列表拼出结果。行为测试对递归语义的约束行为测试套件async_list.rs对recursive(true)定义了一组跨后端必须满足的语义包括test_list_dir_with_recursive递归列出dir/应返回dir/自身及所有子条目test_list_dir_with_recursive_no_trailing_slash路径不带尾部/时语义等价test_list_root_with_recursive对根路径递归列出test_list_file_with_recursive对文件路径递归列出test_list_non_exist_dir_with_recursive列出不存在的目录应返回空而非报错。这些测试用例与Capability联动注册——仅当cap.list_with_recursive为真时才执行递归相关断言体现了能力声明 行为契约的测试组织方式。未来可能性递归删除RFC 结尾的 Future possibilities 提出了为 delete 增加 recursive 支持并点名 azfile 这类有原生递归删除能力的服务。当前仓库中这一方向已经落地OpDelete携带OP_DELETE_RECURSIVE标志位ops.rs用户 API 对应op.delete_with(path).recursive(true)simulate 层的simulate_delete_with_recursive会在后端未声明delete_with_recursive能力时返回Unsupported行为测试async_delete.rs也仅在能力满足时注册test_delete_with_recursive_basic。可以看出RFC 3526 确立的recursive布尔语义已经从 list 场景自然延伸到了 delete 场景。小结RFC 3526 是一次典型的以用户心智模型重塑 API的演进语义显式化recursive(true/false)取代了需要对象存储背景知识才能理解的delimiter()/(/)实现落地OpList以位标志承载recursiveHierarchyLister负责非递归层级的目录折叠FlatLister负责在后端能力不足时模拟递归simulate 层依据Capability::list_with_recursive在两者间调度契约保障行为测试把递归列表的边界情况根路径、无前缀斜杠、不存在目录、文件路径固化为跨后端的统一契约模式复用同一recursive语义已扩展到delete_with形成贯穿 OpenDAL 各操作的一致遍历/作用域表达方式。对使用者的直接建议很简单默认op.list(path)/op.lister(path)即为非递归列表需要递归遍历时使用.recursive(true)并在关心后端能力时通过capability().list_with_recursive判断是否需要 OpenDAL 以模拟方式完成遍历。赞分享数据存储后端【免费下载链接】opendalApache OpenDAL: One Layer, All Storage.项目地址https://gitcode.com/gh_mirrors/op/opendal点击查看免费下载相关推荐Apache OpenDAL 目录列表能力演进从 ObjectStream RFC 到现代 Lister APIApache OpenDAL 目录列表能力演进从 ObjectStream RFC 到现代 Lister API 导读 本文以 OpenDAL 核心仓库中的数据存储后端OpenBao 递归列表SCAN与列表结果过滤从 RFC 到 API、ACL 与 CLI 的完整实战指南OpenBao 递归列表SCAN与列表结果过滤从 RFC 到 API、ACL 与 CLI 的完整实战指南 导读 OpenBao 在 v2.2.0 引入了后端认证鉴权密钥管理密码学Apache OpenDAL 错误体系重构RFC-0977从 std::io::Error 到自研 opendal::Error 的演进与实践Apache OpenDAL 错误体系重构RFC 0977从 std::io::Error 到自研 opendal::Error 的演进与实践 Apach数据存储后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考