跳转至

数仓连接器

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.ymldatasources: 词汇表。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 写法(dialectuserpassword_envpathurl, 以及顶层的 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 RecordBatchquery_arrow), 行数据经由共用的归一化器派生。init_sql 在连接打开时执行一次;execute_batch 在内存数据库上同样有效(状态保存在连接上)。不需要任何外部可执行文件。 配置字段:uriduckdb:///...;省略 = 内存库)。

--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 SQLexec-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)中的发现, 全部已在适配器里处理或记录在案:

  • 没有 DoPutacceptPutStatement 未实现,适配器把每条语句 (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/passwordaccess_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 再加上主机名校验。 不带 sslrootcertverify-* 会在构造时被拒绝,并给出可操作的错误。 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 的库以 PGA(Oracle —— GaussDB 的默认)或 B (MySQL)兼容模式创建;较新的 GaussDB 内核还多了 M(完全 MySQL)。 PGAB 都受支持:引擎发出的 SQL 面在每种模式下都由完整语料验证过 (PGAB 一律 149/150 —— 唯一的跳过就是 Postgres 自己也声明的那个 三参数 DATEDIFF 用例),逐个构造的语义差异记录在 design/gaussdb-compat-contract.yaml 里并被持续断言。

在配置里声明你预期的模式,执行器会在首次连接时校验,不匹配就拒绝运行 (假设错了会悄悄改变语义,所以这里是硬失败):

    compat_mode: PG                  # optional: PG | A | B

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_URLoracle://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)、usernamepassworddatabase。对 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(纯粹是重新解释;DateTime64Date32 本来就导得正确)。

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)、usernameX-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} 提供)。配置:accountusernamewarehouse,可选的 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: INLINEformat: JSON_ARRAY;未能在 wait_timeout 内完成的语句 会被轮询到终态,跨多个 inline 分块的结果通过 result/chunks/{n} 拼接。 配置:hostwarehouse(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, 只读的 samples catalog 灌不了数据。
  • 编码:每个单元格都是一个 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_*), 未设置时跳过。