跳转至

REST API

dosi-server(crate crates/dosi-server) 通过 HTTP 提供一份 OSI 语义模型的服务:校验模型、浏览编译后的语义层、 把指标查询编译成方言 SQL,并拿到数仓上执行。只用 JSON, 与 dosi --format json 是同一套机器契约。每个端点对应的 shell 用法见 CLI 参考

交互式文档:每个运行中的服务都会在 /openapi.json 上托管自己的 OpenAPI 3.1 规范,并在 /docs 上提供 Swagger UI (用 utoipa 生成;UI 资源已内置, 所以两者都能离线使用)。这一页是配套的叙述性说明;权威的 schema 是那份规范。

启动服务

# compile-only, in-memory DuckDB for execution
cargo run -p dosi-server -- --model fixtures/orders/model.yaml

# with warehouse connectors and a connections file (a `datasources:` YAML
# in Datus agent.yml vocabulary, or a full agent.yml)
cargo run -p dosi-server --features exec-all -- \
  --model model.yaml --connections dosi-connections.yaml --bind 0.0.0.0:8080
参数 环境变量 默认值 含义
--model DOSI_MODEL 必填 OSI 模型文件(.yaml/.json
--connections DOSI_CONNECTIONS ./dosi-connections.yaml~/.config/dosi/connections.yaml./conf/agent.yml~/.datus/conf/agent.yml(旧的 osi- 路径仍会被发现) 数仓配置,用 Datus agent.yml 的 datasources: 词汇表(connectors.md
--bind DOSI_BIND 127.0.0.1:8081 监听地址
--db 内存 无连接执行时使用的 DuckDB 文件
--max-concurrent-executions DOSI_MAX_EXECUTIONS 16 执行的并发上限
--pool-size DOSI_POOL_SIZE 8 每份配置的连接池上限
--execute-timeout-secs 60 单次数仓执行的时间预算
--request-timeout-secs 30 非执行类请求的时间预算
--auth-token DOSI_SERVER_TOKEN 关闭 要求 /v1/* 携带 Authorization: Bearer <token>
--disable-execute 关闭 只编译的部署方式(execute → 403)
--osi-datus / --osi-basic --osi-datus 引擎模式,作用于整个服务:datus 采纳 DATUS custom_extensions;basic 是严格的标准 OSI,扩展会被忽略、在启动时记为警告,并由 /v1/validate 报告(cli.md

模型在启动时加载、校验并编译一次,无效就直接退出进程; 编译出的 IR 以不可变的方式在各请求间共享。数仓执行器按配置各建一次, 所以 MySQL 家族和 Postgres 的连接是池化复用的。

端点

方法 路径 用途
GET /health 存活探测,永不需要鉴权
GET /ready 就绪探测
GET /docs/openapi.json Swagger UI / OpenAPI 规范,永不需要鉴权
GET /v1/model 模型名、路径、引擎 mode、对象数量、datus_ext_version
GET /v1/capabilities 引擎/OSI 规范/datus-ext 版本,以及本引擎读取的每个 DATUS 扩展键,附带引入它的版本和忽略它的代价(datus-extensions.md
GET /v1/datasets 数据集及其来源、键、字段数
GET /v1/metrics 指标及其推断出的种类和数据集
GET /v1/dimensions 维度(dataset.field)及时间标记
GET /v1/connections {name, dialect, available},绝不返回接入点或密钥
POST /v1/validate 校验内联的模型文本(按服务当前的引擎模式;响应包含 warnings
POST /v1/query/compile 指标查询 → SQL
POST /v1/query/explain 指标查询 → 逻辑计划(文本)
POST /v1/query/execute 指标查询 → 结果行

查询请求体

compileexplainexecute 共用同一个请求体:一个 MetricQuery, 外加 dialect(默认 duckdb)、pretty,以及仅 execute 才有的 connection (一个配置名;省略 = 本地 DuckDB)。

{
  "metrics": ["revenue", "order_count"],          // required
  "group_by": [
    {"field": "customers.region"},                // dataset.field
    {"field": "orders.order_date", "grain": "month"}  // day|week|month|quarter|year
  ],
  "where_sql": "status = 'completed'",            // pre-aggregation SQL filter
  "time_range": {"start": "2024-01-01", "end": "2025-01-01",
                 "dimension": "orders.order_date"},   // half-open [start, end)
  "order_by": [{"key": "order_date__month", "desc": true}],
  "limit": 12,
  "dialect": "postgres",
  "connection": "warehouse-prod"                  // execute only
}
$ curl -s localhost:8081/v1/query/compile -H 'content-type: application/json' \
    -d '{"metrics":["revenue"],"group_by":[{"field":"orders.order_date","grain":"month"}],"dialect":"postgres"}'
{"dialect":"postgres","sql":"SELECT DATE_TRUNC('MONTH', orders.order_date) AS order_date__month, ..."}

$ curl -s localhost:8081/v1/query/execute -H 'content-type: application/json' \
    -d '{"metrics":["revenue"],"group_by":[{"field":"customers.region"}],"connection":"pg"}'
{"dialect":"postgres","sql":"...","columns":["region","revenue"],
 "rows":[{"region":"east","revenue":340},{"region":"west","revenue":110}],"row_count":2}

$ curl -s localhost:8081/v1/validate \
    -d "$(jq -Rs '{model: .}' < model.yaml)" -H 'content-type: application/json'
{"valid":true,"issues":[],"compile_errors":[]}

Arrow IPC 流式传输

POST /v1/query/execute 带上 Accept: application/vnd.apache.arrow.stream 时,会以流式的 Arrow IPC body 而不是 JSON 返回结果: record batch 从数仓适配器经由 IPC writer 直接流进响应, 既不做行物化,也不逐值做 JSON 编码。列式消费方 (Polars、pandas/pyarrow、DataFusion、另一个 Dosi)可以零解析地读取; 收益随结果规模放大。对不主动选择它的客户端,JSON 响应逐字节保持不变。

$ curl -s localhost:8081/v1/query/execute -H 'content-type: application/json' \
    -H 'accept: application/vnd.apache.arrow.stream' \
    -d '{"metrics":["revenue"],"group_by":[{"field":"customers.region"}]}' \
    | python3 -c 'import pyarrow.ipc,sys; print(pyarrow.ipc.open_stream(sys.stdin.buffer).read_all())'

语义:

  • 预检阶段的错误仍走 JSON 信封:编译、配置、数仓层面的失败都在响应提交之前 发现,所以拿到的仍是结构化的 {"error": {code, message, hint}} 和恰当的 HTTP 状态码。
  • 流中途的失败会直接终止 body,这是流式 API 的常规做法: IPC 流在没有结束标记的情况下中止,重新发起请求即可。
  • 这条通路上,--execute-timeout-secs 限制的是首字节时间, 不是整个流的时长;并发许可会一直持有到流结束。
  • CLI 上的等价物是 dosi query --execute --format arrow(IPC 输出到 stdout)。
  • 可用性:默认开启(服务端 feature arrow)。Flight SQL 的服务端端点 属于后续工作,目前的列式出口就是这个 REST body。

错误

错误响应体原样包裹引擎的结构化错误,机器码与 dosi --format json 完全一致且稳定:

{"error": {"code": "unknown_metric",
           "message": "unknown metric \"revenu\"",
           "candidates": ["revenue", "order_count", "..."]}}
HTTP 什么时候
400 规划器拒绝(unknown_metricambiguous_dimensionno_join_path 等)、坏 JSON、未知方言、配置 config 错误
401 缺少或错误的 bearer token(仅当设置了 --auth-token 时)
403 --disable-execute 下访问 /v1/query/execute
429 所有执行槽位都忙(Retry-After: 1
501 规划器的 not_implemented
502 数仓不可达/凭据被拒/SQL 被拒(connectionauthsql_rejecteddriver
504 数仓 timeout,或服务自身的执行超时

并发模型

  • 编译、explain、列表都是纯 CPU 操作,作用在共享的内存 IR 上, 直接在异步工作线程上内联执行,无锁、无 I/O。release 构建可以在 p50 个位数毫秒的水平上持续支撑每秒数千次编译请求。
  • 执行是阻塞式的数仓 I/O:由信号量(--max-concurrent-executions)限流, 用 spawn_blocking 挪出异步工作线程,--execute-timeout-secs 到点后 放弃响应。已知的 v1 限制:被放弃的调用仍会在后台跑完, 期间一直占着许可。
  • MySQL 家族和 Postgres 的配置会池化连接(--pool-size 按配置计), ClickHouse/Trino 复用 HTTP keep-alive,DuckDB 每次调用起一个子进程。