The UDF macros are enabled by
better-duck-core’s feature = "udf" (which also turns on
columnar). You never depend on better-duck-macros directly — import everything from
better_duck_core::…. Registration is always on a core
Connection; through better-duck-diesel’s udf feature, register
on the underlying connection via conn.inner_mut().DuckLogicalType — any type that round-trips through
AppendAble (every integer width, floats,
String / &str, bool, …) works. No unsafe, no manual vector handling.
Scalar functions
#[duckdb_scalar] — one value per row, for a SELECT list or WHERE.Table functions
#[duckdb_table_function] — rows and columns for a FROM clause.Aggregate functions
#[duckdb_aggregate] — fold many rows into one value.Cast functions
#[duckdb_cast] — a custom CAST / TRY_CAST.#[duckdb_entrypoint] for packaging functions into a loadable
extension.
The generated register
Each function macro generates a register(&mut Connection) associated function in a module
named after your function, so you register with my_fn::register(&mut conn)?. The annotated
function also remains an ordinary, directly callable Rust fn.
Shared semantics
An
Option<T> parameter or return maps to DuckDB NULL. Any Option<T> parameter also
auto-enables special_handling, so the function is still invoked for NULL inputs (a non-Option
parameter instead short-circuits NULL rows to a NULL result without calling your body).A
Result<T, E> return fails the query with E’s message on Err
(E: Into<Box<dyn Error + Send + Sync>>). Prefer this over panicking.duck_bail!("…") macro is return Err(format!(…).into()) for a fallible body — see
helper macros.
The panic caveat
Low-level traits
Every macro compiles down to a trait implementation you can also write by hand and register directly:
Each function page documents its trait and the by-hand path. The macros are the recommended surface;
the traits exist for cases the macros don’t cover (multiple overloads, bind-time constant folding,
per-worker local state).
Replacement scans (
udf::ReplacementScan, which rewrite a bare FROM identifier into a
table-function call) are an experimental part of the same udf feature but have no attribute
macro. Register one per-database with database.register_replacement_scan::<T>().See also
- Columnar overview — the
DataChunkHandle/ vector API callbacks use. - Values overview — the type inference that drives parameter/return mapping.
- Helper macros —
duck_bail!,duck_state!,duck_projection!,duck_extra_info!.