跳到主要内容

错误与 panic

错误​

DuckOptionResult<T> 就是 Result<Option<T>, ExtensionError>,它让一个函数同时具备三种结果:

#[duck_scalar_function]
fn dfn_scalar_ret_checked(i: i32) -> DuckOptionResult<i32> {
if i == 0 {
return Err(duck_error("dfn_scalar_ret_checked: division by zero"));
}
if i < 0 {
return Ok(None);
}
Ok(Some(100 / i))
}
返回值SQL 侧表现
Ok(Some(v))v
Ok(None)NULL
Err(e)查询失败,错误信息为 e 的内容。
SELECT dfn_scalar_ret_checked(4); -- 25
SELECT dfn_scalar_ret_checked(-1); -- NULL
SELECT dfn_scalar_ret_checked(0); -- 报错:dfn_scalar_ret_checked: division by zero

duck_error("…") 用于构造错误。ExtensionError 即 quack_rs::error::ExtensionError,因此 ? 也能直接 传播 quack-rs API 的错误;需要把变量放进消息里时用 format!。

示例扩展里通行的两条约定,值得照做:

  • 消息以函数名开头,出错时一眼能看出源头:dfn_cast_str_to_int: not an integer: "abc"、 dfn_table_checked: n must be >= 0。
  • Ok(None) 留给「这一行没有值」,真正的失败用 Err。

需要注意,Result<T, ExtensionError> 不是宏接受的返回形态 —— 请用 DuckOptionResult<T>, 把成功的值包进 Some。

panic​

duckfn 函数里的 panic 不会跨 FFI 边界展开,而是被捕获并作为查询错误报告:

#[duck_scalar_function]
fn dfn_scalar_ret_panic(i: i32) -> i32 {
if i == 13 {
panic!("unlucky input: {i}");
}
i
}
SELECT dfn_scalar_ret_panic(1); -- 1
SELECT dfn_scalar_ret_panic(13); -- 报错:unlucky input: 13
在 DuckDB-Wasm 下消息不同

上面这个块跑在 DuckDB-Wasm 里,同样的输入报的是 Maximum call stack size exceeded,而不是 unlucky input: 13:被捕获的 panic 在 wasm 构建里丢掉了消息。原生构建(本地用 duckdb -unsigned)与 sqllogictest 用例报的是注释里引用的那条。

各类注册方式都一样:

类型捕获 panic 的位置示例
标量函数函数体SELECT dfn_scalar_ret_panic(13);
聚合函数行处理函数SELECT dfn_agg_panic(x) FROM (VALUES (13)) t(x);
类型转换函数体SELECT CAST('NaN'::DOUBLE AS BIGINT); → dfn_cast_double_to_bigint: not a finite number: NaN
表函数迭代器以及 bind 阶段SELECT * FROM dfn_table_full(5); → dfn_table_full: bad row 2
替换扫描回调SELECT * FROM 'boom.panic'; → dfn_scan_points: panic while handling boom.panic
panic 是安全网,不是控制流

被捕获只能保证进程不崩,它仍然会终止整条查询,而且丢失了你本可以自行选择的错误类型。 请优先使用 Ok(None) 与 Err(duck_error(…))。

各类失败的暴露位置​

表函数:返回形态决定失败在什么时候被报告:

失败类型形态结果
参数不合法DuckResult<impl Iterator<Item = Row>>在任何行产出之前查询就失败。
某行是 NULLDuckFullIteratorResult 里的 Ok(None)该行所有列都是 NULL。
某行无法产出DuckFullIteratorResult 里的 Err(…)查询失败。

类型转换:同一个 Err 会随进入方式不同而不同:

SQL结果
CAST('abc' AS INTEGER)查询失败。
TRY_CAST('abc' AS INTEGER)该行为 NULL,查询继续。

聚合函数:行处理函数返回的 Err 会让查询失败;这里没有逐行的 NULL 通道,因为一行只是更新状态。

源码与测试​

接下来​