sql_types::Duck* markers and how to read/write them, and the values::* newtypes that
make foreign Rust types bindable. Verified against
crates/better-duck-diesel/src/types/duckdb_types.rs and src/values.rs.
Standard Diesel types
These work with no extra imports — Diesel’s own SQL types with DuckDB backend impls provided here:DuckDB-specific SQL types
use better_duck_diesel::sql_types::*; — use these inside table! and in .bind::<T, _> /
.into_sql::<T>(). Each is a zero-size type implementing Diesel’s SqlType + QueryId:
Date/time types map to
chrono with the chrono feature, or to
better_duck_core::types::date_native plain structs without it. Only one set compiles at a time — see
Feature flags.How Duck* columns move through Diesel
These marker types do not all behave the same through the query builder. There are two tiers:
Narrow unsigned widths — load typed, bind via raw SQL or values::*
Narrow unsigned widths — load typed, bind via raw SQL or values::*
DuckTinyInt, DuckUTinyInt, DuckUSmallInt, DuckUInt, DuckUBigInt (Rust i8/u8/u16/u32/u64)
can be loaded through the typed .select(...) DSL — their Rust types satisfy Diesel’s
Queryable blanket, so a .select((t::tiny, t::ubig)) into (i8, u64) works. They have no
AsExpression, however, so they cannot be written with .values(...) — insert them with
diesel::sql_query(...).bind::<DuckXxx, _>(v) or wrap them in the matching
values::* newtype (which adds both bind and load).HUGEINT/UHUGEINT and composite/semantic types — raw SQL only
HUGEINT/UHUGEINT and composite/semantic types — raw SQL only
DuckHugeInt/DuckUHugeInt (i128/u128) and DuckUuid, DuckEnum, DuckList, DuckArray,
DuckStruct, DuckMap, DuckUnion, DuckBit, DuckBignum, DuckTimestamptz, DuckInterval,
DuckTimeTz, DuckTimeNs do not flow through the typed builder at all. Round-trip them via raw
SQL: diesel::sql_query(...).bind::<DuckXxx, _>(value) to write, and a #[derive(QueryableByName)]
struct whose field carries #[diesel(sql_type = DuckXxx)] to read. Values are ordinary
better_duck_core types (DuckValue, DuckUuid, DuckBit, …). The scalar ones
(i128/u128/DuckUuid/DuckBit/DuckBignum) also become fully typed-DSL-usable through their
values::* newtype.DECIMAL (Numeric → rust_decimal::Decimal) is similar: Decimal has no AsExpression<Numeric>, so
it binds through diesel::sql_query(...).bind::<Numeric, _>(d) rather than .values(...), though its
FromSql/ToSql for the DuckDB backend still apply. INTERVAL can be bound either through DuckInterval
or Diesel’s own diesel::sql_types::Interval (both map to chrono::Duration).Reading & writing raw-SQL Duck* columns
ENUM/STRUCT columns you can bind/read your own Rust types instead of DuckValue — see
Data derives.
The concrete Rust types the composite columns read into (verified in tests / example 22-diesel-types):
DuckStruct → HashMap<String, DuckValue>, DuckMap → HashMap<DuckValue, DuckValue>, DuckUnion →
Box<DuckValue>, DuckEnum → String, DuckUuid/DuckBit/DuckBignum → the core
DuckUuid/DuckBit/DuckBignum, and DuckTimestamptz → chrono::DateTime<Utc>.
The values::* binding newtypes
Diesel’s blanket impl<T: Expression> AsExpression<T::SqlType> for T means a third-party crate can’t make
a foreign value type (i128, u64, DuckUuid, …) usable as an expression at a custom Duck* SQL type —
coherence rejects it. The thin local newtypes in better_duck_diesel::values::* fix this: wrap the value
and it becomes bindable (.eq(…), .values(…), function arg, .into_sql::<T>()) and loadable
(Queryable). The standard widths (i16/i32/i64/f32/f64) already work directly and have no
wrapper.
Each has
From<inner> / From<newtype> for inner both ways. Round-trip (verified in
tests/values_dsl.rs):
See also
Schema
Declaring columns with these types.
Data derives
Map your own types to ENUM / STRUCT.
Core values
The
DuckValue type the raw path uses.DSL overview
Binding
values::* in expressions.