示例扩展
示例扩展是 duckfn 包自己的一部分:模块树在 test/extension/,src/bin/duckfn.rs 是它的命令行
入口;WebAssembly 那边也不需要单独的 root,同一个 lib 编成 staticlib 即可。它不单独发布,
存在的意义是把每一项功能都跑
一遍;test/sql/ 下的 sqllogictest 用例则是各函数行为的权威依据。由于它随已发布的 duckfn 包一起
分发,不用克隆仓库就能读到它的源码与测试。
构建与加载
make configure # 只做一次:准备测试运行器所需的 Python venv
make debug # -> build/debug/extension/duckfn/duckfn.duckdb_extension
示例只在打开 quack feature 时才参与编译,所以所有构建它的命令都会带上 --features quack:
make debug 靠根 Makefile 带、just build 在 Justfile 里带(顺带把结果打包成可加载的
duckfn.duckdb_extension)。裸跑 cargo build 只会得到一个没有入口符号的 cdylib,DuckDB 在
LOAD 时会拒绝它。
duckdb -unsigned -c "
LOAD './build/debug/extension/duckfn/duckfn.duckdb_extension';
SELECT rusty_echo('Jane');
"
开发时用 just sql "<SQL>" 可以一步完成重新构建并执行语句,just test 跑完整的 sqllogictest 套件。
源码组织
| 路径 | 内容 |
|---|---|
test/extension/mod.rs | 模块树 —— 入口不在这个文件里。 |
test/extension/entry.rs | duckfn_entrypoint!("duckfn");单独一个文件,CLI 才能只编模块树、不重复定义入口符号。 |
test/extension/demo/ | 每个功能域一个文件,另有若干手写 FFI 版本用于对照。 |
test/extension/functions/ | 每种注册方式一个文件:标量、聚合、表函数、类型转换、替换扫描、SQL 宏。 |
test/extension/functions/sql/ | 通过 include_str! 与 duck_sql_macro_files! 注册的 .sql 文件。 |
test/extension/types/ | 每种受支持类型的 echo 函数,标量与表函数两种形式。 |
test/sql/ | 41 个 sqllogictest 文件,与源码目录一一对应。 |
test/extension/demo/rewrite_official_template_demo.rs 是最小的可用起点 —— 它是把 DuckDB 官方的模板示例
用 duckfn 重写了一遍:
#[duck_scalar_function]
fn rusty_echo(s: String) -> String {
format!("🐤 {s} 🦀 {s}")
}
#[derive(Clone, Debug, DuckStruct)]
pub struct RustyQuackResult {
column0: String,
}
#[duck_table_function]
fn rusty_quack(name: String) -> impl Iterator<Item = RustyQuackResult> {
vec![RustyQuackResult {
column0: format!("Rusty Quack {} 🐥", name),
}]
.into_iter()
}
SELECT rusty_echo('Hello'); -- 🐤 Hello 🦀 Hello
SELECT * FROM rusty_quack('Sam'); -- Rusty Quack Sam 🐥
标量、列表、映射与结构体
demo 模块覆盖了函数可以取的形态,包括嵌套的输入与输出:
SELECT double_it5(21); -- 42
SELECT first_word_tuple('hello world'); -- hello
SELECT sum_list_w([1, 2, 3, 4]); -- 10
SELECT sum_list_nest([[1, 2], [3, null, 4], null]); -- 10
SELECT struct_scalar_w({hello_count: 15}); -- 25
SELECT struct_nest_scalar_w({structf: {hello_count: 15}, list: [1, null, 2]}); -- 18
SELECT input_map_demo(MAP {'key1': [10], 'key2': [20, 5], 'key3': null}); -- 35
SELECT CAST(input_array_demo(a) AS VARCHAR) FROM (VALUES (ARRAY[1, 2]), (ARRAY[4, null])) t(a);
-- [1, 2]
-- [4, NULL]
返回结构化数据同理:make_list_scalar_w(range) 返回 LIST,nest_list_scalar_w(range) 返回嵌套 LIST,
struct_nest_output_scalar_w(range::int) 返回嵌套 STRUCT。
错误与 NULL 的实战
error_scalar_demo 对同一个入参可以返回值、NULL 或错误:
SELECT error_scalar_demo(3); -- 6
SELECT error_scalar_demo(10); -- 报错:input is 10
SELECT error_scalar_demo(20); -- 报错:panic: input is 20
SELECT error_scalar_demo(30); -- 报错:explicit panic
上面两个 panic! 例子(20、30)在原生 CLI 上会打印可读消息,但在浏览器里会变成
Maximum call stack size exceeded —— Rust 的 panic! 不会以一条消息的形式穿过 wasm→JS 边界。
只有 error_scalar_demo(10)(返回 Err(duck_error(...)))在两边都能干净报错。报错请用
Err(duck_error(...)) 而不是 panic!;参见已知问题。
聚合函数
SELECT word_count_m(sentence)
FROM (VALUES ('hello world'), (' padded '), (''), (NULL)) t(sentence);
-- 3
SELECT range % 3 AS g, agg_list_w(range)
FROM range(9)
GROUP BY g;
-- 0 [0, 3, 6]
-- 1 [1, 4, 7]
-- 2 [2, 5, 8]
word_count_w 通过 #[duck_custom_register] 与 WordCountStateWrapper 手动注册;agg_list_w 把行收集成
LIST,遇到值 12 时让查询失败。
表函数与命名参数
SELECT * FROM count_down_m_simple(start=12); -- 11, 10, 9, … 0
SELECT * FROM bind_map_demo(MAP {'key1': [10], 'key2': [20, 5], 'key3': null});
-- 10
-- 25
-- 0
类型转换与替换扫描
SELECT CAST('42' AS INTEGER); -- 42
SELECT TRY_CAST('abc' AS INTEGER); -- NULL
SELECT CAST('abc' AS INTEGER); -- 报错:not an integer: "abc"
SELECT * FROM '3.points'; -- x 0 y 0 / 1 1 / 2 4
SELECT * FROM 'hi.echo'; -- hi.echo 7
SQL 宏
SELECT clamp(range, 4, 7) FROM range(9);
-- 4, 4, 4, 4, 4, 5, 6, 7, 7
SELECT add_two_v1(1), add_two_v2(2), add_two_v3(3), add_two_v4(4);
-- 3, 4, 5, 6
四个 add_two_* 是同一个宏的四种返回形态:SqlMacro、DuckResult<SqlMacro>、DuckResult<String> 与 String。
dfn_macro_inc_* 系列来自 include_str!,dfn_macro_files_* 来自 duck_sql_macro_files!。
类型 echo
每种受支持类型都有标量与表函数两种形式的恒等函数:
SELECT dfn_echo_integer(42); -- 42
SELECT dfn_echo_date(DATE '2024-01-02'); -- 2024-01-02
SELECT CAST(dfn_echo_list_integer_n([1, NULL, 3]) AS VARCHAR); -- [1, NULL, 3]
SELECT CAST(v AS VARCHAR) FROM dfn_table_echo_bool(true, count => 3);
-- true
-- NULL
-- true
命名规律固定:标量是 dfn_echo_<type>,表函数是 dfn_table_echo_<type>,其中 <type> 形如 integer、
list_integer、map_varchar_integer、array_bigint、struct_with_list 等。
测试套件
test/sql/ 与源码目录一一对应,是关于行为最精确的描述:
test/sql/
├── demo/ 与 demo 目录同名的一系列用例,外加 rusty_echo、rusty_quack、sql_lang_demo
├── functions/ scalar_function、aggregate_function、table_function、cast_function、
│ replacement_scan、sql_macro
└── types/ 每种受支持类型的 <type>_scalar_echo 与 <type>_table_echo
make configure debug test # 或者:just test