
数据库OLAP嵌入式数据库数据分析【免费下载链接】duckdbDuckDB is an analytical in-process SQL database management system项目地址https://gitcode.com/GitHub_Trending/du/duckdb点击查看免费下载DuckDB 的 C API 在 api_spec/VERSIONING.md 中定义了一套完整的声明式版本化方案所有 API 符号函数、类型、回调都带有一个按日期堆叠的生命周期lifecycle并据此决定何时出现在duckdb.h中、何时稳定、何时弃用或移除。本文从生命周期状态机讲起依次说明直接链接libduckdb的消费者如何用版本宏做按版本取 API的版本门控以及 C 扩展如何通过版本化 v-table 结构实现 ABI 前缀兼容、向前加载与固定版本锁定最后解析重命名、移除与版本宏转发等边界规则并给出仓库内的源码与脚本依据。为什么需要一套声明的版本化方案DuckDB 的 C API 有两个头文件面向直接消费者的 src/include/duckdb.h以及面向 C 扩展开发者的 src/include/duckdb_extension.h。这两个头文件都不是手写的而是由 api_spec/v1/ 下的 YAML 规范经 capigen 生成器统一生成见 scripts/capi_v1_regen.sh 与 api_spec/README.md。这一设计带来一个关键好处两个头文件共享同一条记录在规范中的生命周期历史因此二者永远不会对某个符号何时出现、何时稳定产生分歧。直接使用duckdb.h的消费者和通过duckdb_extension.h使用扩展 API 的开发者面向某个版本取到的 API 是一致的唯一的差别在于扩展还额外要求固定的 ABI 布局v-table 中函数指针的槽位这一点下文会专门展开。生命周期Lifecycle每个符号的四态历史规范中每一个 API 条目都携带一叠带日期的状态迁移记录最新的状态在最上面lifecycle: - [ stable, v1.5.6, 2026-07-30 ] - [ unstable, v1.4.0, 2025-09-12 ]当前共有四种状态语义如下状态含义unstable已存在但未承诺可能在任何时候改变甚至消失stable已承诺从此冻结签名与 ABI 槽位deprecated仍承诺可用但已排入移除计划调用方应迁移removed已从库中消失仓库中的真实生命周期示例随处可见。例如 api_spec/v1/common/types.yaml 中blob类型在v0.2.52021-03-10稳定bit类型在v1.2.02025-02-05稳定api_spec/v1/arrow/arrow.yaml 中多个 Arrow 符号在v1.0.02024-05-29被标记为deprecatedapi_spec/v1/appender/appender.yaml 中也有符号在v1.4.02025-09-15弃用。在 api_spec/v1/metadata.yaml 中四种状态的可见性策略被显式声明lifecycle_states: stable: visibility: always deprecated: visibility: opt_out guard: DUCKDB_API_NO_DEPRECATED unstable: visibility: opt_in guard: DUCKDB_EXTENSION_API_VERSION_UNSTABLE这里always始终可见、opt_out默认可见、可关闭、opt_in默认隐藏、需显式打开直接对应下文要讲的版本宏默认值。值得注意的是 metadata 中的注释说明当前已没有处于unstable的符号此前所有通过DUCKDB_EXTENSION_API_VERSION_UNSTABLE选择加入的符号都已稳定进 v1.5.6但该状态仍被保留用于支撑 174 条经过它的历史——这正是让目标是 v1.5.6 之前版本的消费者能够重新选择加入这些符号的机制。每个函数都必须声明生命周期。原因在于函数在扩展 v-table 中的槽位由它稳定时的版本若未稳定则在末尾的 unstable 段决定一个没有日期的函数因此不存在确定的槽位位置。这也是版本化能落地的根本前提。直接消费者duckdb.h用宏做版本门控客户端库或直接链接libduckdb的应用程序属于此类。对于它们声明本身没有必须保持的内在顺序因此可以按符号独立进行版本门控。控制可见符号的宏有三个DUCKDB_API_VERSION_MAJOR/_MINOR/_PATCH要瞄准的 API 版本默认取头文件描述的最新版本当前为 1.5.6见 src/include/duckdb.h。三个必须全定义或全不定义。声明只有在截至该版本已稳定时才出现即给我版本 X 时的 API。DUCKDB_API_ALLOW_DEPRECATED默认1。设为0后任何相对你的目标版本已弃用的符号都会消失编译器就能帮你找出剩余的所有旧用法。弃用是相对目标而言的一个在 v1.5.6 弃用的符号对瞄准 v1.5.4 的构建仍然可见。DUCKDB_API_ALLOW_UNSTABLE默认0。设为1可显示尚未稳定的符号代价是接受它们可能变化。这要求目标必须是最新版本原因见下节。为向后兼容旧的DUCKDB_API_NO_DEPRECATED与DUCKDB_EXTENSION_API_VERSION_UNSTABLE宏仍然有效它们只是分别设置新的DUCKDB_API_ALLOW_DEPRECATED/DUCKDB_API_ALLOW_UNSTABLE宏见 src/include/duckdb.h 的默认值推导逻辑。Unstable 本身是一个独立的版本一个符号从它被稳定的版本开始对外发布而不是从它被引入的版本。在稳定之前它不属于任何已发布的版本签名仍可改变。如果既要求旧目标版本、又要求 unstable 表面就会在该版本从未承诺过的名字下拿到今天的签名——这正是版本 X 时的 API唯一不成立的场景。因此二者互斥#if DUCKDB_API_ALLOW_UNSTABLE !DUCKDB_API_VERSION_AT_LEAST(1, 5, 6) #error the unstable surface requires targeting the newest API version #endif这段保护逻辑就实实在在写在生成的 src/include/duckdb.h 中。它同样被DUCKDB_EXTENSION_API_VERSION_AT_LEAST版本的语义继承。由此对两类消费者都只留下一条规则选一个版本拿到截至该版本已稳定的表面签名冻结、弃用状态相对该版本选 unstable拿到最新版本的全部符号含不稳定符号不提供任何承诺。因此任何门控#if永远只读取DUCKDB_API_VERSION_AT_LEAST(稳定版本)或者对尚未稳定的符号使用裸开关DUCKDB_API_ALLOW_UNSTABLE。扩展duckdb_extension.h版本化的 v-table 与 ABI扩展与直接消费者有本质区别非静态链接的扩展并不直接链接引擎符号。它收到一个函数指针结构体v-table所有调用都经由一组间接层宏indirection macros穿透该结构。扩展加载时这个结构体会被拷贝进一个全局静态变量duckdb_ext_api *res; /* copies sizeof(the extensions struct) */对应的初始化宏DUCKDB_EXTENSION_API_INIT定义在扩展头模板 api_spec/v1/extension/duckdb_extension.h.in 中它通过access-get_api(info, minimum_api_version)拿到结构体指针并逐字节拷贝。这个拷贝之所以成立前提是扩展期望的结构体必须是 DuckDB 实际传入结构体的前缀prefix——因此结构体的布局是 ABI 的一部分不能依赖任何引擎看不到的东西。这正是 scripts/check_extension_abi.py 存在的原因。v-table 结构体按版本分带bandv-table 中的函数按被稳定的版本而非被引入的版本划分成连续的带band每个带只在单一门控下发出typedef struct { /* band v1.2.0 — 404 slots, always present */ #if DUCKDB_API_VERSION_AT_LEAST(1, 5, 6) /* band v1.5.6 — 142 slots */ #endif #if DUCKDB_API_ALLOW_UNSTABLE /* any future unstable functions, always at the end */ #endif } duckdb_ext_api_v1;瞄准旧版本因此会把结构体截断到该版本当时实际发布的槽位数量。由于 Extension-C-API 结构体在v1.2.0才首次引入v-table 中 v1.2.0 带始终存在共 404 个槽位v1.5.6 带 142 个槽位任何早于它的目标都会是编译错误而非空结构体——这一保护也写在模板中见 api_spec/v1/extension/duckdb_extension.h.in。scripts/check_extension_abi.py负责针对每个发布标签验证这一前缀性质它用cc -E -DDUCKDB_EXTENSION_API_VERSION_MAJOR...预处理出固定到某版本的槽位列表再与各 release tag 上实际随引擎发布的结构体对比确保每个被该引擎接受的扩展 pin 都是引擎结构体的前缀见 scripts/check_extension_abi.py 与主循环 scripts/check_extension_abi.py。注意脚本需要本地存在 release tag缺失时会提示跳过并以 0 退出避免浅克隆误报失败。同样是两种选择这次由构建系统替你选上一节Unstable 是一个独立的版本的规则在扩展场景完全适用选版本或选 unstable。区别在于扩展自己不设置宏且这个选择不仅决定它能看见哪些符号还决定哪些 DuckDB 版本能加载它。所以由 CMake 构建辅助函数来做决定Versioned版本化可向前加载forward-loadablebuild_loadable_extension_capi(my_ext 1 5 6 ${SOURCES})ABI 类型为C_STRUCT。你声明所需的最低 API 版本这隐式设置了DUCKDB_EXTENSION_API_VERSION_*宏。任何等于或高于该版本的 DuckDB 都能加载你的扩展你拿到截至目标版本的 v-table 前缀不会更多。要点钉住包含所需功能的最低版本v1.2.0 与 v1.5.6 之间没有新符号稳定因此中间任何一个 pin 都产生同样的 404 个槽位却白白排除了更早的 DuckDBDUCKDB_API_ALLOW_UNSTABLE按前述规则不可用DUCKDB_API_ALLOW_DEPRECATED仍然有效禁用它去掉的是名字间接层宏而不是槽位因此可以在不扰动 v-table 布局的前提下隐藏符号。Pinned固定锁定到特定 DuckDB 版本build_loadable_extension_capi_unstable(my_ext ${SOURCES})ABI 类型为C_STRUCT_UNSTABLE。你不能设置任何版本宏你拿到完整的 v-table 结构体外加尚未稳定的尾部。DuckDB 只会在与你构建时完全相同的版本中加载该扩展——这一约束由扩展 metadata footer 中记录的版本来强制。这种构建向get_api报告的是确切的版本身份release tag或开发构建的 git commit hash而非语义化版本该值取自构建系统设置的DUCKDB_EXTENSION_API_VERSION_UNSTABLE见模板中的DUCKDB_EXTENSION_API_VERSION_STRING推导api_spec/v1/extension/duckdb_extension.h.in。为什么不能既要 unstable 符号又要钉版本通用原因前面已说不稳定的签名未冻结任何版本都无法承诺它。但对扩展还有一个额外的重要原因unstable 尾部位于所有版本带之后其槽位偏移依赖前面所有稳定版本带先编译进来。若一个扩展钉住 v1.2.0 却又编译了尾部它的第一个尾部槽位会落在索引 404而 DuckDB 实际放在 546——每一次 unstable 调用都会静默地穿过错误的指针。duckdb.h已发出的互斥守卫#error正是为了阻止这种情况。这在实践中没有任何代价唯一能触达尾部的构建本来就被锁定到特定 DuckDB 版本。这也再次确认槽位在符号稳定时冻结而非引入unstable时冻结。函数处于 unstable 期间只会被锁定到单一版本的构建观察到因此仍可被重排、改签名或整体删除一旦设为stable槽位永久固定。对 C-API 新增函数的工程含义是一个开发中发现有问题的函数必须在它的stable版本发布之前修好否则将永久占据一个槽位。重命名Renames保持 ABI、放弃源码兼容应尽可能避免重命名函数但仓库历史上已经发生过几次。重命名的符号保留槽位与签名只改变拼写因此 ABI 兼容但不源码兼容。规范中这样记录create_bignum: renamed_from: { name: create_varint, version: v1.4.0 }仓库真实案例见 api_spec/v1/value/value.yamlcreate_varint→create_bignum、api_spec/v1/value/value.yamlget_varint→get_bignum以及 api_spec/v1/common/types.yaml类型varint→bignum均为 v1.4.0。旧拼写随后以门控在该版本BELOW的别名发出类型用typedef、函数用#define。这样它只在你瞄准的版本仍包含它时可见一旦瞄准了执行重命名的版本就消失。这些别名存放在duckdb.h中扩展头包含它之后其自身的映射宏链会穿过该别名因此重命名后的函数仍通过 v-table 解析而不是解析到库符号。scripts/check_extension_abi.py直接从生成的头文件的// Renamed constructs兼容区读回这些别名见 scripts/check_extension_abi.py在对比前把旧名映射回新名——这样一次重命名不会被误读为后续所有槽位都发生了位移且未来再有重命名也无需改动检查脚本。移除Removal槽位保留、名字消失一个removed函数必须保留它的槽位以免后续偏移发生移动但它会失去映射宏因此名字不再编译。DuckDB 可以自由地把对应函数指针留为NULL。需要特别强调的是移除是唯一能破坏已经构建好的扩展的操作——旧扩展的槽位索引已固化遇到更新的 DuckDB 时该槽位是NULL调用会直接崩溃而非编译失败。版本化机制对此无能为力因为扩展的编译早于移除动作。因此文档给出的工程建议是优先选择弃用deprecation——它在运行时零成本槽位保持填充、旧二进制继续工作、新构建得到编译错误。版本宏转发Forwarding让两个头文件始终锁步duckdb_extension.h在包含duckdb.h之前先解析DUCKDB_EXTENSION_API_VERSION_*并转发为DUCKDB_API_VERSION_*若直接消费者宏未定义则把DUCKDB_EXTENSION_API_VERSION_MAJOR/MINOR/PATCH原样转发见 api_spec/v1/extension/duckdb_extension.h.in若两者都被显式定义且不一致则报错#errorapi_spec/v1/extension/duckdb_extension.h.in。若不转发duckdb.h会声明最新的表面而映射宏却跟随扩展的旧目标一个映射宏缺失的名字会悄悄解析到duckdb.h里的真实声明使可加载扩展直接引用引擎符号——这正是扩展绝不允许发生的事。转发使二者保持锁步这样的名字会变成编译错误。实战如何选择与验证综合全文直接消费者与扩展开发者的决策路径可以总结为直接消费者默认用最新头文件需要兼容旧引擎时定义全部三个DUCKDB_API_VERSION_*宏瞄准旧版本需要强制淘汰旧用法时设DUCKDB_API_ALLOW_DEPRECATED0只有明确接受无承诺时才设DUCKDB_API_ALLOW_UNSTABLE1且必须瞄准最新版本。扩展开发者尽量选择 versioned 构建build_loadable_extension_capi钉住所需功能的最低稳定版本以获得最大兼容面向前加载只有需要尚未稳定的新符号时才使用 pinned 构建build_loadable_extension_capi_unstable并接受只能被完全相同版本加载的限制。验证 ABI在本地包含 release tag 的仓库中运行python3 scripts/check_extension_abi.py [--cc cc]确认每个 pin 都是所有接受它的引擎结构体的合法前缀脚本用法见 scripts/check_extension_abi.py。新增函数按 api_spec/README.md 的流程在模块 YAML 中声明带lifecycle的条目必要时把新版本加入 api_spec/v1/metadata.yaml 的versions再通过make generate-files或./scripts/capi_v1_regen.sh、uv run --project api_spec --group generate ./scripts/capi_v1_regen.sh需 Python 3.12重新生成头文件CI 会校验提交的头文件与规范一致。这套从单一规范源到双头文件生成再到ABI 前缀校验脚本的闭环正是 DuckDB C API 能同时服务直接消费者与扩展生态、并在多年版本演进中保持稳定 ABI 的基础设施。赞分享数据库OLAP嵌入式数据库数据分析【免费下载链接】duckdbDuckDB is an analytical in-process SQL database management system项目地址https://gitcode.com/GitHub_Trending/du/duckdb点击查看免费下载相关推荐SponsorBlock扩展版本控制策略语义化版本与发布周期SponsorBlock扩展版本控制策略语义化版本与发布周期 你是否曾困惑于浏览器扩展的版本号究竟代表什么含义为什么有时更新仅修复小问题有时却带来全新功能前端Ollama版本控制策略语义化版本与发布周期深度解析Ollama版本控制策略语义化版本与发布周期深度解析 想要在本地高效运行Llama 2等大型语言模型Ollama作为业界领先的本地AI模型管理工具其精心设人工智能大模型模型推理服务本地部署Azure Linux容器镜像生命周期版本控制与清理策略Azure Linux容器镜像生命周期版本控制与清理策略 为什么容器镜像管理至关重要 在云原生应用部署中容器镜像的生命周期管理直接影响系统稳定性、安全性和操作系统云原生容器上一篇高可用Redis集群终极指南Redisson复制模式实现主从自动切换的完整教程下一篇Mesh Navigation核心架构解析分层网格地图如何改变机器人导航创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考