跳转至

错误码

所有表面——CLI、REST、MCP、Python——都用同一套结构化错误报告失败。 错误码是稳定 API,错误文本不是:按 code(snake_case)匹配,读 candidates / suggested_retry / hint 来自纠错,不要解析文本。

错误的结构

错误沿流水线分阶段,每个阶段一个错误码枚举:

阶段 何时运行 payload 形态
load 读取模型文件 目前只有文本(无 code)
validate 模型结构检查 {severity, code, location, message}
compile 把模型降解为 IR {code, location, message, candidates?, hint?}
plan 编译指标查询 {code, message, metrics?, candidates?, suggested_retry?}
execute 在数仓上执行 SQL {code, message, hint?}

可选字段为空时省略,payload 保持紧凑。location 指出该改哪里 (metric 'revenue'、semantic_model[0].relationships[2].to); candidates 列出错误引用的合法替代;suggested_retry / hint 给出能成功的改写。

各表面:

  • CLI —— --format json 把错误结构体以 JSON 打到 stderr; validate --format json 在 stdout 输出 {issues, compile_errors, warnings}。退出码:0 成功,1 引擎拒绝(结构化错误),2 用法错误。已知不对称:load 错误和 非 validate 命令下的校验问题只有文本形态。
  • REST 与 MCP —— 同一个 body,外面包一层:{"error": {…}}。MCP 工具失败时把它作为 isError: true 的文本内容返回;MCP 工具参数不合法时返回的是纯文本 schema 错误。
  • Python(dosi_engine)—— ModelError / QueryError / ExecuteError,都有 .code 与 .to_dict();planner 的 suggested_retry 到 Python 侧叫 hint。
{"error": {"code": "unknown_metric",
           "message": "unknown metric \"revenu\"",
           "metrics": ["revenu"],
           "candidates": ["revenue", "order_count", "..."]}}

各命令可能出现的错误码

表面 validate issues compile 错误 plan 错误 execute 错误
CLI validate ✓(issues) ✓(compile_errors) — —
CLI query / explain / list ✓(文本,中止) ✓ ✓ 仅 query --execute
REST POST /v1/validate、MCP validate_model ✓(在 body 中) ✓(在 body 中) — —
REST /v1/query/compile · /explain、MCP compile_sql · explain_query —(模型启动时加载) ✓(惰性编译) ✓ —
REST /v1/query/execute、MCP run_query — ✓ ✓ ✓
Python Engine(...) ModelError("invalid_model") ModelError("compile_error") — —
Python compile_sql / query — — QueryError ExecuteError

服务级错误码(bad_request、unauthorized、forbidden、busy、 internal、timeout)可伴随任意 REST/MCP 调用出现,见 服务级错误码。

模型校验问题

结构检查(dosi validate、POST /v1/validate)。error 使模型无效; warning 不会——模型仍可编译。

错误码 级别 何时触发 修复
unsupported_version error version 不等于本引擎实现的 Apache Ossie 规范版本 按消息中的版本号钉住
duplicate_name error 模型、数据集、字段、指标或关系重名 重命名其一
empty_datasets error 语义模型没有数据集 至少加一个数据集
empty_columns error 关系未声明任何列对 填写 from_columns / to_columns
column_count_mismatch error from_columns 与 to_columns 长度不等 二者按位置配对——改成等长
unknown_dataset error 关系端点引用了未声明的数据集 修正 from: / to: 名称
unknown_column error 关系列不是所在数据集声明的字段 声明该字段或改列名
missing_sql_dialect error(指标)/ warning(字段) 缺少 ANSI_SQL(或其他 SQL)方言条目 补一条 SQL 方言表达式
self_relationship warning 关系把数据集连到自身 通常是有意的;复查即可
relationship_target_not_unique warning to_columns 不是目标的主键/唯一键——join 可能发散 让 join 指向唯一键;若是有意的事实表配对,声明 cardinality: many_to_many
{"severity": "error", "code": "unknown_dataset",
 "location": "semantic_model[0].relationships[0].to",
 "message": "relationship \"orders_to_customers\" references unknown dataset \"customers\""}

编译错误

把有效模型降解为 IR(validate 信封中的 compile_errors;查询表面上在模型 首次编译时出现)。全部携带 location;candidates / hint 见备注。

错误码 何时触发 payload 附加
unparseable_expression 表达式不是可解析的 SQL
no_sql_dialect 被使用的字段/指标没有 SQL 方言条目
unknown_dataset SUM(no_such.amount)——限定名不是数据集;另外在文档内语义模型数量 ≠ 1 时也被(误)用 candidates:数据集名
unknown_field dataset.field 中数据集没有该字段 candidates:该数据集的字段
unknown_column 裸列在任何数据集都找不到 hint:限定为 dataset.field
ambiguous_column 裸列存在于多个数据集 candidates:限定拼法
not_an_aggregate 指标表达式没有聚合 hint:包一层 SUM(...)
nested_aggregate SUM(MAX(...))
bare_column_in_metric 指标里出现未聚合的列(或纯字面量聚合) hint:对数据集字段聚合
cross_dataset_aggregate 一个聚合混用两个数据集的列 拆开聚合
count_star_needs_dataset 多数据集模型里的裸 COUNT(*) hint:改数键列
unsupported_aggregate SUM/COUNT/COUNT DISTINCT/AVG/MIN/MAX 之外的聚合
multi_column_count_distinct 已声明但目前不会产生
window_in_metric 指标表达式里的窗口函数 hint:在数据集里预计算
subquery_in_metric 指标表达式里的子查询
measure_name_collision 两个不同聚合合成了同名 measure 部分触发点带 hint
metric_reference_cycle D-DERIVE compose 指标互相引用 消息展示环路路径
derive_expression_mismatch 手写 fallback 表达式与 derive 展开不一致 hint
invalid_datus_extension DATUS payload 不合法(v 错、字段引用错等) hint 带文档锚点
unsupported_datus_ext_version payload 的 v 是更新的 major / 低于 1.0 hint 指出支持的 major
datus_ext_key_required requires: [...] 点名了本引擎未实现的键
not_implemented 声明合法但超出当前 derive/window/LOD 范围 hint 带文档锚点

编译警告

非致命:模型编译通过,但并非所有声明都生效。形态 {code, location, message};CLI 在 stderr 打 ! … 且退出码为 0。

错误码 何时触发
ignored_vendor_extension basic(标准 Ossie)模式忽略了 DATUS 扩展
datus_ext_version_ahead payload 声明了更新的 minor;未知键被丢弃(消息中点名)
datus_ext_key_newer_than_declared 用了比声明的 v 更新的键,但仍被采纳
datus_ext_requires_missing 使用了非空的 time、measure 等 silent 扩展,却未列入 requires;补齐后可防止旧引擎忽略它
compose_no_conformed_dimensions compose 指标的成员没有共同维度——任何查询都无法分组
datus_time_format_misplaced 时间维度的顶层 format 写的是存储布局(%Y%m%d 等);它是展示元数据,已被忽略——布局应写进 time 块(D-FORMAT)
datus_param_unbounded D-PARAM 的 int/float 参数没声明 allowed/min/max
datus_param_unused 声明了却没有任何槽位引用的参数
underdeclared_field (datus 模式,仅 dosi validate)字段没写 is_dimension,但看起来是度量、业务键或没标记的时间列,因而会被当作分组维度推荐
dimension_role_conflict (datus 模式,仅 dosi validate)字段声明了 is_dimension: true,却被某个指标当度量聚合;声明照常生效,消息会点出是哪个指标
datus_param_opaque_token allowed 里的取值经 ASCII 词根消毒后无法区分,其输出列改用 h<hex> 后缀(消息里给出对应关系)
aggregate_field_dropped 字段表达式里有聚合,却没有声明 D-LOD 粒度——字段被丢弃,引用它的地方以 unknown_field 失败

查询规划错误

两个查询平面的拒绝:指标平面(dosi query、/v1/query/*、compile_sql / run_query)与明细平面(dosi select、/v1/select/*、compile_select / select)。HTTP 一律 400,例外:not_implemented → 501、 internal → 500。

错误码 何时触发 payload 附加
empty_query 未请求任何指标 candidates:全部指标
dimensions_required 归因请求没给 dimensions;引擎不猜哪些列能解释这次变化 metrics、candidates:该指标值得拆解的维度(好的在前)、suggested_retry:可直接粘贴的 dimensions 片段
unknown_metric 指标名不在模型里 metrics、candidates
unknown_dimension group-by 项不是维度 —— 包括写了一个指标名,此时 retry 会告诉你该怎么请求它;窗口指标的时间轴缺席 group-by 时同码 candidates 或 suggested_retry
ambiguous_dimension 裸字段存在于多个数据集 candidates:限定拼法
grain_on_non_time_dimension 对非时间字段加 :grain suggested_retry
grain_too_fine 请求的粒度细于存储的 time_granularity;或时间范围的边界落在 time.format 列无法细分的周期内部 suggested_retry
duplicate_output_name 两个 group-by 项——或两个指标列——产出同名输出列 suggested_retry
unknown_order_key 排序键既不是指标也不是 group-by 项;列表绑定下的指标名(摊成多列)同码 candidates:可用的键,或各组绑定 + suggested_retry
unsupported_filter where 中包含子查询/窗口、where_sql 引用了 D-FORMAT 编码字段、time_ranges 下直接写了指标名(应写 {metric}__{tag}),或时间范围格式不对 相关时 candidates 列出带 tag 的名字。编码时间字段改用 time_range。
aggregate_in_where where 里出现内联聚合 suggested_retry:声明成指标、加入 metrics,再在 where_sql 里按名引用(过滤条件)
result_filter_metric_not_selected where_sql 引用了查询没有选择的指标;指标条件筛选的是本次查询自己的结果行 metrics、candidates:已选的指标、suggested_retry
result_filter_unprojected where_sql 的同一个子条件既引用指标又引用未分组的字段,没有哪个位置能同时求值两者 suggested_retry:把字段加入分组,或拆成两个子条件
no_join_path 没有数据集能沿多对一同时到达 measure 与全部 group-by metrics
ambiguous_join_path 存在多条关系路径——语义会不同 candidates:各路径、suggested_retry
fan_out_risk 对重复敏感的聚合跨越会发散的 join metrics、suggested_retry
time_range_needs_dimension 作用域里有多个时间维度,范围目标不明 candidates
no_primary_time_dimension 用了 metric_time 但没有声明主时间维度 candidates、suggested_retry
metric_time_conflict 已不再产生:同一底表上时间轴不同的指标现在每条轴一个分支(见语义);保留这个错误码是为了兼容已有客户端
unconformed_dimension 维度在派生指标的 conformed 集合之外 metrics、candidates、suggested_retry
unknown_metric_param params 里的名字没有任何被查指标声明 candidates:已声明的名字、suggested_retry
param_out_of_domain 绑定越出参数的类型/取值域、列表为空或有重复、或在只允许单值处(归因)传了列表 metrics、suggested_retry:一段合法的 params
param_expansion_too_large 列表绑定展开超过 64 个输出列 metrics、suggested_retry
window_reset_too_fine 窗口 reset 细于查询粒度 suggested_retry
window_exclude_not_grouped 依赖链上某个窗口 partition.exclude 的维度不在查询的 group_by 里 metrics:依赖链缺维度的那些被请求指标(具体是哪一层在 message 里);candidates:本次查询的维度;suggested_retry:一次性列出需要补上的全部维度
dialect_unsupported_window_function 目标方言缺少所需窗口函数 metrics、suggested_retry
unknown_dataset 明细查询的 from 不是任何数据集 candidates:数据集名
detail_fanout 明细查询只能逆着连接方向抵达某数据集,一行根会变成好几行 candidates:反向路径;suggested_retry:应改用的根
metric_in_detail_query 指标出现在明细查询的投影里 metrics、suggested_retry:对应的 dosi query 调用
unknown_relationship 关系前缀引用里有不存在的关系名 candidates:从当前数据集出发的边;suggested_retry
invalid_relationship_path 命名路径接不上、重复访问某数据集,或不从查询的基础数据集出发 candidates:该数据集的出边;suggested_retry
not_implemented 模型层面合法,超出当前引擎范围——包括一次查询用两条路径抵达同一数据集,以及超出当前里程碑的 D-LOD 形态(跨层 OR、窗口指标、跨事实对齐) metrics 或 candidates、suggested_retry
internal 引擎不变量被破坏——重试无用;请报 bug

典型的自纠错循环:unknown_metric → 从 candidates 里选 → 重试; fan_out_risk → 按 suggested_retry(按可达维度分组,或改成 COUNT DISTINCT/MIN/MAX):

{"code": "fan_out_risk",
 "message": "measure \"views_source_views_sum\" aggregates Sum over dataset \"views_source\", which has no many→one path to [\"bookings_source\"]; evaluating a duplicate-sensitive aggregate (SUM/AVG/COUNT) across that join would double-count rows",
 "metrics": ["views_times_booking_value"],
 "suggested_retry": "group by dimensions reachable from the measure's dataset, or restate the metric with a duplicate-insensitive aggregate (COUNT DISTINCT/MIN/MAX)"}

明细平面的两个循环同理:detail_fanout 直接给出能在正确粒度上回答该问题的 根数据集,ambiguous_join_path 直接给出点名各条候选路径的关系前缀, 两者都可以粘贴进下一次调用。参见明细查询。

执行错误

在数仓上执行编译出的 SQL(query --execute、/v1/query/execute、 run_query)。形态 {code, message, hint?}。

错误码 HTTP 何时触发
config 400 连接配置有问题(URI 错、DuckDB 文件打不开、驱动选项缺失)
connection 502 数仓不可达
auth 502 凭据被拒
sql_rejected 502 数仓拒绝了 SQL(表缺失、类型错误)——消息内嵌数仓自身的报错
driver 502 连接建立后驱动层失败
timeout 504 语句或服务端执行超时

服务级错误码

dosi-server 在同一信封里追加传输层错误码, {"error": {"code", "message"}}:

错误码 HTTP 何时触发
bad_request 400 JSON body 不合法、方言未知
unauthorized 401 bearer token 缺失/错误
forbidden 403 --disable-execute 下调用执行
busy 429 执行槽位占满(Retry-After: 1)
timeout 504 服务端自身的执行超时
internal 500 意料之外的服务失败

Python 异常

dosi_engine wheel 抛一套异常层级(基类 OsiError,均有 .code 与 .to_dict()):

异常 错误码 说明
ModelError io、parse、ontology、invalid_model、compile_error invalid_model 带 issues;compile_error 带 compile_errors 及首个错误的 candidates/hint;ontology(ontology 无法解析或降低)在消息里列出每条拒绝及其自身的错误码
QueryError 上文全部 plan 错误码 suggested_retry 到达时名为 hint
ExecuteError 上文全部 execute 错误码

编译警告在 Engine(...) 构造时以 Python RuntimeWarning 抛出—— python -W error 可把它们升级为失败。

SQL 生成错误(parse_error、unsupported_aggregate 等)是 planner 与 渲染器之间的内部边界,只会出现在上述 compile 或 plan 错误内部, 不构成独立的用户级契约。

本页每个错误码都有测试钉住:fixtures/errors/ 的负向用例目录随每次提交 运行,实际 payload 可在 test-status 看板的 "Error contract" 区浏览。