Skip to main content
With feature = "udf", you can call your own Rust-authored DuckDB functions from the typed Diesel DSL — no raw SQL. The pattern: register a #[duckdb_*] function on the underlying core connection, declare a matching define_sql_function!, then call it as a typed function. Verified in the crate’s tests/udf_dsl.rs.

The workflow

1

Author and register the UDF

Write a #[duckdb_scalar] (or aggregate / table) function with better-duck-core’s UDF macros, then register it on the core connection reached via conn.inner_mut().
2

Declare it to Diesel

Mirror the SQL name and DuckDB argument/result types with Diesel’s define_sql_function!.
3

Call it typed

Use the generated function in .select / .filter like any built-in DSL function.
When the UDF uses name = "…", the generated registration type is named after the Rust function (add_one_impl here), so you call add_one_impl::register(conn.inner_mut()). The define_sql_function! name and signature must match the registered SQL name and DuckDB types.

Aggregate and table UDFs

The same pattern covers aggregate and table functions. Aggregates authored with #[duckdb_aggregate] are declared with define_sql_function! carrying the #[aggregate] attribute, then used in a grouped .select like the built-in aggregates. See the UDF macros page for authoring #[duckdb_scalar] / #[duckdb_aggregate] / #[duckdb_table_function].

Per-connection registration

Registration is per-connection — a UDF registered on one DuckDbConnection is not visible on another. With a pool, register on every connection via the on_connect hook so each pooled connection can call your functions. See Pooling → on_connect.

Binding custom-typed arguments

If a UDF takes a DuckDB-specific type (UBIGINT, HUGEINT, UUID, …), wrap literal arguments in the matching values::* newtype and .into_sql::<SqlType>() — bare foreign values can’t be used as expressions at a Duck* SQL type.

See also

UDF macros

Authoring #[duckdb_scalar] and friends.

Pooling

Register per connection with on_connect.

Aggregates

Built-in aggregates and custom ones.

Installation & setup

inner_mut() and the core connection.