Table user-defined functions produce rows and columns and are used in aDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/nimdeveloper/better-duck/llms.txt
Use this file to discover all available pages before exploring further.
FROM clause — SELECT * FROM my_func(arg1, arg2). Unlike scalar functions, which compute one value per row, a table function is an iterator: it is driven lazily by DuckDB in chunks and can return any number of rows. With better-duck’s udf feature, the #[duckdb_table_function] attribute macro turns any Rust function returning impl Iterator<Item = T> + Send into a fully registered DuckDB table function. No unsafe code, no manual BindInfo/InitInfo plumbing, and no manual vector handling is required.
Enabling UDF Support
Table functions, like scalar functions, require theudf feature:
Basic Table Function
Annotate a function that returnsimpl Iterator<Item = T> + Send with #[duckdb_table_function]. Call <fn_name>::register(&mut conn) before querying. Use the columns(...) option to name the output column(s); for a single-column function it defaults to the SQL function name.
init callback boxes the iterator once and the func callback advances it on each pull.
Like
#[duckdb_scalar], the original Rust function is preserved and remains directly callable.
The generated <fn_name> module exposing register is placed alongside it in the same scope.Named (Keyword) Parameters
Declare one or more function parameters as SQL named (keyword) parameters with thenamed_params(...) option. Named parameters are bound with := syntax in SQL, not by position. A non-Option named parameter is required; an Option<T> named parameter is optional (returns None when omitted).
named_params(...) must exactly match the Rust parameter identifiers — the macro validates this at compile time and emits a descriptive error if they don’t match.
Projection Pushdown
Add theprojection_pushdown flag to tell DuckDB that this function honors the optimizer’s request to skip computing columns the query doesn’t need. Inside the function body, call duck_projection!() to retrieve a Vec<usize> of the 0-based column indices the current query actually wants.
duck_projection!() returns an empty Vec when DuckDB’s optimizer does not trigger pushdown for a given query shape. Always treat an empty list as “all columns are needed”. The projection_pushdown flag must be set for the returned list to ever be non-empty; without it, duck_projection!() will always return an empty Vec.
Extra Info with duck_extra_info!
Use extra_info(Type, init_expr) to attach shared, read-only context to a table function at registration time. The value is initialized once with init_expr and is accessible inside the function body via duck_extra_info!(Type). The type must be Clone.
duck_state! (which is for scalar functions), duck_extra_info! is for table functions.
Multi-Column Tables
Return tuples from the iterator to produce multiple columns. Name them withcolumns("col1", "col2", ...). The number of names must exactly match the tuple arity — the macro validates this at compile time.
DuckLogicalType inference used for scalar parameters and return types applies here.
NULL and Error Handling
NULL items
Yield
Option<T> items (or Option<(A, B)> for multi-column rows) to produce NULL values.
A None item sets the entire row’s columns to NULL.varargs (variable-arity table functions) and LIST/STRUCT column types are not yet supported
via the #[duckdb_table_function] macro. For those cases, implement the VTab trait directly.Complete Attribute Reference
| Option | Description |
|---|---|
name = "sql_name" | Override the SQL function name (defaults to the Rust function name) |
crate = ::path | Re-export escape hatch for the generated code’s ::better_duck_core path |
columns("a", "b", ...) | Output column names (defaults: the function name for one column, column_0, column_1, … for several) |
named_params("a", "b") | Bind the named Rust parameters as SQL keyword parameters instead of positional ones |
projection_pushdown | Declare that this function honors duck_projection!() for optimizer-driven column skipping |
extra_info(Type, init_expr) | Attach read-only registration-time context, readable inside the body via duck_extra_info!(Type) |