duckfn 0.0.18

Write DuckDB extensions in plain Rust: attribute macros that turn ordinary functions into scalar/aggregate/table functions, SQL macros and nested LIST/MAP/ARRAY/STRUCT types.
Documentation
# name: test/sql/functions/dynamic_table_function.test
# description: 动态列表函数:bind 阶段由外部元数据决定 schema(宏层 dynamic_columns 与底层手动 impl),标量/LIST/STRUCT/MAP 列、NULL 单元格、分批流式输出与注册控制
# group: [functions]

require duckfn

# ============================================================================
# 宏层:#[duck_table_function(dynamic_columns = true)]
# 列名与列类型在 bind 阶段由「外部元数据」给出,而不是来自编译期结构体
# ============================================================================

# DESCRIBE 校验动态列名与列类型(标量 + LIST + STRUCT + MAP)
query TT
SELECT column_name, column_type FROM (DESCRIBE SELECT * FROM dfn_table_dynamic('sales', 1));
----
id	BIGINT
region	VARCHAR
amount	DOUBLE
tags	VARCHAR[]
info	STRUCT(host VARCHAR, code BIGINT)
attrs	MAP(VARCHAR, BIGINT)

# 标量列 + 可空标量列(每两行一个 NULL)
query ITR
SELECT id, region, amount FROM dfn_table_dynamic('sales', 3);
----
0	r0	0.0
1	r1	NULL
2	r2	1.0

# LIST / STRUCT / MAP 列:len(NULL) 是 NULL,STRUCT 子字段与 MAP 下标读取照常
query IIT
SELECT id, len(tags), info.host FROM dfn_table_dynamic('sales', 6) ORDER BY id;
----
0	0	host-0
1	1	host-1
2	2	NULL
3	3	host-0
4	NULL	host-1
5	1	NULL

query II
SELECT id, attrs['k0'] FROM dfn_table_dynamic('sales', 4) ORDER BY id;
----
0	NULL
1	0
2	NULL
3	0

# LIST 元素本身按序写出:每 5 行整个 LIST 为 NULL
query IT
SELECT id, CAST(tags AS VARCHAR) FROM dfn_table_dynamic('sales', 5) ORDER BY id;
----
0	[]
1	[t0]
2	[t0, t1]
3	[t0, t1, t2]
4	NULL

# 零行也必须声明出列:schema 只来自外部元数据
query I
SELECT count(*) FROM dfn_table_dynamic_empty('sales');
----
0

query T
SELECT column_type FROM (DESCRIBE SELECT * FROM dfn_table_dynamic_empty('sales')) WHERE column_name = 'tags';
----
VARCHAR[]

# bind 阶段读元数据失败 -> 整条查询失败
statement error
SELECT * FROM dfn_table_dynamic('nope', 1);
----
dfn_table_dynamic: unknown source `nope`, valid sources are: sales

# ============================================================================
# 数据规模:跨 chunk(vector_size 默认 2048)时动态列也不能丢行或串位
# ============================================================================

query III
SELECT count(*), min(id), max(id) FROM dfn_table_dynamic('sales', 5000);
----
5000	0	4999

query IIT
SELECT id, len(tags), info.host FROM dfn_table_dynamic('sales', 5001) WHERE id >= 2047 ORDER BY id LIMIT 3;
----
2047	3	host-1
2048	0	NULL
2049	NULL	host-0

# 动态列能正常参与 WHERE / ORDER BY / 聚合
query I
SELECT sum(amount)::BIGINT FROM dfn_table_dynamic('sales', 10);
----
10

# ============================================================================
# 底层:手写 DynamicTableFunctionAdapter + #[duck_custom_register]
# 列名与列数都由 bind 阶段的参数算出来
# ============================================================================

query TT
SELECT column_name, column_type FROM (DESCRIBE SELECT * FROM dfn_table_dynamic_manual(start=1, label='step'));
----
step	BIGINT
squared	BIGINT

query II
SELECT * FROM dfn_table_dynamic_manual(start=3, label='step');
----
3	9
2	4
1	1

# label 是可空命名参数,缺省时列名回落为 n
query T
SELECT column_name FROM (DESCRIBE SELECT * FROM dfn_table_dynamic_manual(start=1)) WHERE column_name = 'n';
----
n

# ============================================================================
# 底层:手写 DuckBindArgs + DuckDynamicValue::from_duck_value
# 显式 `NULL` 必须被读成 None —— 只判 `Value::is_null()` 会漏掉,触发 FFI foreign exception
# ============================================================================

query I
SELECT * FROM dfn_table_dynamic_echo_arg(42);
----
42

query I
SELECT * FROM dfn_table_dynamic_echo_arg(NULL);
----
NULL