better-duck-core. For the value types that come back, see
Type system; for how extensions reach a connection, see
Extensions.
The dialect — analytics extras
DuckDB’s SQL is close to PostgreSQL, so most of what you know transfers. The pieces that surprise people are the analytics-focused additions:- Column selection sugar —
SELECT * EXCLUDE (col),SELECT * REPLACE (expr AS col), andCOLUMNS(...)regex/lambda column selection. - Grouping shortcuts —
GROUP BY ALL,ORDER BY ALL, andQUALIFYto filter on window functions without a subquery. - Rich literals and nesting —
LIST,STRUCT, andMAPliterals,UNNEST, window functions, andPIVOT/UNPIVOT. - Friendly casting —
CAST(x AS t)or the shorthandx::t;TRY_CASTyieldsNULLinstead of erroring on a bad cast.
Batch vs single statements
The driver draws a sharp line between running many statements and preparing one.The split in
execute_batch is done by DuckDB’s own parser — it is NUL-safe and aware of
comments and dollar-quoted strings — not a Rust-side split(';'). A semicolon inside a string
literal does not split the batch. The same parser backs
extract_statements, which hands you a batch of individually
preparable statements.What a statement returns
execute works for every statement kind; what you get back depends on the kind:
SELECT/RETURNING→ rows. Iterate theDuckResult(it isIterator<Item = Result<DuckRow>>).INSERT/UPDATE/DELETE→ an affected-row count viaresult.changes().- DDL →
changes()is0, with no rows.
Parameters: positional and named
DuckDB parameters are positional ($1, $2, … or ?) or named ($name). Bind values as
&mut dyn AppendAble — every DuckValue and the primitive Rust types
(i32, f64, &str / String, bool, …) implement AppendAble.
- A named
$namethat is not present in the SQL yieldsError::InvalidParameterName. - A wrong number of positional binds yields
Error::InvalidParameterCount. - Bind an explicit SQL
NULLwithDuckValue::Null(or anOption::None).
Transactions
DuckDB has no dedicated transaction entry point in its C API, so the driver issuesBEGIN TRANSACTION / COMMIT / ROLLBACK as SQL. Three ergonomic styles exist on Connection:
Replacement scans
A bare identifier in aFROM clause can resolve to a registered source rather than a catalog
table — for example an Arrow or Polars view, or a Rust replacement scan. This is how
register_arrow / query_polars surface in-memory data to SQL without
copying it into a table first:
udf
feature (better_duck_core::udf::replacement), with no attribute macro.
Related
Type system
What the values coming back actually are.
Extensions
Bundled vs runtime JSON, Parquet, ICU, and spatial.
Queries, Results & Values
The full result-iteration and metadata API.
Parameters & Prepared
Reusable statements,
CachedStatement, and incremental execution.