数仓连接器¶
Dosi 通过 SQL 下推来执行:它把一次指标查询编译成方言 SQL,由数仓来跑。
连接器(适配器)负责连接建立、语句提交,以及把结果归一化成一个 ResultSet
(列 + 由 Null | Bool | Int | Float | Str 组成的行,日期用 ISO-8601 字符串),
好让不同引擎的结果可以逐一比对。
这一页是每个连接器的配置指南。原生 Arrow 结果传输 (DuckDB、ClickHouse、StarRocks/Doris、Databricks)见 arrow.md; 内部的成熟度阶梯、状态矩阵和变更记录见 design/connector-maturity.md。
连接配置¶
连接配置沿用 Datus agent.yml 的
datasources: 词汇表。dosi-exec 保留了自己那套最小化的 QueryExecutor
接口(刻意不与 datus-db-adapters 对齐),但配置是共享的,
所以一套 Datus 的配置不需要任何转换。
--connections <path>(环境变量 DOSI_CONNECTIONS)既接受一份完整的
agent.yml(配置在 services.datasources 下),也接受一份独立文件
(同样的映射放在顶层的 datasources: 下)。不带该参数时,
按 ./dosi-connections.yaml、~/.config/dosi/connections.yaml、
./conf/agent.yml、~/.datus/conf/agent.yml 的顺序发现该文件,
已有的 Datus 安装零配置就能用。改名之前的 ./osi-connections.yaml 和
~/.config/osi/connections.yaml 仍会作为回退被发现,各自紧排在对应的 dosi- 路径之后。
datasources:
local-duck:
type: duckdb
uri: duckdb:///warehouse.duckdb # duckdb:////abs vs duckdb:///rel; omit for in-memory
prod-sr:
type: starrocks
host: sr.internal
port: 9030
username: osi
password: ${SR_PASSWORD} # ${VAR} / ${VAR:-fallback} from the environment
database: analytics
default: true # used by --execute without --connection
ch:
type: clickhouse
uri: http://localhost:8123 # or host/port
username: default
database: default
trino:
type: trino
host: trino.internal
port: 8080
catalog: hive # session catalog for the compiled schema.table names
schema: sales
snowflake:
type: snowflake
account: ab12345.us-east-2.aws # full identifier; JWT uses the leading locator
username: DOSI_TEST
warehouse: COMPUTE_WH
role: SYSADMIN # optional
database: ANALYTICS # session default for two-part names
private_key_file: ~/.config/dosi/snowflake_key.p8 # PKCS#8 PEM
private_key_file_pwd: ${DOSI_SF_KEY_PASS} # omit if the key is unencrypted
有几条语义值得知道:
- 恰好写成
${VAR}的密钥是惰性解析的:变量没设置文件也能加载, 只有真正用到那个连接时才报错(并带上export提示)。写明文password:也能用,但会在 stderr 上告警。 - 一条坏掉的配置(用不到的数据源里
${VAR}未设置、type: sqlite这种不支持的类型 等等)不会拖垮整个文件,它的错误只在这条配置被点名时才浮现。 未知的配置键会被忽略(Datus 的适配器会在那里放私有设置); 1.0 之前的 osi 写法(dialect、user、password_env、path、url, 以及顶层的connections:列表)会被拒绝,并给出迁移提示。 - 最多只能有一条配置设置
default: true。
运行:dosi query --metrics revenue --group-by orders.status --execute --connection prod-sr。
--execute 不带 --connection 时,如果有配置标了 default: true 就用它,
否则用本地 DuckDB(--db <file> 或内存库)。
连接器¶
DuckDB¶
通过内置的 duckdb crate 在进程内运行(默认的 exec-duckdb feature):
每个执行器一条惰性打开的连接,结果一次性解码为 Arrow RecordBatch(query_arrow),
行数据经由共用的归一化器派生。init_sql 在连接打开时执行一次;execute_batch
在内存数据库上同样有效(状态保存在连接上)。不需要任何外部可执行文件。
配置字段:uri(duckdb:///...;省略 = 内存库)。
--no-default-features 会换成历史上那套通过 CLI 外调的实现(系统的 duckdb
可执行文件、JSON 模式、每次查询一个新进程;那里内存库上的 execute_batch
是一个 config 错误)。为精简构建保留一个版本。
datasources:
local_duckdb:
type: duckdb
uri: duckdb:////absolute/path/warehouse.duckdb # duckdb:///rel/path = relative; omit = in-memory
MySQL / TiDB / StarRocks / Doris¶
同一个适配器(dosi-exec/src/mysql.rs),走 MySQL 协议,并且刻意使用文本协议
(StarRocks/Doris 对预处理语句的支持参差不齐)。四者都在真实服务器上跑通了完整语料
(MySQL 8.4、TiDB v8.5、StarRocks 3.3、Doris 2.1)。
MySQL 和 TiDB 没有 FULL OUTER JOIN,但这已经不再是查询上的限制:
多分支合并(多个指标处在不同粒度)在那里会被下降成可移植的
UNION-keys CTE + LEFT JOIN 形态,和 Postgres 家族用的是同一套。
StarRocks 和 Doris 仍然保留 FULL OUTER 形态。
四者共用同一种配置形态,只有 type 不同。也可以用驱动 URL:
uri: mysql://user:pass@host:port/db。
Arrow Flight SQL(exec-flightsql,仅 StarRocks/Doris):
在配置里加上 arrow_flight_port:,查询就切到 Flight 执行器:
由 FE 做规划,客户端直接从各 BE 拉取 Arrow 批次
(端到端列式,不经 FE 做行序列化)。这个键的值是 FE 的 flight 端口
(StarRocks 的 fe.conf arrow_flight_port,需 StarRocks ≥3.5.1;
Doris 的 fe.conf arrow_flight_sql_port,需 Doris ≥2.1.5,两者共用一个 osi 键)。
去掉这个键即回退到 MySQL 协议。
prod-sr:
type: starrocks
host: sr.internal
port: 9030 # MySQL wire — still used as seeding/DDL fallback
arrow_flight_port: 9408 # presence opts queries into Arrow Flight SQL
username: osi
password: ${SR_PASSWORD}
database: analytics
首次真机接触(StarRocks 4.0.5,2026-07-13)中的发现, 全部已在适配器里处理或记录在案:
- 没有 DoPut:
acceptPutStatement未实现,适配器把每条语句 (DDL/DML/USE)都走查询通路(GetFlightInfo),而不是execute_update。 - SR 4.0 上库级 DDL 在 Flight 上会失败(
CREATE/DROP DATABASE→ 一个笼统的 RPC 错误);表级 DDL 和 DML 能走到分析器。请用 MySQL 协议来灌数据和管理 schema (语料的 flight 运行正是这么做的)。 - BE 可达性:FE 会把每个 BE 自己的地址交给客户端做直连读取。
客户端和 BE 必须对这个地址达成一致。在 Docker 里请把 BE 的 flight 端口
1:1 发布(
9419:9419),或使用引擎自带的 FE 代理回退方案 (StarRocks 的arrow_flight_proxy*会话变量;Doris 的public_host+arrow_flight_sql_proxy_port)。 - FE 的 flight 服务可能会重置启动后的第一条连接,适配器会重试一次。
datasources:
mysql_prod:
type: mysql # or tidb
host: ${MYSQL_HOST}
port: 3306 # tidb default: 4000
username: osi
password: ${MYSQL_PASSWORD}
database: analytics
starrocks:
type: starrocks # or doris
host: ${STARROCKS_HOST}
port: 9030 # FE MySQL-protocol port (doris: 9030 too)
username: ${STARROCKS_USER}
password: ${STARROCKS_PASSWORD}
database: ${STARROCKS_DATABASE}
Postgres¶
使用 postgres crate(同步封装)。解码策略:用 prepare 拿到权威的类型 OID,
用 simple_query 拿文本值,NUMERIC(SUM/AVG 的输出)无需引入 decimal 依赖即可归一化。
会话被固定到 UTC(SET TIME ZONE 'UTC'),这样无论服务端默认时区如何,
TIMESTAMPTZ 的文本都会裁成不带时区的 ISO 形式。对 Postgres 16 是 32/32 语料通过,
并且在一台配置了 timezone=America/Los_Angeles(PST)的服务器上复验过。
datasources:
warehouse_pg:
type: postgres
host: pg.internal # or uri: postgres://osi:pass@pg.internal/analytics
port: 5432
username: osi
password: ${PG_PASSWORD}
database: analytics
Hologres¶
阿里云 Hologres 使用 Postgres 协议,所以它复用 Postgres 驱动,
并原封不动地继承其解码策略(用 prepare 拿类型 OID、用 simple_query 拿文本;
会话固定为 UTC)。编译出的 SQL 按 PostgreSQL 生成。有两点是 Hologres 特有的:
- 批量执行一次只发一条语句。 多语句脚本会构成一个隐式事务,
而 Hologres 拒绝在一个事务里混用 DDL 和 DML,所以
execute_batch会拆分脚本(正确处理字符串字面量、带引号的标识符、美元引用的函数体和注释), 在同一条连接上逐条发送。这与 datus-hologres 适配器在execute_queries里提出的限制是同一条。 - 控制台给出的接入点可能自带端口。
host:既接受<instance>.hologres.aliyuncs.com,也接受<instance>.hologres.aliyuncs.com:80; 如果两处都写了端口,必须一致。只写主机名时默认端口是 80 (Hologres 对外公布的那个),而不是 5432。
凭据是一对阿里云 AccessKey。username/password 与
access_key_id/access_key_secret 互为别名,二选一,不要都写。
在任何其它 type: 上使用 AccessKey 写法都会被拒绝,
这样凭据就绝不会被悄悄丢掉。
datasources:
hologres:
type: hologres
host: ${HOLOGRES_HOST} # or host:port; bare hostname defaults to :80
port: ${HOLOGRES_PORT:-80}
username: ${HOLOGRES_ACCESS_KEY_ID} # alias: access_key_id
password: ${HOLOGRES_ACCESS_KEY_SECRET} # alias: access_key_secret
database: ${HOLOGRES_DATABASE}
schema: public # default; pins search_path
sslmode: disable # see the TLS note below
TLS: sslmode 接受 libpq 的完整词汇表,并遵循 libpq 那套精确的校验阶梯:
require 只加密、不校验(所以自签服务端证书可用)—— 除非同时设置了
sslrootcert:,那时会像 verify-ca 一样校验证书链(这是 libpq 有文档记载的
向后兼容行为);verify-ca 会用 sslrootcert: 指定的 CA 包校验证书链
(不含系统根证书 —— 只信任那一个 CA);verify-full 再加上主机名校验。
不带 sslrootcert 的 verify-* 会在构造时被拒绝,并给出可操作的错误。
Hologres 在 80 端口上的公网接入点是明文的(sslmode: disable),
语料也正是在这种配置下验证的。
用 --features exec-hologres 构建(exec-all 隐含)。语料运行以
HOLOGRES_HOST + HOLOGRES_ACCESS_KEY_ID / HOLOGRES_ACCESS_KEY_SECRET /
HOLOGRES_DATABASE(与 Python 适配器文档里相同的那组变量)为开关,
或者以一个普通的 DOSI_TEST_HOLOGRES_URL 为开关。
对真实 Hologres 95/95 语料通过(2026-08-01),无任何声明的跳过。 这是第一个满分通关的非 DuckDB 引擎。
有两个方言细节已经替你处理好了:
DATEDIFF会被改写,而不是被拒绝。 Hologres 有DATEDIFF, 但单位参数在最后(DATEDIFF(end, start, 'day'),计算的是d1 - d2), 而模型是按单位在前的写法写的。编译器会轮转参数列表, 一并修正单位位置和符号。已经按 Hologres 自己写法写的调用则原样保留。 Hologres 的单位词汇表是个子集,没有week,也没有quarter。- 会话固定为 UTC,这一点在这里比在 Postgres 上更要紧。
Hologres 的
DATE_TRUNC不接受DATE(它的签名是TIME|TIMESTAMP|TIMESTAMPTZ),所以日期会被放宽成 TIMESTAMPTZ、 结果就带上了时区,而实例默认是PRC(+08)。固定时区能让时间粒度分桶 保持不带时区,并且可以跨引擎比对。
有一处差异 Dosi 不做归一化:Hologres 使用 C 排序规则,
所以文本上的 ORDER BY 是按字节排的。这与 DuckDB 一致,
但和一台默认 en_US.UTF-8 的 Postgres 不同。
GaussDB¶
华为 GaussDB / openGauss 派生自 PostgreSQL(9.2 血统):编译出的 SQL 按 PostgreSQL
生成,解码策略与 Postgres 执行器完全一致(用 prepare 拿类型 OID、
用 simple_query 拿文本;会话固定为 UTC)。真正的分野在于线上协议:
GaussDB 用一套私有的 SHA256 握手替换了 PostgreSQL 的 SASL 认证,
而它的认证码编号与 PG 的 SASL 相撞,标准的 Postgres 驱动
(libpq、tokio-postgres)在密码还没被校验之前就失败了。
因此该执行器使用 gaussdb 驱动(一个带有 Datus 的 RFC 5802 修复的
rust-postgres 分支;见 design/gaussdb-sha256-auth.md),原生支持那套握手,
所以 pg_hba 方法为 sha256 的服务器(也就是 GaussDB 的生产默认配置)
无需任何服务端配置改动即可工作**。
认证支持情况,按服务器的 hba 方法 × 账号的密码存储格式
(设置密码时的 password_encryption_type)列出:
| 密码存储 hba | md5 |
sha256 |
|---|---|---|
MD5(type=0) |
✅ | ✅(服务端回退到 MD5) |
SHA256(type=2,默认) |
❌ 拒绝并给出提示¹ | ✅ 原生 SHA256 |
MD5+SHA256(type=1) |
✅ | ✅ 原生 SHA256 |
¹ 在这种混合情形下,服务端在握手里省略了 PBKDF2 的迭代次数,
所以任何客户端都无法推导出密钥。错误提示会讲明这一点:
请在 password_encryption_type = 1 下重设该账号密码,或把 hba 方法改成 sha256。
以 SM3 存储的密码(type=3)同样会被拒绝,并给出可操作的错误。
datasources:
gaussdb:
type: gaussdb
host: gauss.internal # or uri: postgres://osi:pass@gauss.internal:8000/analytics
port: 8000 # GaussDB(DWS) default; openGauss uses 5432
username: osi
password: ${GAUSSDB_PASSWORD}
database: analytics
schema: main # optional; pins search_path
sslmode: require # managed instances enable ssl=on
# sslrootcert: /etc/ssl/gauss-ca.pem # only for verify-ca / verify-full
兼容模式: GaussDB 的库以 PG、A(Oracle —— GaussDB 的默认)或 B
(MySQL)兼容模式创建;较新的 GaussDB 内核还多了 M(完全 MySQL)。
PG、A、B 都受支持:引擎发出的 SQL 面在每种模式下都由完整语料验证过
(PG、A、B 一律 149/150 —— 唯一的跳过就是 Postgres 自己也声明的那个
三参数 DATEDIFF 用例),逐个构造的语义差异记录在
design/gaussdb-compat-contract.yaml 里并被持续断言。
在配置里声明你预期的模式,执行器会在首次连接时校验,不匹配就拒绝运行 (假设错了会悄悄改变语义,所以这里是硬失败):
M 兼容模式的库会在首次连接时被拒绝,并给出可操作的错误:M 不是一种语义变体,
而是完全不同的 SQL 方言 —— 那是 MySQL 语法,连 CAST(x AS varchar) 都是语法错误
(已在 GaussDB Kernel 505 上实测),PostgreSQL 方言的 SQL 根本跑不了。
有两处与模式相关的数据语义是明确声明、而不是糊过去的,因为它们存在于已存储的数据里、
而不在查询里:A 模式下空字符串就是 NULL(写入 '' 存的是 NULL,col = ''
永远匹配不上 —— 可移植的空值判断要写 col = '' OR col IS NULL);
B 模式下字符串比较忽略尾部空格(MySQL 的 PAD SPACE)。
还有两处分歧属于整个引擎面而非 GaussDB 特有,记录在那份契约文件里:
所有 GaussDB 模式下整数除法都产出实数(这是既有引擎分裂中 MySQL/ClickHouse 的那一侧),
以及 B 模式升序时 NULL 排在最前(与 MySQL/TiDB 完全一致)。
TLS: 与 Postgres 连接器相同的 sslmode/sslrootcert 处理方式,
底层走该 fork 的 native-tls 实现。华为云上的托管实例通常启用了 ssl=on 且用自签证书,
那里正确的设置是 sslmode: require(已在一台托管的 GaussDB Kernel 505.2.1 上实测)。
用 --features exec-gaussdb 构建(exec-all 隐含)。语料运行以
DOSI_TEST_GAUSSDB_URL(一个 postgres:// 接入点)为开关;
tests/docker/ 提供了一个 openGauss 服务,已预配好一个 PG 兼容模式的库
和一个 sha256-hba 的登录方式。
对真实 openGauss 7.0.0-RC2 语料 149/150 通过(2026-08-13),走的是原生 SHA256
握手。唯一一处声明的跳过,就是 Postgres 自己也声明的那个三参数 DATEDIFF
用例(被拒绝时给出的 SQLSTATE 42883 也完全相同)。
Oracle¶
Oracle Database 有一个一等公民级别的生成器:编译出的 SQL 用 FETCH FIRST 做限制、
用 TRUNC(x, 'FMT') 做时间粒度截断、用带引号的间隔字面量(INTERVAL '1' MONTH),
表别名不带 AS。Oracle 23ai+ 甚至自带原生的三参数 DATEDIFF,
所以其它每个引擎都声明为跳过的那个用例在这里能跑通
(19c/21c 会需要那个跳过,等有了这些版本的真机运行再声明)。
执行器通过 ODPI-C 使用 oracle crate。构建时没有任何 Oracle 依赖:
ODPI-C 随 crate 一起编译,并在首次连接时 dlopen
Oracle Instant Client(免费,约 80MB)。只有真正要连 Oracle 的机器才需要装它,
其他人不受影响。如果它不存在,连接会失败并给出可操作的 DPI-1047 提示。安装方式:
Linux 上 dnf install oracle-instantclient-basic(或解压 + LD_LIBRARY_PATH),
macOS 上挂载 DMG 并把 libclntsh.dylib 软链到 ~/lib。
架构必须与可执行文件匹配(在 Rosetta 下的 x86_64 构建需要 Intel 版客户端)。
会话在连接时就被固定:TIME_ZONE = 'UTC'(不带时区的 Value 契约)以及 ISO 的
NLS_DATE_FORMAT/NLS_TIMESTAMP_FORMAT:编译出的
CAST('2024-01-01' AS DATE) 字面量要经 NLS 解析,而其 DD-MON-RR 默认格式会拒绝它们。
datasources:
oracle:
type: oracle
host: db.internal # or uri: oracle://osi:pass@db.internal:1521/ORCLPDB1
port: 1521
username: osi
password: ${ORACLE_PASSWORD}
database: ORCLPDB1 # the service name
Oracle 里 schema 就等于 user:编译出的 SQL 引用 main.<table>,
所以登录用户(或它能看到的某个 schema)就是 main,
语料容器设置了 APP_USER: main。有一处语义差异 Dosi 不做归一化:
Oracle 把 '' 当作 NULL。
用 --features exec-oracle 构建(exec-all 隐含)。语料运行以
DOSI_TEST_ORACLE_URL(oracle://user:pass@host:port/service)为开关;
tests/docker/ 提供了一个 gvenzl/oracle-free(23ai)服务。
对 Oracle 23ai Free 语料 150/150 通过(2026-08-13),零声明跳过。
凭借 23ai 原生的 DATEDIFF,它与 DuckDB、Snowflake、Hologres 一同进入零跳过阵营。
ClickHouse¶
走 HTTP 接口,输出 JSONCompact,并设置
output_format_json_quote_64bit_integers=0。一次请求一条语句 →
灌数据时在客户端拆分批次。配置:uri(或 host/port)、username、
password、database。对 ClickHouse 24.8 是 45/45 语料通过
(2026-07-13,含逐用例的 Arrow 一致性校验)。
Arrow 通路(exec-http-arrow):同样的 POST,加上
default_format=ArrowStream,批次以真正的流式解码
(lz4 分帧;output_format_arrow_string_as_string=1 让字符串保持 Utf8)。
适配器里处理掉的一个坑:ClickHouse 的 Arrow 写出器把 Date 导成裸的 UInt16
(epoch 天数)、把 DateTime 导成裸的 UInt32(epoch 秒),
而 v24.8 没有任何输出设置能保留它们,于是适配器会先跑一次
DESCRIBE (query)(只做类型推断,不执行),再把那些列即时重新定型为
Date32/Timestamp(纯粹是重新解释;DateTime64 和 Date32 本来就导得正确)。
datasources:
ch:
type: clickhouse
uri: http://ch.internal:8123 # or host/port (port defaults to 8123)
username: default
password: ${CLICKHOUSE_PASSWORD}
database: analytics
Trino¶
走 REST 的 /v1/statement,用 nextUri 轮询(50→200 ms 退避)。
HTTP 503 按协议约定重试("忙,请再问一次"),上限约 10 秒,
超过则抛出 timeout 错误。测试通过内置的 memory catalog 灌数据。
配置:uri(或 host/port)、username(X-Trino-User)、
catalog/schema(或用 database 写成 catalog[.schema])作为会话默认值。
对 Trino 467 是 32/32 语料通过。
datasources:
trino:
type: trino
host: trino.internal
port: 8080 # or uri: http://trino.internal:8080
username: osi # sent as X-Trino-User
catalog: hive # session catalog for compiled schema.table names
schema: sales # optional session schema
Snowflake¶
dosi-exec/src/snowflake.rs,由 exec-snowflake feature 开启
(ureq + rustls TLS,认证用 rsa/sha2/pkcs8,全部按开关引入,不用 SDK)。
它使用 SQL API v2(/api/v2/statements),配合密钥对 JWT 认证:
JWT 的 iss/sub 使用账号 locator(转大写、去掉 region)
以及公钥的 SHA-256 指纹;PKCS#8 私钥可以是 PBES2 加密的
(口令通过 private_key_file_pwd: ${VAR} 提供)。配置:account、username、
warehouse,可选的 role/schema/database,以及 private_key_file。
datasources:
snowflake:
type: snowflake
account: ${SNOWFLAKE_ACCOUNT} # full identifier, e.g. ab12345.us-east-2.aws
username: ${SNOWFLAKE_USER}
warehouse: COMPUTE_WH
role: SYSADMIN # optional
database: ANALYTICS # session default for two-part names
schema: PUBLIC # optional
private_key_file: ~/.config/dosi/snowflake_key.p8 # PKCS#8 PEM
private_key_file_pwd: ${SNOWFLAKE_KEY_PASSPHRASE} # omit if unencrypted
语料暴露出的两个坑,都已在适配器里处理:
- 无状态 API:每个请求彼此独立,所以
USE从不持久。 warehouse/database/schema 上下文随每个请求体一起发送;灌数据用的表名写全限定 (DB.main.<table>),这样引导阶段的CREATE DATABASE就不会被一个尚不存在的 上下文库卡住,而查询则携带库上下文,让两段式的main.<table>能解析。 - DATE 编码:API 返回的 DATE 是距 epoch 的天数
(例如
19723= 2024-01-01),而不是 ISO 字符串;解码器会做转换。 不带引号的标识符返回时会转成大写,所以语料在比对列名时不区分大小写。
对一个真实账号 45/45 语料通过(2026-07-12)。CI 在 L2 周任务里以凭据为开关
(仓库 secret DOSI_TEST_SNOWFLAKE_*),未设置时跳过。
Databricks¶
dosi-exec/src/databricks.rs,由 exec-databricks feature 开启
(ureq + rustls TLS;不用 SDK)。它对着一个 SQL warehouse 使用
Statement Execution API(/api/2.0/sql/statements),
认证方式是 Personal Access Token(Bearer)。结果请求为
disposition: INLINE、format: JSON_ARRAY;未能在 wait_timeout 内完成的语句
会被轮询到终态,跨多个 inline 分块的结果通过 result/chunks/{n} 拼接。
配置:host、warehouse(SQL warehouse 的 id)、password(那个 PAT)、
catalog,以及可选的 schema。
datasources:
databricks:
type: databricks
host: dbc-xxxx.cloud.databricks.com # a pasted https://.../ URL is tolerated
warehouse: e9678899fc3ffa09 # SQL warehouse id (from Connection details)
password: ${DATABRICKS_TOKEN} # a dapi... PAT
catalog: workspace # Unity Catalog catalog (session context)
schema: analytics # optional default schema for queries
语料暴露出的两件事,都已处理:
- catalog 作为上下文传递,而不是靠
USE:该 API 每个请求都无状态, 所以catalog随每个请求体一起发送。灌数据时只带 catalog 上下文 (用全限定的main.<table>名),这样引导阶段的CREATE SCHEMA main就不会被一个尚不存在的默认 schema 卡住;查询则携带 catalog(以及可选的 schema), 让两段式的main.<table>能解析。因此语料需要一个可写的 catalog, 只读的samplescatalog 灌不了数据。 - 编码:每个单元格都是一个 JSON 字符串,类型由
manifest.schema.columns[].type_name给出;与 Snowflake 不同, DATE 本来就以 ISO 形式返回(2024-01-01),所以不需要 epoch 天数的转换垫片。 TIMESTAMP 确实是 ISO-8601 形式(2020-01-01T00:00:00.000Z), 会被规范化成参考值的2020-01-01 00:00:00。不带引号的标识符返回时转成小写, 所以语料在比对列名时不区分大小写。
原生 Arrow 通路(exec-databricks-arrow):同一个执行器还实现了
execute_arrow,使用 format=ARROW_STREAM + disposition=EXTERNAL_LINKS:
每个结果分块都是一个位于预签名云存储 URL 上的自包含 Arrow-IPC 流,
获取时不带 workspace token(URL 本身已签名),并用 arrow-ipc 一次性解码;
行式的 execute() 再经由共用的归一化器从这些批次派生。不需要重新定型
(不像 ClickHouse):Databricks 导出的是真正的 Date32/Timestamp Arrow 类型。
零行结果不带任何链接,所以 schema 会从 manifest 里合成出来(由一致性校验发现)。
对 Databricks 来说,这是比 Foundry 的 ADBC 驱动更推荐的通路:
线上传的本来就是列式 Arrow,所以驱动那套 C-Data-Interface 零拷贝交接
并不能省下直接 arrow-ipc 解码省不掉的东西,还避免了一个笨重的 Go .so
(见 design/arrow-adbc.md §4)。
对一个免费额度的 Serverless SQL warehouse 45/45 语料通过(2026-07-13),
行通路和 Arrow 通路都是绿的(Arrow 与行通路以及 DuckDB 参考结果三方一致)。
CI 在 L2 周任务里以凭据为开关(仓库 secret DOSI_TEST_DATABRICKS_*),
未设置时跳过。