OpenDAL 中 recursive 参数的演进:从 delimiter 到递归列表(RFC 3526)
数据存储后端【免费下载链接】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),仅供参考

相关新闻

职业学校网站开发实战:Java+Vue+Spring Boot全解析

职业学校网站开发实战:Java+Vue+Spring Boot全解析

简介:职业学校网站源码是一套通用型网站模板,适用于职业院校、技术学校、大学及各类企业官网,帮助建站者省去从零设计页面布局与视觉风格的重复劳动,从而快速生成可用的站点框架。压缩包以rar格式打包,整体大小11.41MB…

2026/10/13 14:52:12 阅读更多 →
picocli Java 8 兼容性测试子项目剖析:Optional 类型支持与多版本测试隔离策略

picocli Java 8 兼容性测试子项目剖析:Optional 类型支持与多版本测试隔离策略

开发工具 【免费下载链接】picocli Picocli is a modern framework for building powerful, user-friendly, GraalVM-enabled command line apps with ease. It supports colors, autocompletion, subcommands, and more. In 1 source file so apps can include as source &…

2026/10/13 14:52:12 阅读更多 →
SSD vs HDD存储选型(进阶篇)——工程师必备知识

SSD vs HDD存储选型(进阶篇)——工程师必备知识

本文深入探讨SSD vs HDD存储选型(进阶篇),涵盖背景分析、原理剖析、实战步骤、配置示例、优化建议和避坑指南。随着业务规模增长,SSD vs HDD存储选型(进阶篇)的重要性日益凸显。无论你是刚入门还是资深工程…

2026/10/13 14:52:12 阅读更多 →

最新新闻

电商盗图风险:搜索引擎下载的图片用于商品主图是否侵权

电商盗图风险:搜索引擎下载的图片用于商品主图是否侵权

在电商运营与内容创作中,许多从业者习惯通过搜索引擎直接保存图片作为商品主图或详情页素材。然而,搜索引擎仅作为信息索引工具,并不拥有图片版权,也未获得转授权许可。将来源不明的网络图片直接用于商业展示、广告投放或产品包装…

2026/10/13 15:37:33 阅读更多 →
5人销售团队用AI智能体每月省600小时:OpenRouter架构与实践

5人销售团队用AI智能体每月省600小时:OpenRouter架构与实践

先看一组我们自己算出来时都愣了一下的数字:5个人的销售团队,靠一套内部代号叫 Rasp 的 AI 智能体,每个月省下约600小时的等效工时。当时这个数字报上去,运营负责人第一反应是“你们是不是把等待时间也算进去了”。我说对&#xf…

2026/10/13 15:37:33 阅读更多 →
LiteLLM:统一LLM API调用的轻量级抽象层实战指南

LiteLLM:统一LLM API调用的轻量级抽象层实战指南

1. 项目概述:LiteLLM 是什么,它解决的到底是什么问题? LiteLLM 这个名字刚出现时,我第一反应是“又一个 LLM 封装库?”——毕竟市面上类似工具已经不少,从 LangChain 到 LlamaIndex,再到各种自研…

2026/10/13 15:37:33 阅读更多 →
Open-Code-Review:一种可落地的开源协作契约范式

Open-Code-Review:一种可落地的开源协作契约范式

1. 这不是代码审查工具,而是一套可落地的开源协作契约“open-code-review”这个标题乍看像某个新出的 GitHub Action 或 CLI 工具,但实际翻遍主流包管理器、GitHub Trending 和 Hacker News 近三个月热帖,都找不到一个叫这个名字的成熟项目。…

2026/10/13 15:37:33 阅读更多 →
artcraft:基于Canvas的轻量级创意工具架构与实现

artcraft:基于Canvas的轻量级创意工具架构与实现

1. 项目缘起与整体设计思路1.1 为什么会有 artcraft 这个想法做创意工具这些年,我越来越强烈地感受到一个矛盾:市面上要么是功能极其复杂、学习曲线陡峭的专业软件,要么是简单到只能做几个固定模板的在线工具。中间地带几乎是空白的——那种你…

2026/10/13 15:37:33 阅读更多 →
Docker备份迁移选型指南:save/load、export/import、commit全解析

Docker备份迁移选型指南:save/load、export/import、commit全解析

接手 Docker 环境迁移,或者在给线上容器做备份方案时,很多人第一反应是“把容器打个包带走”。真正到了目标服务器上才发现,docker export导出来的东西和docker save导出来的东西完全不是一回事,而docker commit又像是另一种玄学。…

2026/10/13 15:36:30 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →