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
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
//! 标量函数适配层:把 quack-rs 的向量级 C 回调拆成「按批读、逐行算、按批写」的 Rust 代码。
//!
//! Scalar-function adapter: splits quack-rs' vector-level C callback into batch-read, row-wise
//! evaluation and batch-write Rust code.
//!
//! panic 兜底仅在原生成立:本文件提到的 `catch_unwind` 把 panic 转成查询错误,在 wasm / 浏览器
//! (`wasm32-unknown-emscripten`) 上兜不住 —— panic 无法跨 JS 边界展开,会变成 `Maximum call stack
//! size exceeded` 栈溢出。报错请用 `Err(duck_error(..))`,不要用 `panic!`。详见 `crate::utils::helpers`
//! 模块文档与文档站 Troubleshooting。
//!
//! The `catch_unwind`-catches-panics guarantee below is native-only: on wasm the panic cannot
//! unwind across the JS boundary and surfaces as a stack overflow. Report errors via
//! `Err(duck_error(..))`, not `panic!`.

use crate::duck_columns::DuckColumns;
use crate::utils::builder_with_params::BuilderWithParams;
use crate::value_types::duck_value_type::{DuckValueReader, DuckValueType};
use crate::{
    DuckExtraInfo, DuckOptionResult, DuckResult, duck_error, duck_scalar_unwind, erased_extra_info,
    raw_extra_info, vec_option_to_ref,
};
use libduckdb_sys::{duckdb_connection, duckdb_data_chunk, duckdb_function_info, duckdb_vector};
use quack_rs::data_chunk::DataChunk;
use quack_rs::prelude::{
    LogicalType, NullHandling, ScalarFunctionBuilder, ScalarFunctionInfo, ScalarOverloadBuilder,
};

/// 把「参数结构体 -> 输出值」的纯 Rust 函数注册成 DuckDB 标量函数。
///
/// 适配层读写两侧都按「批」处理,只有用户代码默认是逐行的:
///
/// - 用 [`Self::Args`](实现 [`DuckColumns`])把输入 `DataChunk` 整体读成
///   `Vec<Option<Self::Args>>`(`None` 表示该行整体为 NULL);
/// - 默认逐行调用 [`Self::apply`](或 NULL 时代替的 [`Self::apply_with_null`],以及带附加数据的
///   [`Self::apply_with_extra`]);想一次处理整批的实现改覆盖 [`Self::apply_batch`];
/// - 启用可变参数([`Self::varargs_element_type`] 返回 `Some`)时改走
///   [`Self::apply_varargs`],固定参数之后的列全部按该类型读成一个集合(流式,不物化整批);
/// - 收集成 `Vec<Option<Self::Output>>` 后一次性写入输出向量。
///
/// 一般不用手写这个 impl,直接用 `#[duck_scalar_function]` 作用在普通函数上即可;需要批量处理时
/// 加 `batch = true`。
///
/// Registers a plain Rust function `Args -> Output` as a DuckDB scalar function. Both reading and
/// writing are batched; only the user code is per-row by default: the adapter materialises the
/// input `DataChunk` into `Vec<Option<Self::Args>>` through [`Self::Args`] (a [`DuckColumns`]
/// implementation), with `None` meaning the row is NULL as a whole. It then calls [`Self::apply`]
/// (or [`Self::apply_with_null`] for NULL rows, or [`Self::apply_with_extra`] when extra data is
/// attached) once per row — an implementation that wants the whole batch at once overrides
/// [`Self::apply_batch`] instead. Variadic functions ([`Self::varargs_element_type`] returns
/// `Some`) go through [`Self::apply_varargs`], still streaming; every column past the fixed ones is
/// read as one element of the variadic element type. Both paths finally write the collected results
/// into the output vector in one batch. Usually you do not implement this manually: annotate a plain
/// function with `#[duck_scalar_function]`, adding `batch = true` for batch processing.
pub trait ScalarFunctionAdapter: Sized + 'static {
    /// # Safety
    ///
    /// 由 DuckDB 回调,`info`/`input`/`output` 均由 DuckDB 保证有效。
    ///
    /// Called by DuckDB; `info`, `input` and `output` are guaranteed valid by DuckDB.
    unsafe extern "C" fn scalar_function_wrapper(
        info: duckdb_function_info,
        input: duckdb_data_chunk,
        output: duckdb_vector,
    ) {
        let info: ScalarFunctionInfo = unsafe { ScalarFunctionInfo::new(info) };
        // SAFETY: 回调期间 info 有效;extra_info 由 builder 在注册时挂上(没挂时为 None)。
        //
        // SAFETY: `info` is valid during the callback; the extra info was attached by the builder at
        // registration time (None when nothing was attached).
        let extra = unsafe { erased_extra_info(&info) };
        duck_scalar_unwind(&info,|| {
            let chunk: DataChunk = unsafe { DataChunk::from_raw(input) };
            let mut readers = Self::Args::create_column_readers(&chunk);
            // 固定参数列数:可变参数(`varargs_element_type()` 为 `Some`)从这一列之后开始。
            //
            // Number of fixed-argument columns; variadic arguments (when `varargs_element_type()`
            // is `Some`) start right after them.
            let fixed_count = readers.len();
            let has_varargs = Self::varargs_element_type().is_some();
            if has_varargs {
                for column_index in fixed_count..chunk.column_count() {
                    readers.push(Self::varargs_create_reader(&chunk, column_index));
                }
            }
            let row_count = chunk.size();

            // 读值与写值一样按批处理:先把整个 chunk 读成「一批行」,再交给用户代码。
            //
            // - 可变参数没有稳定的行结构(每个参数列的个数由调用处决定),仍按 `(readers, row)`
            //   流式读取;
            // - 其余情况统一物化成 `Vec<Option<Self::Args>>`,默认由 [`Self::apply_batch`] 逐行遍历
            //   (语义与改造前逐行读取完全一致),批量实现只需覆盖它,就得到「整批进、整批出」。
            //
            // Reading mirrors writing: the whole chunk is turned into a batch of rows first and only
            // then handed to the user code. Variadic functions keep streaming (`their arguments have
            // no stable row structure`), everything else materialises into `Vec<Option<Self::Args>>`
            // and goes through [`Self::apply_batch`], whose default walks the rows one by one —
            // exactly what the previous per-row loop did. A batch implementation only overrides that
            // method to get "whole batch in, whole batch out".
            let result = if has_varargs {
                let mut output_vec: Vec<Option<Self::Output>> = Vec::with_capacity(row_count);
                for row in 0..row_count {
                    match Self::apply_varargs(&readers, row, fixed_count) {
                        Ok(r) => output_vec.push(r),
                        Err(e) => {
                            info.set_error(e.as_str());
                            return;
                        }
                    }
                }
                Ok(Some(output_vec))
            } else {
                let rows: Vec<Option<Self::Args>> = (0..row_count)
                    .map(|row| Self::Args::read_columns(&readers, row))
                    .collect();
                Self::apply_batch(rows, extra)
            };
            match result {
                Ok(Some(results)) => {
                    // 兜底校验:批量实现必须逐行返回,长度对不上就是实现有 bug(宏生成的实现
                    // 已经先查过一遍并给出更具体的错误)。
                    //
                    // A safety net: a batch implementation must return one result per row; a length
                    // mismatch is a bug in the implementation (the macro-generated one already
                    // checks it with a more specific message).
                    if results.len() != row_count {
                        info.set_error(
                            duck_error(format!(
                                "{}: batch implementation returned {} rows for {} input rows",
                                Self::NAME,
                                results.len(),
                                row_count
                            ))
                            .as_str(),
                        );
                        return;
                    }
                    Self::Output::write_batch(output, &vec_option_to_ref(&results));
                }
                // `Ok(None)` 在批量语义下表示「整批输出 NULL」(逐行实现不会走到这里)。
                //
                // Under the batch semantics `Ok(None)` means "the whole batch is NULL" (a per-row
                // implementation never returns it).
                Ok(None) => {
                    let nulls: Vec<Option<&Self::Output>> = vec![None; row_count];
                    Self::Output::write_batch(output, &nulls);
                }
                Err(e) => info.set_error(e.as_str()),
            }
        });
    }

    /// NULL 处理策略,默认 [`NullHandling::DefaultNullHandling`](NULL 行不进入回调)。
    ///
    /// Null-handling strategy; defaults to [`NullHandling::DefaultNullHandling`] (NULL rows
    /// never reach the callback).
    fn null_handling() -> NullHandling {
        NullHandling::DefaultNullHandling
    }

    /// 是否把函数标记为 volatile,默认 `false`。
    ///
    /// 返回 `true` 时注册期会调用 DuckDB 的 `duckdb_scalar_function_set_volatile`:DuckDB
    /// 不会缓存或复用相同参数的调用结果,每一行都会重新求值(`random()` 这类函数需要它)。
    /// 不开启时 DuckDB 可能把常量参数的调用折叠成只执行一次。
    ///
    /// 该开关走 quack-rs 的 `ScalarFunctionBuilder::volatile`,只有在 duckfn 打开
    /// `duckdb-1-5` feature(DuckDB 1.5.0+ 的 C API)时才真正生效;未开启时本方法被忽略。
    /// 函数集重载([`Self::scalar_overload_builder`])不支持该开关。
    ///
    /// Whether to mark the function volatile; defaults to `false`. Returning `true` makes the
    /// registration call DuckDB's `duckdb_scalar_function_set_volatile`, so DuckDB neither caches
    /// nor reuses the result of a call with the same arguments — every row is re-evaluated, which
    /// is what functions like `random()` need. Without it DuckDB may fold constant-argument calls
    /// into a single execution. The switch goes through quack-rs' `ScalarFunctionBuilder::volatile`
    /// and only takes effect when duckfn's `duckdb-1-5` feature (the DuckDB 1.5.0+ C API) is
    /// enabled; otherwise it is ignored. Function-set overloads ([`Self::scalar_overload_builder`])
    /// do not support it.
    fn volatile() -> bool {
        false
    }

    /// 可变参数的元素逻辑类型;默认 `None`(函数没有可变参数)。
    ///
    /// 返回 `Some(lt)` 表示函数带可变参数:注册期会调用 DuckDB 的
    /// `duckdb_scalar_function_set_varargs`(也就是 quack-rs 的
    /// `ScalarFunctionBuilder::varargs_logical`),调用期除去固定参数之外的每一列都按 `lt`
    /// 读成一个元素,交给 [`Self::apply_varargs`]。
    ///
    /// 元素可以是任意 [`DuckValueType`],包括 `Option<T>`(元素可为 NULL)与 `Vec<T>`
    /// (即「可变参数本身是 LIST」,对应 `varargs_logical(LogicalType::list(...))`)。
    ///
    /// 该能力只在 duckfn 打开 `duckdb-1-5` feature(DuckDB 1.5.0+ 的 C API)时真正生效;
    /// 函数集重载([`Self::scalar_overload_builder`])不支持它。宏 `#[duck_scalar_function(varargs = true)]`
    /// 会从函数签名最后一个参数 `Vec<T>` 推断出 `T`。
    ///
    /// Variadic-argument element logical type; `None` (the default) means the function has no
    /// variadic arguments. `Some(lt)` makes registration call DuckDB's
    /// `duckdb_scalar_function_set_varargs` (quack-rs' `ScalarFunctionBuilder::varargs_logical`)
    /// and, at call time, reads every column after the fixed ones as one element of type `lt`
    /// handed to [`Self::apply_varargs`]. The element may be any [`DuckValueType`], `Option<T>`
    /// (nullable element) and `Vec<T>` (i.e. the variadic argument is itself a LIST, matching
    /// `varargs_logical(LogicalType::list(...))`) included. The capability only takes effect with
    /// duckfn's `duckdb-1-5` feature (the DuckDB 1.5.0+ C API) and is not supported for function-set
    /// overloads ([`Self::scalar_overload_builder`]). The macro
    /// `#[duck_scalar_function(varargs = true)]` infers the element type `T` from the last
    /// parameter `Vec<T>` of the signature.
    fn varargs_element_type() -> Option<LogicalType> {
        None
    }

    /// 为可变参数的第 `column_index` 列创建读取器。
    ///
    /// 只在 [`Self::varargs_element_type`] 返回 `Some` 时调用;默认实现直接 panic,因此手写
    /// impl 不需要实现它。
    ///
    /// Creates the reader for the `column_index`-th variadic column. Only called when
    /// [`Self::varargs_element_type`] returns `Some`; the default panics, so hand-written
    /// implementations do not need it.
    fn varargs_create_reader(_chunk: &DataChunk, _column_index: usize) -> DuckValueReader {
        unreachable!("varargs_create_reader is only used when varargs_element_type() returns Some")
    }

    /// 带可变参数时的一行求值。
    ///
    /// `readers` 覆盖固定参数与可变参数的全部列,`fixed_count` 是固定参数列数;实现方用
    /// `readers[..fixed_count]` 读固定参数、其余读成一个可变参数集合。`Ok(None)` 表示该行整体
    /// 输出 SQL NULL(任一非可空参数或元素为 NULL 时就应该这样短路)。
    ///
    /// 只在 [`Self::varargs_element_type`] 返回 `Some` 时调用;默认实现直接 panic,因此手写
    /// impl 不需要实现它。
    ///
    /// Evaluates one row when variadic arguments are enabled. `readers` covers both the fixed and
    /// the variadic columns and `fixed_count` is the number of fixed ones; the implementation reads
    /// the fixed arguments from `readers[..fixed_count]` and the rest as one variadic collection.
    /// `Ok(None)` makes the whole row SQL NULL — the right short-circuit when any non-nullable
    /// argument or element is NULL. Only called when [`Self::varargs_element_type`] returns `Some`;
    /// the default panics, so hand-written implementations do not need it.
    fn apply_varargs(
        _readers: &[DuckValueReader],
        _row: usize,
        _fixed_count: usize,
    ) -> DuckOptionResult<Self::Output> {
        unreachable!("apply_varargs is only used when varargs_element_type() returns Some")
    }

    /// 对「一整批行」求值;默认逐行转调 [`Self::apply_with_extra`]。
    ///
    /// `rows` 是本 chunk 读出来的一批参数行,长度就是本次要算的行数;`None` 表示该行整体为
    /// NULL(任一非可空参数为 NULL,与逐行路径的判据完全一致 —— 批量并不会另搞一套判空逻辑)。
    ///
    /// `extra` 是注册期通过 [`Self::extra_info`] 挂上的函数级数据,原样转给
    /// [`Self::apply_with_extra`];宏以 `batch = true` 生成的批量实现目前不把 `extra` 交给用户函数
    /// (用户函数签名里没有它的位置),手写 impl 可以直接使用。
    ///
    /// 返回约定(元素是 `Option<Self::Output>`,因此「某一行是 NULL」与「值本身可空」都能表达):
    ///
    /// - `Ok(Some(results))`:`results.len()` **必须**等于 `rows.len()`,且顺序一一对应。长度对不上
    ///   会报错(宏生成的实现会先给出一条带函数名的具体错误,适配层另有一次兜底校验);
    /// - `Ok(None)`:**整批**输出 NULL —— 注意这与逐行的「本行 NULL」不同;
    /// - `Err(e)`:整条查询失败。
    ///
    /// 逐行实现完全不需要理会这个方法:默认实现就是「遍历 `rows`,逐个调用
    /// [`Self::apply_with_extra`]」,行为与改造前一致。想要「整批进、整批出」(例如把整批行合并成
    /// 一次 HTTP 请求 / 一次数据库往返)的实现才覆盖它,此时 `apply` 不会被调用。
    ///
    /// 可变参数([`Self::varargs_element_type`] 返回 `Some`)的函数不走这里,适配层仍按
    /// `(readers, row)` 流式读取。
    ///
    /// Evaluates one whole batch of rows; by default it just walks `rows` and delegates to
    /// [`Self::apply_with_extra`] for every one of them. `rows` is the chunk materialised as
    /// argument rows and `None` means that row is NULL as a whole (a NULL in any non-nullable
    /// argument — the very same criterion as the per-row path, since batching does not introduce a
    /// second notion of nullability).
    ///
    /// `extra` is the function-level data attached at registration time through
    /// [`Self::extra_info`] and is forwarded to [`Self::apply_with_extra`]; the batch implementation
    /// the macro generates with `batch = true` does not hand it to the user function (whose
    /// signature has no place for it), while a hand-written impl can use it directly.
    ///
    /// Contract (the element is `Option<Self::Output>`, so "this row is NULL" and "the value itself
    /// is nullable" are both expressible): `Ok(Some(results))` requires
    /// `results.len() == rows.len()`, in the same order (a mismatch is an error; the
    /// macro-generated implementation reports a function-specific message first and the adapter has
    /// a second safety net), `Ok(None)` means the **entire batch** is NULL — unlike a per-row
    /// `None`, which only nulls that row — and `Err(e)` fails the whole query.
    ///
    /// A per-row implementation never needs to touch this method: the default is exactly the loop
    /// that used to sit in the adapter. Only implementations that want "whole batch in, whole batch
    /// out" (for instance one HTTP request or one database round trip for the whole batch) override
    /// it, in which case `apply` is never called. Variadic functions
    /// ([`Self::varargs_element_type`] returns `Some`) do not go through here: the adapter keeps
    /// streaming them row by row.
    fn apply_batch(
        rows: Vec<Option<Self::Args>>,
        extra: Option<&DuckExtraInfo>,
    ) -> DuckOptionResult<Vec<Option<Self::Output>>> {
        let mut output_vec: Vec<Option<Self::Output>> = Vec::with_capacity(rows.len());
        for args in rows {
            output_vec.push(Self::apply_with_extra(args, extra)?);
        }
        Ok(Some(output_vec))
    }

    /// 构造「独立函数」用的 builder(自带函数名)。
    ///
    /// Builds the builder for a standalone function (carrying its own name).
    fn scalar_function_builder() -> ScalarFunctionBuilder {
        let mut builder = ScalarFunctionBuilder::new(Self::NAME)
            .function(Self::scalar_function_wrapper)
            .null_handling(Self::null_handling())
            .returns_logical(Self::Output::logical_type())
            .with_params(Self::Args::column_types());
        if Self::volatile() {
            builder = set_volatile(builder);
        }
        if let Some(varargs_type) = Self::varargs_element_type() {
            builder = set_varargs(builder, varargs_type);
        }
        if let Some((ptr, destroy)) = raw_extra_info(Self::extra_info()) {
            // SAFETY: ptr 由 `DuckExtraInfo::into_raw` 产生,destroy 与它配对;函数对象交给 DuckDB 后
            // 由 DuckDB 在销毁时调用 destroy。
            //
            // SAFETY: `ptr` comes from `DuckExtraInfo::into_raw` and `destroy` matches it; once the
            // function object is handed to DuckDB, DuckDB calls `destroy` on destruction.
            builder = unsafe { builder.extra_info(ptr, destroy) };
        }
        builder
    }

    /// 构造「函数集重载」用的 builder(不带函数名,由函数集决定)。
    ///
    /// 注意:quack-rs 的 [`ScalarOverloadBuilder`] 没有暴露 volatile / varargs 开关,因此
    /// [`Self::volatile`] 与 [`Self::varargs_element_type`] 对重载无效;需要它们时请注册成独立函数。
    ///
    /// Builds the builder for a function-set overload (no name; the set provides it). Note that
    /// quack-rs' [`ScalarOverloadBuilder`] exposes neither a volatile nor a varargs switch, so
    /// [`Self::volatile`] and [`Self::varargs_element_type`] have no effect on overloads; register
    /// the function standalone when they are required.
    fn scalar_overload_builder() -> ScalarOverloadBuilder {
        let mut builder = ScalarOverloadBuilder::new()
            .function(Self::scalar_function_wrapper)
            .null_handling(Self::null_handling())
            .returns_logical(Self::Output::logical_type())
            .with_params(Self::Args::column_types());
        if let Some((ptr, destroy)) = raw_extra_info(Self::extra_info()) {
            // SAFETY: 同上;重载句柄最终由函数集持有,析构时机由 DuckDB 决定。
            //
            // SAFETY: as above; the overload handle ends up owned by the function set and DuckDB
            // decides when it is destroyed.
            builder = unsafe { builder.extra_info(ptr, destroy) };
        }
        builder
    }

    /// # Safety
    ///
    /// `con` 必须是有效的 DuckDB 连接句柄。
    ///
    /// `con` must be a valid DuckDB connection handle.
    unsafe fn register(con: duckdb_connection) -> DuckResult<()> {
        unsafe { Self::scalar_function_builder().register(con) }
    }

    /// 回调名称,仅用于标识(SQL 里的函数名由 builder 决定)。
    ///
    /// Callback name, used for identification only (the SQL name comes from the builder).
    const NAME: &'static str;
    /// 参数结构体类型:实现 [`DuckColumns`],负责按行读取各参数列。
    ///
    /// The argument struct type: implements [`DuckColumns`] and reads each argument column
    /// row by row.
    type Args: DuckColumns;
    /// 输出值类型:决定返回的 DuckDB 逻辑类型与写向量方式。
    ///
    /// The output value type: determines the returned DuckDB logical type and how values are
    /// written into the vector.
    type Output: DuckValueType;

    /// 注册期附加的数据(DuckDB 的 `extra_info`);默认不附加。
    ///
    /// 数据在函数对象销毁时由 DuckDB 调用析构回调释放,因此类型必须是 `Send + Sync + 'static`
    /// (函数对象可能被多线程、多查询共享,且应视为只读)。需要「每次查询一份」的状态请改用表函数的
    /// `with_state` 或 quack-rs 的 bind data。
    ///
    /// Function-level data attached at registration time (DuckDB's `extra_info`); nothing is
    /// attached by default. DuckDB frees it through the destructor when the function object is
    /// dropped, so the type must be `Send + Sync + 'static` (the function object may be shared
    /// across threads and queries, and must be treated as read-only). For per-query state use a
    /// table function's `with_state` or quack-rs' bind data instead.
    fn extra_info() -> Option<DuckExtraInfo> {
        None
    }

    /// NULL 行的默认处理:入参为 `None`(本行有 NULL 且参数不可空)时直接输出 NULL。
    ///
    /// Default handling of NULL rows: when the arguments are `None` (a NULL in this row with
    /// non-nullable parameters), output NULL directly.
    fn apply_with_null(args_option: Option<Self::Args>) -> DuckOptionResult<Self::Output> {
        if let Some(args) = args_option {
            Self::apply(args)
        } else {
            Ok(None)
        }
    }

    /// 对一行参数求值,并带上函数级附加数据。
    ///
    /// 默认忽略 `extra` 并转调 [`Self::apply_with_null`];需要读 `extra_info` 时重写本方法:
    ///
    /// ```ignore
    /// fn apply_with_extra(
    ///     args: Option<Self::Args>,
    ///     extra: Option<&duckfn::DuckExtraInfo>,
    /// ) -> duckfn::DuckOptionResult<Self::Output> {
    ///     let config = extra.and_then(|extra| extra.downcast_ref::<MyConfig>());
    ///     // ...
    /// }
    /// ```
    ///
    /// Evaluates one row of arguments together with the function-level extra data. By default it
    /// ignores `extra` and delegates to [`Self::apply_with_null`]; override it to read the
    /// `extra_info` (see the snippet above).
    fn apply_with_extra(
        args: Option<Self::Args>,
        extra: Option<&DuckExtraInfo>,
    ) -> DuckOptionResult<Self::Output> {
        let _ = extra;
        Self::apply_with_null(args)
    }

    /// 对一行非 NULL 参数求值;返回 `Ok(None)` 表示该行输出 SQL NULL。
    ///
    /// 启用可变参数([`Self::varargs_element_type`] 返回 `Some`)时不会调用本方法,适配层改走
    /// [`Self::apply_varargs`];`#[duck_scalar_function(varargs = true)]` 生成的实现因此只放一个
    /// 占位方法体。
    ///
    /// Evaluates one row of non-NULL arguments; returning `Ok(None)` makes this row SQL NULL. It
    /// is not called once variadic arguments are enabled ([`Self::varargs_element_type`] returns
    /// `Some`), where the adapter goes through [`Self::apply_varargs`] instead; the implementation
    /// generated by `#[duck_scalar_function(varargs = true)]` therefore only carries a placeholder
    /// body.
    fn apply(args: Self::Args) -> DuckOptionResult<Self::Output>;
}

/// 把标量函数标记为 volatile([`ScalarFunctionAdapter::volatile`] 的实现细节)。
///
/// `ScalarFunctionBuilder::volatile` 只在 quack-rs 的 `duckdb-1-5` feature 下存在,因此没有该
/// feature 时这里原样返回 builder:开关被忽略,而不是让整个扩展编译失败。
///
/// Marks a scalar function volatile (the implementation detail behind
/// [`ScalarFunctionAdapter::volatile`]). `ScalarFunctionBuilder::volatile` only exists under
/// quack-rs' `duckdb-1-5` feature, so without it the builder is returned unchanged: the switch is
/// ignored rather than failing the whole extension build.
fn set_volatile(builder: ScalarFunctionBuilder) -> ScalarFunctionBuilder {
    #[cfg(feature = "duckdb-1-5")]
    {
        builder.volatile()
    }
    #[cfg(not(feature = "duckdb-1-5"))]
    {
        builder
    }
}

/// 给标量函数设置可变参数类型([`ScalarFunctionAdapter::varargs_element_type`] 的实现细节)。
///
/// `ScalarFunctionBuilder::varargs_logical` 只在 quack-rs 的 `duckdb-1-5` feature 下存在,因此
/// 没有该 feature 时这里原样返回 builder:可变参数被忽略,而不是让整个扩展编译失败。
///
/// Sets the variadic-argument type (the implementation detail behind
/// [`ScalarFunctionAdapter::varargs_element_type`]). `ScalarFunctionBuilder::varargs_logical`
/// only exists under quack-rs' `duckdb-1-5` feature, so without it the builder is returned
/// unchanged: varargs are ignored rather than failing the whole extension build.
fn set_varargs(builder: ScalarFunctionBuilder, varargs_type: LogicalType) -> ScalarFunctionBuilder {
    #[cfg(feature = "duckdb-1-5")]
    {
        builder.varargs_logical(varargs_type)
    }
    #[cfg(not(feature = "duckdb-1-5"))]
    {
        let _ = varargs_type;
        builder
    }
}