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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# name: test/sql/functions/aggregate_function.test
# description: duck_aggregate_function 的参数/状态拆分、入参可空性、返回形式、输出类型、注册控制(auto_register / 函数集重载 / overloads_name)与 special_null_handling
# group: [functions]

require duckfn

# ============================================================================
# 基本形态:状态参数在最后 / 无返回类型
# ============================================================================

query I
SELECT dfn_agg_sum(x) FROM (VALUES (1), (2), (3)) t(x);
----
6

query T
SELECT typeof(dfn_agg_sum(x)) FROM (VALUES (1)) t(x);
----
BIGINT

# 空输入:一次 update 都没有,结果来自 Default 状态
query I
SELECT dfn_agg_sum(x) FROM (SELECT 1 AS x WHERE false) t(x);
----
0

# GROUP BY:每个分组一个状态
query II rowsort
SELECT x % 3, dfn_agg_sum(x) FROM range(0, 9) t(x) GROUP BY 1;
----
0	9
1	12
2	15

# FILTER:被过滤掉的行不进入状态
query I
SELECT dfn_agg_sum(x) FILTER (WHERE x % 2 = 0) FROM (VALUES (1), (2), (3), (4)) t(x);
----
6

# 跨 chunk(多批 update)
query I
SELECT dfn_agg_sum(x) FROM range(0, 10000) t(x);
----
49995000

# 多线程:DuckDB 会建多个状态再 combine,结果必须与单线程一致
statement ok
PRAGMA threads=4;

query I
SELECT dfn_agg_sum(x) FROM range(0, 10000) t(x);
----
49995000

statement ok
PRAGMA threads=1;

# DISTINCT 由 DuckDB 在聚合外包装去重
query I
SELECT dfn_agg_sum(DISTINCT x) FROM (VALUES (1), (1), (2)) t(x);
----
3

# 状态参数在最前:宏按签名顺序取参数,输入仍是位置参数
query I
SELECT dfn_agg_state_first(x) FROM (VALUES (1), (2)) t(x);
----
3

# 零个输入参数:注册成无参聚合,每行调用一次 update
query I
SELECT dfn_agg_row_count() FROM (VALUES (1), (2), (3)) t(x);
----
3

# 多个输入参数按位置匹配
query I
SELECT dfn_agg_weighted(x, w) FROM (VALUES (1, 10), (2, 20)) t(x, w);
----
50

# 参数类型不匹配
statement error
SELECT dfn_agg_sum('x');
----
No function matches

# ============================================================================
# 入参可空性:非 Option 参数跳过整行,Option 参数看见 NULL
# ============================================================================

# NULL 行被跳过,聚合继续(而不是让整组变成 NULL)
query I
SELECT dfn_agg_sum(x) FROM (VALUES (1), (NULL), (3)) t(x);
----
4

# 整列 NULL:所有行都被跳过
query I
SELECT dfn_agg_sum(x) FROM (VALUES (NULL), (NULL)) t(x);
----
0

# Option 参数:NULL 以 None 进入函数体,所以能数出 NULL 的个数
query I
SELECT dfn_agg_null_count(x) FROM (VALUES (1), (NULL), (3)) t(x);
----
1

query I
SELECT dfn_agg_null_count(x) FROM (VALUES (NULL), (NULL)) t(x);
----
2

# 混合:非 Option 参数拦下整行,Option 参数放行 NULL
query T
SELECT dfn_agg_mixed(a, b) FROM (VALUES (1, 2), (NULL, 5), (3, NULL), (4, 6)) t(a, b);
----
sum:16|nulls:1

# ============================================================================
# 行处理函数的返回形式:-> () 与 -> DuckResult<()>
# ============================================================================

query I
SELECT dfn_agg_checked(x) FROM (VALUES (1), (2)) t(x);
----
3

# 某一行返回 Err,整条查询失败
statement error
SELECT dfn_agg_checked(x) FROM (VALUES (1), (13), (2)) t(x);
----
dfn_agg_checked: unlucky input 13

# panic 被 duck_aggregate_unwind 捕获成查询错误
query I
SELECT dfn_agg_panic(1);
----
1

statement error
SELECT dfn_agg_panic(13);
----
dfn_agg_panic: unlucky input 13

# ============================================================================
# 输出类型与结果语义
# ============================================================================

# f64 输出 + 自定义 combine/result
query R
SELECT dfn_agg_avg(x) FROM (VALUES (1), (2), (3)) t(x);
----
2.0

# 没有有效行时 result() 返回 None -> SQL NULL
query R
SELECT dfn_agg_avg(x) FROM (VALUES (NULL), (NULL)) t(x);
----
NULL

query R
SELECT dfn_agg_avg(x) FROM (SELECT 1 AS x WHERE false) t(x);
----
NULL

# VARCHAR 输出
query T
SELECT dfn_agg_concat(x) FROM (VALUES ('a'), ('b'), ('c')) t(x);
----
a,b,c

query T
SELECT dfn_agg_concat(x) FROM (VALUES ('a'), (NULL), ('c')) t(x);
----
a,c

query T
SELECT dfn_agg_concat(x) FROM (SELECT 'x' AS x WHERE false) t(x);
----
NULL

# LIST 输出:NULL 也按位置收集进列表
query T
SELECT dfn_agg_list(x) FROM (VALUES (1), (2), (3)) t(x);
----
[1, 2, 3]

query T
SELECT dfn_agg_list(x) FROM (VALUES (1), (NULL), (3)) t(x);
----
[1, NULL, 3]

query T
SELECT dfn_agg_list(x) FROM (SELECT 1 AS x WHERE false) t(x);
----
NULL

# ============================================================================
# 注册控制:auto_register = false、手动注册、函数集重载
# ============================================================================

# 只声明、不手动注册:SQL 层看不到这个名字
statement error
SELECT dfn_agg_reg_unregistered(1);
----
Scalar Function with name dfn_agg_reg_unregistered does not exist

# 手动注册(#[duck_custom_register] + aggregate_function_builder())后可用
query I
SELECT dfn_agg_reg_manual(1);
----
1

# 函数集重载:一个 SQL 名字挂多个签名,按参数类型分派
query T
SELECT dfn_agg_reg_overload(1);
----
int(1);

query T
SELECT dfn_agg_reg_overload('x');
----
str(x);

# 两个重载分支的函数名本身没有被注册
statement error
SELECT dfn_agg_reg_over_int(1);
----
Scalar Function with name dfn_agg_reg_over_int does not exist

statement error
SELECT dfn_agg_reg_over_varchar('x');
----
Scalar Function with name dfn_agg_reg_over_varchar does not exist

# 重载匹配不到参数类型就报错
statement error
SELECT dfn_agg_reg_overload(1.5);
----
No function matches

# ============================================================================
# 新版函数集重载:DuckfnAggregateFunctionSetBuilder
#
# 每个重载是独立的 duckdb_aggregate_function,返回类型取各自签名的 Output,
# 因此同一函数集里可以有不同返回类型(旧版 AggregateFunctionSetBuilder 只能
# 在函数集上设一个统一的 returns_logical)。
# ============================================================================

# INTEGER 分支:求和,返回 BIGINT
query I
SELECT dfn_agg_set_overload(x) FROM (VALUES (1), (2), (3)) t(x);
----
6

# 返回类型随分支变化:INTEGER 分支是 BIGINT
query T
SELECT typeof(dfn_agg_set_overload(x)) FROM (VALUES (1)) t(x);
----
BIGINT

# VARCHAR 分支:拼接,返回 VARCHAR
query T
SELECT dfn_agg_set_overload(x) FROM (VALUES ('a'), ('b')) t(x);
----
str(a);str(b);

# VARCHAR 分支的返回类型是 VARCHAR
query T
SELECT typeof(dfn_agg_set_overload(x)) FROM (VALUES ('a')) t(x);
----
VARCHAR

# 空输入走各分支自己的 result 语义:SumState -> 0,TextState -> NULL
query I
SELECT dfn_agg_set_overload(x) FROM (SELECT 1 AS x WHERE false) t(x);
----
0

query T
SELECT dfn_agg_set_overload(x) FROM (SELECT 'a' AS x WHERE false) t(x);
----
NULL

# GROUP BY:每个分组一个状态,两个分支都正常分派
query II rowsort
SELECT k, dfn_agg_set_overload(x) FROM (VALUES (0, 1), (0, 2), (1, 4)) t(k, x) GROUP BY k;
----
0	3
1	4

# 两个重载分支的函数名本身没有被注册
statement error
SELECT dfn_agg_set_over_int(1);
----
Scalar Function with name dfn_agg_set_over_int does not exist

statement error
SELECT dfn_agg_set_over_varchar('x');
----
Scalar Function with name dfn_agg_set_over_varchar does not exist

# 重载匹配不到参数类型就报错
statement error
SELECT dfn_agg_set_overload(1.5);
----
No function matches

# ============================================================================
# 宏属性 overloads_name:宏直接管理函数集重载
#
# 属性写 overloads_name = "函数集名" 的函数不注册自己的名字,而是由宏提交
# duckfn::DuckAggregateOverloadItem,register_all_aggregate_overload
# 把同名(函数集名相同)的重载合并成一个函数集。无需手写 #[duck_custom_register]。
# ============================================================================

# INTEGER 分支:求和,返回 BIGINT
query I
SELECT dfn_agg_ovl_set(x) FROM (VALUES (1), (2), (3)) t(x);
----
6

query T
SELECT typeof(dfn_agg_ovl_set(x)) FROM (VALUES (1)) t(x);
----
BIGINT

# VARCHAR 分支:拼接,返回 VARCHAR(返回类型与 INTEGER 分支不同)
query T
SELECT dfn_agg_ovl_set(x) FROM (VALUES ('a'), ('b')) t(x);
----
str(a);str(b);

query T
SELECT typeof(dfn_agg_ovl_set(x)) FROM (VALUES ('a')) t(x);
----
VARCHAR

# 空输入:各分支自己 result() 的语义(SumState -> 0,TextState -> NULL)
query I
SELECT dfn_agg_ovl_set(x) FROM (SELECT 1 AS x WHERE false) t(x);
----
0

query T
SELECT dfn_agg_ovl_set(x) FROM (SELECT 'a' AS x WHERE false) t(x);
----
NULL

# 两个分支的函数名本身都没有被注册
statement error
SELECT dfn_agg_ovl_int(1);
----
Scalar Function with name dfn_agg_ovl_int does not exist

statement error
SELECT dfn_agg_ovl_varchar('x');
----
Scalar Function with name dfn_agg_ovl_varchar does not exist

# 参数类型匹配不到就报错
statement error
SELECT dfn_agg_ovl_set(1.5);
----
No function matches

# ============================================================================
# special_null_handling:NULL 行是否进入 update
#
# 适配层默认 DefaultNullHandling,属性写 special_null_handling = true 时覆盖成
# SpecialNullHandling,注册时调用 duckdb_aggregate_function_set_special_handling。
#
# 实测:本 DuckDB 版本下 C API 聚合的 update 默认就能收到 NULL 行,
# 所以两种设置的输出完全一致;该属性在这里是「显式声明」,不产生可观察差异
# (标量侧同一个属性有差异,见 scalar_function.test 的常量 NULL 一段)。
# 「能不能在函数体里看见 NULL」仍由参数是否 Option<T> 决定。
# ============================================================================

# 默认 + Option 入参:3 行都进 update,其中 1 行是 NULL
query T
SELECT dfn_agg_seen_default(x) FROM (VALUES (1), (NULL), (3)) t(x);
----
rows:3|nulls:1

# special_null_handling = true:输出与默认一致
query T
SELECT dfn_agg_seen_special(x) FROM (VALUES (1), (NULL), (3)) t(x);
----
rows:3|nulls:1

# 整列 NULL
query T
SELECT dfn_agg_seen_default(x) FROM (VALUES (NULL::BIGINT), (NULL::BIGINT)) t(x);
----
rows:2|nulls:2

query T
SELECT dfn_agg_seen_special(x) FROM (VALUES (NULL::BIGINT), (NULL::BIGINT)) t(x);
----
rows:2|nulls:2

# 常量 NULL:聚合不会像标量那样把常量 NULL 折叠掉
query T
SELECT dfn_agg_seen_default(NULL::BIGINT);
----
rows:1|nulls:1

query T
SELECT dfn_agg_seen_special(NULL::BIGINT);
----
rows:1|nulls:1

# 空输入:update 一次都不执行,result() 拿到初始状态
query T
SELECT dfn_agg_seen_default(x) FROM (SELECT 1 AS x WHERE false) t(x);
----
rows:0|nulls:0

query T
SELECT dfn_agg_seen_special(x) FROM (SELECT 1 AS x WHERE false) t(x);
----
rows:0|nulls:0

# special_null_handling 不改变读取层:入参写 T 时 NULL 行仍被整行跳过
query T
SELECT dfn_agg_seen_special_plain(x) FROM (VALUES (1), (NULL), (3)) t(x);
----
rows:2|nulls:0