Expand description
User-defined DuckDB functions: scalar functions and table functions. User-defined DuckDB functions: scalar functions and table functions.
Requires the udf feature.
Two kinds of function can be registered on a Connection:
- Scalar functions compute one value per row, for use in a
SELECTlist orWHEREclause:SELECT my_func(x) FROM t. - Table functions produce rows and columns, for use in a
FROMclause:SELECT * FROM my_func(1, 2).
The duckdb_scalar and
duckdb_table_function attribute macros turn
an ordinary Rust function into either kind without requiring any unsafe code
or manual vector handling. Parameter and return types are inferred from the
Rust signature via types::DuckLogicalType —
any type that already round-trips through AppendAble
(all the integer widths, floats, String/&str, bool, …) works here too,
with no extra type table for the macros to maintain.
§Scalar functions
use better_duck_core::{connection::Connection, duckdb_scalar};
/// Repeats `s` `n` times.
#[duckdb_scalar]
fn repeat_str(s: &str, n: i32) -> String {
s.repeat(n.max(0) as usize)
}
/// `Option` parameters/returns propagate `NULL` explicitly.
#[duckdb_scalar]
fn double_or_null(x: Option<i32>) -> Option<i32> {
x.map(|v| v * 2)
}
/// A `Result<T, E>` return fails the query with `E`'s message on `Err`.
#[duckdb_scalar(name = "to_int")]
fn parse_int(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse()
}
let mut conn = Connection::open_in_memory()?;
repeat_str::register(&mut conn)?;
parse_int::register(&mut conn)?;
let mut result = conn.execute("SELECT repeat_str('ab', 3) AS r")?;
assert_eq!(result.next().unwrap()?.get("r"), Some(&better_duck_core::types::value::DuckValue::text("ababab")));§Table functions
The function returns impl Iterator<Item = T> + Send — T for a single
column, or a tuple (A, B, ...) for several. DuckDB pulls rows in chunks, so
the iterator is driven lazily and may be scanned across several calls.
use better_duck_core::{connection::Connection, duckdb_table_function};
/// The integers in `[start, stop)`.
#[duckdb_table_function(name = "series", columns("n"))]
fn series(start: i64, stop: i64) -> impl Iterator<Item = i64> + Send {
start..stop
}
let mut conn = Connection::open_in_memory()?;
series::register(&mut conn)?;
let mut result = conn.execute("SELECT sum(n) AS total FROM series(1, 101)")?;
assert_eq!(result.next().unwrap()?.get("total"), Some(&better_duck_core::types::value::DuckValue::HugeInt(5050)));§Attribute options
| option | scalar | table | meaning |
|---|---|---|---|
name = "sql_name" | yes | yes | the SQL function name (defaults to the Rust fn’s name) |
crate = ::path | yes | yes | re-export escape hatch for generated code’s ::better_duck_core path |
volatile | yes | — | disables DuckDB’s zero-argument constant-folding |
state(Type, init_expr) | yes | — | shared VScalar::State, readable via duck_state! |
columns("a", "b") | — | yes | result column names (defaults: the fn name for one column, column_0, column_1, … for several) |
named_params("a", "b") | — | yes | binds the named fn parameters as SQL named (keyword) parameters instead of positional; a non-Option field becomes a required named parameter, an Option<T> field an optional one |
projection_pushdown | — | yes | declares that this function honors duck_projection! to skip work for columns the query doesn’t need |
extra_info(Type, init_expr) | — | yes | shared, registration-time context, readable via duck_extra_info! |
special_handling (whether NULL inputs still invoke the function) is
inferred automatically: present whenever any parameter is Option<T>.
§Panics and panic = "abort"
Callback panics are caught and reported to DuckDB as a query error whenever
the crate is built with panic = "unwind" (the default for dev and test
profiles). Under a panic = "abort" build — this workspace’s own release
profile, for instance — a panicking user-defined function aborts the process
instead: catch_unwind cannot catch anything once unwinding itself has been
compiled out. Prefer returning Err from a fallible user-defined function
over panicking; the ordinary error path never depends on unwinding.
Re-exports§
pub use crate::types::LogicalType;pub use aggregate::AggregateSetBuilder;pub use aggregate::VAggregate;pub use cast::VCast;pub use replacement::ReplacementScan;pub use replacement::ReplacementScanInfo;pub use scalar::ScalarBindInfo;pub use scalar::ScalarSignature;pub use scalar::VScalar;pub use table::VTabLocalInit;pub use table::run_table_func;pub use table::TableInitData;pub use table::TableRow;pub use table::BindInfo;pub use table::InitInfo;pub use table::TableFunctionInfo;pub use table::VTab;
Modules§
- aggregate
- DuckDB scalar functions: row-wise functions used in a
SELECTlist orWHEREclause. DuckDB aggregate functions: functions that fold many rows into one value per group, e.g.SELECT my_sum(x) FROM t GROUP BY k. - cast
- DuckDB custom cast functions: register a conversion from one logical type to
another, used implicitly by the binder or explicitly via
CAST. - replacement
- DuckDB replacement scans: rewrite an unresolved table reference into a
table function call. Experimental — see the module docs.
DuckDB replacement scans: automatically rewrite an unresolved table
reference into a table function call — e.g. routing
SELECT * FROM 'data.parquet'toread_parquet('data.parquet')by file extension, without the caller writing the function call explicitly. - scalar
- DuckDB scalar functions: row-wise functions used in a
SELECTlist orWHEREclause, e.g.SELECT my_func(x) FROM t. - table
- DuckDB table functions: functions used in a
FROMclause that produce rows and columns. DuckDB table functions: functions used in aFROMclause that produce rows and columns, e.g.SELECT * FROM my_func(1, 2).
Structs§
- Data
Chunk Handle - An owned-or-borrowed DuckDB data chunk.
A DuckDB data chunk: a batch of rows laid out column-wise, one
VectorRef/VectorMutper column. - Vector
Mut - Read and write views over a single column of a
DataChunkHandle, plus theScalarArg/ScalarRetmarshalling traits. An exclusive, writable view of one column of a data chunk. - Vector
Ref - Read and write views over a single column of a
DataChunkHandle, plus theScalarArg/ScalarRetmarshalling traits. A read-only view of one column of a data chunk.
Traits§
- Scalar
Arg - Read and write views over a single column of a
DataChunkHandle, plus theScalarArg/ScalarRetmarshalling traits. A Rust type readable from one row of aVectorRef. - Scalar
Ret - Read and write views over a single column of a
DataChunkHandle, plus theScalarArg/ScalarRetmarshalling traits. A Rust type writable into one row of aVectorMut.
Type Aliases§
- UdfResult
- The error type returned by user-defined-function callbacks.