跳转至

扩展版本与兼容性

每个 DATUS 载荷都可以用 v 声明它按哪个版本编写,并在 requires 里列出旧引擎 不能丢弃的键,两者都在信封一节说明。任何引擎都能 报告自己实现的版本:dosi info、GET /v1/capabilities,或 dosi_engine.DATUS_EXT_VERSION。这一页讲版本号背后的规则,并记录每个版本加了什么。

版本策略

本引擎读取的每个键都登记了引入它的版本,以及消费方忽略它的代价。 这份登记表内置在引擎里,是唯一事实来源: 版本闸门、基础模式的告警文案、dosi info 和 GET /v1/capabilities 都在运行时读取它,所以它们谁也不可能与之漂移。下面的变更记录是照着这份登记表手写的。

忽略影响级别,按危险程度递增排列 —— 注意大声失败排在比悄悄返回不同数字更安全的位置:

影响 含义
inert 引擎从不消费的展示类元数据。结果完全相同。
degraded 不会出错误数字 —— 请求会大声失败(一个结构化错误,或某个查询名不再能解析)。
documented 数字会变,但变得可预期、方向有文档可依(LEFT 对 INNER,NULL 对 0)。
silent 数字是错的,而调用方无从察觉。必须列进 requires。

已发布的键里有三个是 silent:1.4 加入的 measure(D-MEASURE)、1.6 加入的 time(D-FORMAT)和 1.8 加入的 cardinality(D-CONFORM),见变更记录。 其余每个键都是 degraded 或 documented,所以对这三个都不使用的模型来说,基础模式 依然自洽。用了其中之一的模型必须把它列进 requires,正是这一条阻止了没有实现该键的 datus 模式引擎(旧版 Dosi、 其他厂商读这个信封的实现)悄悄算出另一个数字:它会在版本闸门处以 datus_ext_key_required 失败。基础模式是另一回事 —— 它根本不读载荷,requires 也不读,只会告警 ignored_vendor_extension;带 silent 键的模型不应在基础模式下运行。

什么时候提升版本号:

  • MINOR —— 纯增量:一个新键,或者某个已有键的一个新可接受取值 (老载荷不可能用过它)。老载荷原样继续工作;新载荷跑在老引擎上会得到 datus_ext_version_ahead,只损失那个新键。 一个影响为 silent 的新键仍然只是小版本提升, 但生产方必须把它列进 requires。
  • MAJOR —— 只有当某个已有键改变了含义、被重命名或被撤销时。 此后每个声明了更旧 v 的载荷都会按改变了的键被逐一拒绝, 并给出更新 YAML 的指引。
  • 两者都不 —— 不改变任何键契约的引擎内部工作。 一个背后没有任何键的版本会让登记表的不变式变得空洞。

变更记录

每个已发布的小版本,以及它新增的键。这张表的机器可读形式是 capabilities().history —— dosi info --format json 或 GET /v1/capabilities。

版本 日期 新增的键 忽略时的影响
1.0 2026-07-11 join_type (D-JOIN)、fill_nulls_with (D-FILL) documented、documented
1.1 2026-08-02 time_dimension (D-TIME)、time_granularity (D-GRAIN)、dataset (D-DATASET) degraded、documented、degraded
1.3 2026-08-09 (无 —— window 的族内扩展) (继承 window 的 documented)
1.4 2026-08-12 derive(D-DERIVE M1)、measure(D-MEASURE) degraded、silent
1.5 2026-08-26 params(D-PARAM M1) degraded
1.6 2026-09-02 time(D-FORMAT) silent
1.7 2026-09-11 is_dimension(D-DIM) documented
1.8 2026-09-16 cardinality(D-CONFORM) silent
1.9 2026-09-16 (无 —— window 的族内增长) (沿用 window 的 documented)
1.10 2026-09-23 lod(D-LOD;以及 derive 的 lod 家族) degraded
1.11 2026-10-07 (无 —— lod 的族内扩展) (沿用 lod 的 degraded)
1.12 2026-10-25 concept_params(D-PARAM,在 mapping 文档上);params 扩展到 field 载体 degraded
  • 1.0(2026-07-11):初版 —— D-JOIN(关系的 join_type, 已实现:据此生成 INNER/LEFT 连接)、D-FILL(指标的 fill_nulls_with, 2026-07-12 已实现:对指标输出做 COALESCE; 覆盖 S-FAN-6 的计数默认值,对任何种类的指标都适用)。
  • 1.1(2026-08-02):时间与归属 —— D-TIME(数据集/指标的 time_dimension,已实现:数据集的主时间维度与指标自己的时间维度, 保留的 metric_time 查询名, 按分支的时间范围回退)、D-GRAIN(字段的 time_granularity, 已实现:更细粒度的请求 → grain_too_fine)、 D-DATASET(指标的 dataset,已实现:COUNT(*) 的归属)。 基础模式的 ignored_vendor_extension 告警现在覆盖全部五种载体对象 (此前只有 Relationship 和 Metric)。
  • 1.1(2026-08-04,未提升版本号):信封版本闸门本身 —— v 现在会被读取并强制执行(见信封),引入了 requires, 每个键都带上了声明的忽略影响,整份登记表通过 dosi info、 GET /v1/capabilities 和 dosi_engine.DATUS_EXT 对外发布。 没有新增或改变任何键,所以版本保持在 1.1。不带 v 的载荷 —— 也就是 Datus Agent 今天输出的那种 —— 不受影响。
  • 1.2(2026-08-06):窗口指标 —— D-WINDOW(指标的 window, 已实现:即 --explain 里的 WindowStage 步骤; pop/rolling/cumulative 语法糖 + 通用的 offset/frame 形态, 带扫描扩展 + 窗口后裁剪的累计重置;忽略影响为 documented —— 指标退回到它朴素的基础聚合;规范见 window-extension.md)。 最初只在 DuckDB 上执行;全方言启用(按方言的偏移移位下降, 并在真实数仓上验证)作为后续落地。 族内新增(同一小版本,在早于它们的引擎上会 fail-closed): 放宽了混合族/混合移位/混合的带起始重置, 以及有限窗框上的 require_full_window(2026-08-06)—— 见 window-extension.md#restrictions。
  • 1.3(2026-08-09):窗口族与函数 —— 在族内扩展 window 键, 没有新的登记表行(capabilities().history 仍然停在 1.2; 这个版本号标示的是载荷方言)。新增内容:rank 族 (在指标值之上的 row_number / rank / dense_rank / ntile / percent_rank / cume_dist)、value 族(first_value / last_value / nth_value)、 偏移的 direction: "forward"(LEAD —— 下一期参照)、窗框的 order / partition / units / second 修饰符 (按值排序的窗框和 RANGE 窗框、分区模式、双参数输入), 以及登记表层级 W2(stddev_pop/samp、var_pop/samp)
  • W3(covar_pop/samp、corr)。每一项新增在 1.2 引擎上都会 fail-closed: 未知的顶层族键会落进"恰好一个族"的错误,未知的族内键会被拒绝, 未知的函数名会在枚举解析时失败 —— 而且声明 "v": "1.3" 本身还会触发版本闸门。规范见 window-extension.md(各族章节 + 函数登记表)。
  • 1.4(2026-08-12):派生指标与度量命名 —— D-DERIVE M1 (derive 键,已实现:filter + compose 两族、带成环检测的 指标引用解析、表达式等价性闸门、编译期一致维度集, 以及在每个宿主面上暴露的 derive 元数据;reagg / 跨数据集的 filter 谓词 / window.base 属保留形态,会以 not_implemented 拒绝并点名各自的里程碑 —— 注意 window.base 此前会落进"未知键静默忽略"那一档,现在改为 fail closed), 以及 D-MEASURE(measure 键,已实现:显式度量命名, 按签名去重和冲突检查保持不变 —— 这是对非 ASCII stem 冲突的修复, 也是第一个 silent 影响级别的键:请把它列进 requires)。 M2(2026-08-19,族内 —— 没有新键,也不提升版本号):window.base 从保留形态转为可用(窗口作用在叶子指标/filter 指标之上),compose 接受 窗口成员(阶段层下降:算术作用在窗口输出之上)和 filter 成员(展平层)。 用到这些形态的 1.4 载荷在 M2 引擎上能编译,在 M1 引擎上则 fail closed (not_implemented 或直接拒绝)—— 绝不会被静默误读。 嵌套 compose(族内 —— 没有新键,也不提升版本号):compose 的成员可以 是另一个 compose,在编译期内联展开。老引擎遇到这个形态还是报原来那条 invalid_datus_extension,同样是 fail closed。指标仍然留在展平层, derive_members 变成传递展开后的叶子列表、系数逐层折叠; 树里任何位置有窗口,这个组合依旧是 not_implemented。
  • 1.6(2026-09-02):时间存储格式 —— D-FORMAT(字段的 time 块, 已实现:分组侧按方言解析该列,过滤侧在生成 SQL 前就把边界折算成存储格式, 粒度从布局推断)。time.granularity 与扁平的 time_granularity 成为地位相同的两种写法 —— 不废弃任何一种, 但两者不允许矛盾。这是第二个 silent 影响级别的键:请把它列进 requires, 这样缺少它的 datus 模式引擎会在版本闸门处失败(基础模式不读 requires,见信封)。 丢弃它的消费者不会响亮地失败;它会假定这是一个原生日期列, 而对按月或按年存储的列,结果是一个空结果集且没有任何诊断。 配置方法与限制见 D-FORMAT。范围:字符串与整型布局, 外加原生时间类型;epoch_seconds / epoch_millis 暂缓, 因为 from_unixtime 会读会话时区,那套语义值得单独一轮。

现有键扩展(2026-09-08,版本仍为 1.6):time.format 支持连续、有序、定宽的时间部分及任意固定文本。 过滤仍将日期边界转换为存储编码常量;分组按位置提取时间部分,组成标准日期时间后 交给各数仓解析。原有格式保持兼容,旧版 Datus 引擎会拒绝新增格式,不会静默误解。 - 1.7(2026-09-11):字段角色 —— D-DIM(字段的 is_dimension, 已实现:由 list_dimensions 与归因的候选清单读取)。 模型终于能说清一列是度量、标识、自由文本还是 PII,从而不再被当作分组维度推荐。 它只是推荐信号:显式写出的 group_by 照常生效,编译出的 SQL 一个字节都不变, 因此影响级别是 documented,也不需要写进 requires。不写这个键时,引擎只依据 模型结构推断 —— 键、关系列、被数值聚合的列 —— 绝不探测数仓。见 D-DIM。 - 1.8(2026-09-16):事实表之间的一致性关系 —— D-CONFORM(Relationship 上的 cardinality 键,已实现:连接图里的一致性类、 group-by/过滤/时间范围列与 metric_time 逐分支解析到各事实表自己的拼写、 按输出名合并)。many_to_many 边永远不 join;只有配对列是共享的。 这是第三个 silent 影响级别的键:请把它列进 requires —— 没有它,这条边就是 many→one join,对重复敏感的聚合会被扇出。见 D-CONFORM。 - 1.9(2026-09-16):一张排名记分卡 —— 与 1.3 一样是 window 键的族内增长, 因此没有新的登记表行。同一个形态带出四项新增。

frame.scope: "partition" 把帧声明成整个分区, 而不是相对当前行的偏移,编译出 <函数>(…) OVER (PARTITION BY …): 既没有 ORDER BY,也没有帧子句。它就是排名旁边的第二个窗口函数 —— 「我是在多少行里排这个名次」—— 打分卡需要它,而现有的键组合不出来: count 配 preceding: "unbounded" 是累计行数,units: "range" 也只到当前行所在的并列组为止。scope 与 preceding、reset、 require_full_window、units、order 互斥,后面这些只描述有序帧, 写了就报错而不是忽略。

window.base 同时放宽:比率和 compose 现在都能当作窗口读取的序列, 于是「给某个率排名」「看某个率的环比」写得出来了。跨事实 compose 的序列要到分支 合并之后才成为一个值,所以窗口阶段落在合并之上 —— 各分支分别聚合,它们的 CTE 与阶段的基座并排。只有事实表的日期经 D-CONFORM 配对时,这类基座 才有共享的时间轴;否则需要时间轴的那些族会以一致性为由拒绝。

在 1.8 引擎上双重 fail closed:frame 和 order 里的新键会被当作未知键拒绝, 而载荷里的 "v": "1.9" 也会触发版本闸。 见 window-extension.md。

嵌套窗口与 NULL 位置把这个形态补齐。order.nulls: "first" | "last" 钉住窗口排序里 NULL 落在哪一端 —— 不写的话各数仓按自己的默认来,而它们并不一致 (DuckDB 与 PostgreSQL 升序排最后,MySQL 一系与 StarRocks 排最前), 基座是比率之后这就从理论风险变成了常态。生成器只在与目标方言默认不一致时才写出该子句; 方言根本没有这个子句时(MySQL、TiDB),位置声明降为一个打头的 CASE WHEN … IS NULL 排序键 —— 所以渲染随方言变、含义不变。而 window.base 比「比率」更进一步:它接受「建在另一个窗口输出之上的 compose」,于是第二个窗口能读第一个窗口的输出。每一层编译成多一个 SQL 阶段; 层数上限为 3,第 2 层起必须显式声明 partition。要紧的护栏是跨层的而不是层内的: 输出的 trim 始终留在最外层之后,而任何一层的分区全局指标都不能与任何一层会扩大 扫描范围的指标共处一条查询。另外还放宽了:阶段层的 compose 可以有 compose 成员(它保持为成员而不被内联 —— 在那一层每个成员都是一条独立序列)。 - 1.10(2026-09-23):细节层次 —— D-LOD,一套语法、两个载体:Field 上的 lod 键,以及 Metric 上现有 derive 键的 lod 家族(已实现)。

写在字段上,fixed(base, dims…) 让一个「按实体聚合」的值可以被普通查询分组、 过滤、再聚合(即 --explain 里的 LodRollup 步骤;D-FORMAT 编码的汇总键 保留存储原值,只在外层解析一次)。视图需要的超出汇总本身时 —— 汇总键之外的维度、 读取该值的行级字段、多个键族 —— 走广播形态(LodBroadcast):明细行与每个键族 各一张派生表连接;对钉住值做聚合时,每个键在每组里只算一次。不写键就是整表 范围的 FIXED,广播到每一行。

写在指标上,include 按查询自身的 group-by 加上声明的维度分组内层,再用声明的 外层聚合收回来;粒度与 D-FORMAT 解析都在内层完成。exclude 把同一张事实表更粗 的一次聚合挂到视图的每一行上 —— 按共有的维度 LEFT JOIN,没有共有维度就 CROSS JOIN —— 是重新计算而不是开窗,所以 COUNT(DISTINCT) 基座也是对的。fixed 是字段那份声明挂在指标上,好让它挂到视图上:在维度筛选之前算出的整表或按键 总计,也就是 Tableau 的 {SUM([Sales])} 作分母时的含义,exclude() 表达不了 (见 LOD 指标)。一致性配对的两端若是两张事实表各自的 fixed 字段,就把两者对齐成同一根轴:每张事实表各自汇总,分支再合并,两张事实表从不 连接。

筛选位置沿用 Tableau 的运算次序。where 是维度筛选 —— 只点名汇总键时作用在 固定汇总的输出上,否则作用在明细行上,从不进入汇总内部;time_range 是上下文 筛选,约束每一个汇总。只读 D-LOD 值和常量的行级字段按键各聚合一次, 也可以在该键粒度上筛选再聚合。因此,像 customer_margin >= 5000 AND customer_orders >= 6 这样的计算分类会先筛选 fixed 值,再聚合。条件读到另一组 fixed 键时, 会在该组汇总值仍可用的位置、网格归并之前求值。

基础模式丢弃 D-LOD 字段(aggregate_field_dropped),对 D-LOD 指标则 fail closed:它的 expression 就是声明所描述的嵌套聚合(nested_aggregate), 而不是只算其中一层。见理解 LOD。 - 1.11(2026-10-07):跨数据集的细节层次 —— lod 键的族内扩展,没有新的登记表行。 字段的 fixed base 可以聚合字段所在数据集以外的数据集,前提是每个键都经声明过的关系对应到那边的一列: 两表之间任一方向的多对一边、base 到宿主表的多对一路径,或者 D-CONFORM 配对 (一个有自己扫描的汇总)。 汇总按普通等值左关联到宿主表的行上,没有匹配时计数读 0,所以 > 0、= 0、>= n 分别是半连接、反连接和阈值。上下文经 base 自己的关联边或配对进入汇总,那边读不到的合取项不进入 (跨数据集)。1.10 的引擎会拒绝这种 base(报它聚合了另一个数据集),不会算错。 - 1.12(2026-10-25):字段与派生子类型上的参数。params 扩展到 field 载体 —— 字段表达式里的 :name 洞,即 field.literal 槽位,绑定时把字段在整个查询里打补丁, 并在 bindings 下回显(字段参数);concept_params 落在 Ossie mapping 文档上,声明派生子类型 derived_by 规则里的占位符(子类型阈值)。同期语义契约新增 S-ATTR-3′: CASE WHEN <guard> THEN <arg> END 形态的聚合,其 guard 可以读 arg 所在数据集多对一可达的 一个数据集 —— 事实表指标的 D-DERIVE filter 因此可以写成 where: "Customer is PremierCustomer"。旧引擎 fail closed:concept_params 是未知键, 字段里的 :name 不是列,"v": "1.12" 触发版本门。

尚未实现(路线图)

排队中的这些 RFC 全部是增量的,所以每一个都以一次小版本提升落地, 带上自己的登记表行。顺序可能变化;每一条都独占一个小版本。

小版本 RFC 键 载体 影响 需要 requires?
≥1.12 rfc-semi-additive-metrics §4 semi_additive Metric silent —— 期末余额会被跨快照加总 是
≥1.12 rfc-minimal-declarations §3 D-DISTINCT-STATE distinct_state Field / Dataset silent —— 一个物化的 bitmap 列会被当作普通列聚合 是
≥1.7 rfc-derived-metrics M3-M4 derive 的 reagg 与一般的跨数据集 filter(window.base 与 compose 的窗口/filter 成员已在 1.4/M2 以族内方式发布;单个维度守卫已在 1.12 作为 S-ATTR-3′ 发布) Metric degraded/错误 —— 保留形态今天就以 not_implemented 拒绝,绝不静默计算 否
— window-extension.md 的 rank/share/streak 族、W2+ 函数 将来的 window 形态(族内新增,在旧引擎上 fail-closed) Metric degraded 否

已经发布的 silent 键有三个:1.4 的 measure(D-MEASURE)、1.6 的 time(D-FORMAT)和 1.8 的 cardinality(D-CONFORM)。如果你在一个会输出 silent 影响级别键的 Datus Agent 上编写模型,请留意它产生的 requires 列表: 正是它阻止了更旧的引擎悄悄返回错误数字,而这之所以行得通, 是因为 requires 本身在 1.1 就已发布 —— 早于第一个需要它的键。