CLI 参考¶
dosi(crate crates/dosi-engine)
是 Dosi 的命令行界面:校验一份 OSI 语义模型、浏览它编译出的
指标/数据集/维度、把一次指标查询编译成方言 SQL,并把这段 SQL 在数仓上执行。
REST API 通过 HTTP 能做的一切,dosi 都能在 shell 里做:
同一个编译器、同样的结构化错误、同样的 --format json 机器契约。
$ dosi --help
Compile metric queries over OSI semantic models to dialect SQL
Usage: osi [OPTIONS] <COMMAND>
Commands:
validate Validate the model: structure, unique names, relationship integrity, and metric compilation
list List model objects
query Compile a metric query to SQL
explain Show the compiled IR shape and logical plan without generating SQL
安装¶
dosi 以单个自包含可执行文件的形式安装:工作区的库 crate,以及默认情况下的
DuckDB,都静态链接进同一个可执行文件,没有别的东西要装,也没有额外的包要管。
安装后落在 ~/.cargo/bin/osi(已在 PATH 上)。
要求: 一套 Rust 工具链(不低于工作区的 rust-version)和一个 C++ 编译器
(内置 DuckDB 需要从源码编译)。可选连接器里的 TLS 用 rustls,
不需要 OpenSSL 或其它系统库。--no-default-features 可以去掉内置 DuckDB
(运行时改为依赖 duckdb CLI),同时也就不再需要 C++ 构建环境。
从 git 仓库安装¶
对私有仓库同样有效:任何有读权限的用户(SSH key 或 git 凭据助手)都能直接安装, 不必先发布到 crates.io。
$ cargo install --git ssh://git@github.com/datus-ai/osi-engine.git dosi-engine
# if SSH fetch fails, let cargo use your system git:
$ CARGO_NET_GIT_FETCH_WITH_CLI=true \
cargo install --git ssh://git@github.com/datus-ai/osi-engine.git dosi-engine
用 --tag <tag> / --branch <branch> / --rev <sha> 固定版本,加 --locked
以提交进仓库的 Cargo.lock 构建,重新执行时加 --force 即可更新。因为每个用户都在
本地编译,可执行文件会自动匹配他们自己的操作系统/CPU
(Linux、Intel 或 Apple Silicon 的 macOS)。
从 crates.io 安装¶
发布之后:
带上数仓连接器¶
连接器是 cargo feature(见在数仓上执行); 安装时传入即可:
$ cargo install --git <url> dosi-engine --features exec-all # every connector
$ cargo install --git <url> dosi-engine --features exec-mysql,exec-postgres
$ cargo install --git <url> dosi-engine --no-default-features # lean, no bundled DuckDB
从源码构建¶
在一份 checkout 里,scripts/build_binary.sh
会构建一个 release 且 strip 过的可执行文件,并报告它的体积和自包含情况:
$ scripts/build_binary.sh # default: bundled DuckDB, stripped
$ scripts/build_binary.sh --all # + every connector (exec-all)
$ scripts/build_binary.sh --lean # no bundled DuckDB
或者直接执行:cargo build --release -p dosi-engine --bin osi
(可执行文件是 target/release/dosi)。产物只动态链接标准系统库
(glibc ≥ 2.34、libstdc++);DuckDB 本身是静态打包进去的。
全局选项¶
以下选项对每条命令都生效(clap 的 global = true):
| 参数 | 环境变量 | 默认值 | 含义 |
|---|---|---|---|
--model <path> |
DOSI_MODEL |
必填 | OSI 模型文件(.yaml / .yml / .json) |
--format <fmt> |
— | text |
输出格式:text、json 或 arrow(见输出格式) |
--connections <path> |
DOSI_CONNECTIONS |
发现链 | --execute 用的连接配置文件(见在数仓上执行) |
--osi-datus |
— | 开启 | Datus 模式:DATUS custom_extensions 会被采纳(datus-extensions.md) |
--osi-basic |
— | 关闭 | 基础模式,严格的标准 OSI。DATUS 扩展被忽略并给出警告;validate 还会额外运行上游校验器(见下) |
--osi-datus 与 --osi-basic 互斥,同时传入属于用法错误。基础模式下,
每个被忽略的扩展都会在 stderr 上打印一行 ! 警告(在 --format json 下则落在
warnings 里);警告永远不改变退出码。
如果设置了 DOSI_MODEL,--model 可以省略;一条需要模型却两者都没找到的命令会以
no model given; pass --model <path> or set DOSI_MODEL 退出。
退出码: 0 成功,1 引擎拒绝了这次查询/执行(带结构化错误),
2 用法/CLI 错误(参数写错、缺模型、参数无法解析)。
命令¶
dosi info¶
报告引擎版本、OSI 规范版本和 datus-ext 版本,以及本引擎会读取的每一个
vendor_name: DATUS 的 custom_extensions 键。它不需要模型,
所以也可以当作版本探测命令用。
$ dosi info
dosi 0.1.0
osi spec 0.2.0.dev0
datus-ext 1.1 (accepts 1.0 and up)
mode datus
KEY EXTENSION CARRIERS SINCE IF IGNORED
join_type D-JOIN relationship 1.0 documented
fill_nulls_with D-FILL metric 1.0 documented
time_dimension D-TIME dataset, metric 1.1 degraded
time_granularity D-GRAIN field 1.1 documented
dataset D-DATASET metric 1.1 degraded
SINCE 是引入该键的 datus-ext 版本,IF IGNORED 是不采纳它要付出的代价,
见 datus-extensions.md §2.1。
--format json 以机器契约的形式输出同样的内容。
生成模型的 Agent 在决定输出哪些键之前,应该先读它。
dosi validate¶
加载模型并跑完整的校验流水线:结构、名称唯一性、关系引用完整性、 指标表达式编译,然后报告每一个问题。
--format json 会输出 {issues, compile_errors, warnings} 供 CI 把关:
每个 issue 带严重级别和消息,每个编译错误带稳定的 code 和可选的 hint,
每条警告带稳定的 code、location 和 message。有错误的模型以非零码退出;
只有警告则不会。
上游校验(--osi-basic)。 在基础模式下,当能找到一份 checkout 时,
validate 会额外调用上游的 apache/ossie
参考校验器(即 OSSIE_DIR,默认 ~/src/ossie,其下含有 validation/validate.py),
用已发布的规范来要求这份模型。上游失败会让整条命令失败;
找不到 checkout 或缺少 python3 时会打印一条提示,退回到只做内置校验。
--format json 会额外输出 "upstream": {ran, passed, output, note}。
$ OSSIE_DIR=~/src/apache-ossie dosi validate --osi-basic --model model.yaml
✓ upstream OSI validation passed
✓ 1 semantic model(s) valid
dosi list <what>¶
浏览编译后的语义层。三个子命令,在 text 模式下各是一张表,
在 --format json 下则是一个对象数组:
| 子命令 | 列 |
|---|---|
dosi list datasets |
名称、来源、主键、字段数、时间维度 |
dosi list metrics |
名称、推断出的种类(aggregate / ratio / expression)、数据集、描述 |
dosi list dimensions |
dataset.field、时间标记、描述 |
$ dosi list metrics --model fixtures/tpcds/model.yaml
NAME KIND DATASETS DESCRIPTION
total_sales aggregate store_sales Total sales revenue across all transactions
customer_lifetime_value ratio customer, store_sales Average lifetime sales value per customer
store_productivity expression store, store_sales Sales per employee across stores
dosi query¶
核心命令:把一次指标查询编译成方言 SQL,并可选地执行它。查询本身由下面共用的
query spec 参数来描述;在此之上,query 还接受:
| 参数 | 含义 |
|---|---|
--dialect <name> |
目标 SQL 方言(默认 duckdb,或 --connection 所指配置的方言) |
--pretty |
美化打印生成的 SQL |
--explain |
在 SQL 上方一并打印逻辑计划 |
--execute |
把编译出的 SQL 拿到数仓上执行并打印结果行 |
--connection <name> |
连接配置文件里的具名配置(同时决定方言) |
--db <path> |
不带 --connection 时 --execute 使用的 DuckDB 文件(默认:内存) |
只编译(不加 --execute)会打印 SQL(text)或 {dialect, sql}(json):
$ dosi query --model fixtures/tpcds/model.yaml \
--metrics total_sales \
--group-by store.s_state,date_dim.d_date:month \
--where "item.i_category = 'Books'" \
--start-time 2024-01-01 --end-time 2025-01-01 \
--order -total_sales --limit 100 \
--dialect starrocks
SELECT store.s_state AS s_state, DATE_TRUNC('MONTH', date_dim.d_date) AS d_date__month, ...
(把 total_sales 和基于 store 的指标(比如 store_productivity)放在一起,
会得到 fan_out_risk 错误:按门店计算的度量,无法按门店够不到的日期来分组。
这种保护正是要点所在,见 semantics.md §6。)
query spec 参数(与 explain 共用)¶
| 参数 | 含义 |
|---|---|
--metrics <a,b,…> |
必填。 逗号分隔的指标名 |
--group-by <items> |
逗号分隔的 dataset.field 或 dataset.field:grain(粒度:day\|week\|month\|quarter\|year);metric_time[:grain] 表示按每个指标各自的主时间维度分组 |
--where <sql> |
作用在维度字段上的标量布尔 SQL,在聚合之前应用 |
--start-time <YYYY-MM-DD> |
时间范围的下界,含该值 |
--end-time <YYYY-MM-DD> |
上界,不含该值 |
--time-dimension <field> |
该范围作用在哪个时间维度上(默认:group-by 里唯一的那个时间维度,否则是每个指标的主时间;metric_time 用于显式指明) |
--order <keys> |
逗号分隔的排序键;前缀 - 表示降序 |
--limit <n> |
行数上限 |
有三条行为值得记牢(完整契约见 semantics.md):
- 粒度的输出列命名。
--group-by orders.order_date:month会产生名为order_date__month的列({field}__{grain}),--order里也引用这个名字。 - 排序键用输出列名,不是限定字段名。 写
--order -total_sales或--order ds__month,而不是orders.status。前缀-表示降序; clap 会把它当成参数标志,所以--order特意允许了前导连字符。 - 时间范围是左闭右开的
[start, end)。--start-time 2024-01-01 --end-time 2025-01-01包含整个 2024 年,且恰好不含 2025-01-01。 当既没有--time-dimension、group-by 里也没有时间项时,范围会回退到每个指标的 主(聚合)时间维度(通过 Datus 的 D-TIME 扩展声明,或取数据集里唯一的is_time字段,见 datus-extensions.md); 只有当 group-by 里同时存在多个时间维度时才仍然需要显式的--time-dimension(time_range_needs_dimension)。保留名metric_time用于显式选中主时间, 在--group-by(metric_time:month→ 输出列metric_time__month)和--time-dimension里都可以用。
dosi explain¶
接受与 query 相同的 query spec 参数,但停在逻辑计划这一步,不生成 SQL。
适合在挑定方言之前,用来理解连接路径、扇出分支归属和粒度处理。
$ dosi explain --model fixtures/tpcds/model.yaml \
--metrics customer_lifetime_value --group-by store.s_state
计划以文本渲染,其中包含每个 JOIN 的类型(left / inner),
据此可以确认 Datus 的 join_type 扩展是否生效。
这里的 --format json 只作用于错误路径,成功的计划只有文本形式。
输出格式¶
--format 接受三个值之一:
text(默认):面向人,结果行/列表是对齐的表格,编译结果是原始 SQL, 计划是缩进的树。NULL单元格以灰显渲染。颜色会自动探测终端。json:所有输出(以及所有错误)都是机器可读的。query的编译结果是{dialect, sql};--execute会再加上{columns, rows: [{col: val}], …};validate是{issues, compile_errors};list是一个对象数组。 错误带有稳定的code、涉及到的名字、当一个错误引用存在备选时给出的candidates,以及当某种改写能成功时给出的suggested_retry, Agent 类调用方无需解析自然语言就能自我纠正。稳定 API 是错误码,不是错误文本。arrow:在 stdout 上输出一个 Arrow IPC 流,仅用于query --execute(其它命令会报错:--format arrow only applies to 'query --execute')。结果批次从数仓适配器直通 stdout,不做行物化, 可以零 JSON 解析地管进 DuckDB、Polars 或 pyarrow。需要支持 Arrow 的构建 (默认构建,或任意exec-*-arrow/exec-flightsql/exec-duckdbfeature; 一个不含这些 feature 的--no-default-features构建会在运行时拒绝--format arrow)。
# Stream results into DuckDB for further analysis
$ dosi query --model model.yaml --metrics revenue --group-by orders.status \
--execute --connection prod-ch --format arrow \
| duckdb -c "SELECT * FROM read_arrow('/dev/stdin')"
# Or into Polars
$ dosi query ... --execute --format arrow \
| python -c "import polars as pl,sys; print(pl.read_ipc_stream(sys.stdin.buffer))"
在数仓上执行¶
--execute 会执行编译出的 SQL 并打印结果行。不指定连接就用本地 DuckDB,
默认进程内、原生 Arrow(内置的 exec-duckdb);--db <file> 指向
DuckDB 文件,不指定则用内存库。带上 --connection <name>,
则指向该配置对应的数仓和方言。
$ dosi query --model model.yaml --metrics revenue --group-by orders.status \
--execute --connection prod-sr
连接配置沿用
Datus agent.yml 的 datasources:
词汇表。可以把 --connections 指向一份完整的 agent.yml(读取
services.datasources),或一份独立的 datasources: 文件。不带该参数时,
按以下顺序发现该文件:DOSI_CONNECTIONS 环境变量 →
./dosi-connections.yaml → ./osi-connections.yaml →
~/.config/dosi/connections.yaml → ~/.config/osi/connections.yaml →
./conf/agent.yml → ~/.datus/conf/agent.yml。已有的 Datus 安装零配置就能用,
改名之前的 osi- 路径也仍然有效。密钥以 ${VAR} 的形式从环境变量插值。
datasources:
prod-sr:
type: starrocks
host: sr.internal
port: 9030
arrow_flight_port: 9408 # opt into Arrow Flight SQL (SR ≥3.5.1)
username: osi
password: ${SR_PASSWORD}
database: analytics
default: true # used by --execute without --connection
prod-ch:
type: clickhouse
uri: http://ch.internal:8123
username: default
database: analytics
解析规则:
--execute不带--connection时用标了default: true的那份配置; 一份都没标就退回本地 DuckDB(--db或内存)。标了default: true却解析失败的配置(比如${VAR}没设置)会在 stderr 上告警, 而不是悄悄用一个空的 DuckDB。--dialect与--connection同时使用时,方言必须与配置一致, 否则命令报错。去掉--dialect,让配置来决定就行。
数仓驱动是按 feature 开关的,好让默认的可执行文件保持精简。
按需构建(或直接用 exec-all):
| Feature | 引擎 | 结果通路 |
|---|---|---|
exec-duckdb(默认) |
DuckDB(进程内) | 原生 Arrow |
exec-mysql |
MySQL、TiDB、StarRocks、Doris(MySQL 协议) | 行 |
exec-postgres |
Postgres | 行 |
exec-hologres |
Hologres(Postgres 协议;隐含 exec-postgres) |
行 |
exec-gaussdb |
GaussDB / openGauss(原生 SHA256 认证驱动) | 行 |
exec-oracle |
Oracle Database(ODPI-C;运行时需要 Instant Client) | 行 |
exec-http |
ClickHouse、Trino | 行 |
exec-http-arrow |
ClickHouse FORMAT ArrowStream |
原生 Arrow |
exec-flightsql |
StarRocks / Doris 的 Arrow Flight SQL(arrow_flight_port:) |
原生 Arrow |
exec-snowflake |
Snowflake(SQL API v2 + 密钥对 JWT) | 行 |
各引擎的配置方式与 Arrow 结果通路的支持状态见 connectors.md 和 arrow.md。
另请参阅¶
- semantics.md:CLI 所编译到的那份规范性行为契约 (指标推断、连接、扇出保护、时间处理)。
- rest-api.md:同样的能力,通过 HTTP 提供。
- extensions-guide.md:可选的 Datus 模型扩展
(
join_type、fill_nulls_with、time_dimension、time_granularity、dataset);它们的规范性契约与版本策略见 datus-extensions.md。 - connectors.md:数仓连接器的配置。
- arrow.md:如何启用 Arrow 结果传输,以及哪些环节会变快。