Skip to main content

Documentation 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.

DuckValue is the central Rust type in better-duck-core that represents any value that can be stored in a DuckDB column. It appears as the return type of DuckRow::get, is used to supply parameters to Connection::execute_with for parameterized queries, and is passed directly to Appender::append via the AppendAble trait. Every column in every row you read from DuckDB becomes a DuckValue variant, and every value you bind into a prepared statement is expressed as one.

The DuckValue Enum

DuckValue is a #[non_exhaustive] enum defined in better_duck_core::types::value. Each variant carries the most natural Rust type for the corresponding DuckDB SQL type. The full set of variants, grouped by category, is shown below.
DuckValue is marked #[non_exhaustive]. New variants will be added as DuckDB gains new types. Always include a _ catch-all arm in every match so your code keeps compiling after upgrades.

Primitive numeric variants

VariantInner Rust typeDuckDB type
Boolean(bool)boolBOOLEAN
TinyInt(i8)i8TINYINT
UTinyInt(u8)u8UTINYINT
SmallInt(i16)i16SMALLINT
USmallInt(u16)u16USMALLINT
Int(i32)i32INTEGER
UInt(u32)u32UINTEGER
BigInt(i64)i64BIGINT
UBigInt(u64)u64UBIGINT
HugeInt(i128)i128HUGEINT
UHugeInt(u128)u128UHUGEINT
Float(f32)f32FLOAT
Double(f64)f64DOUBLE
Decimal(rust_decimal::Decimal)rust_decimal::DecimalDECIMAL (feature: decimal)

Text and blob variants

VariantInner Rust typeDuckDB type
Text(String)StringVARCHAR / TEXT
Blob(Blob)better_duck_core::types::blob::BlobBLOB

Date and time variants

The inner type for each temporal variant depends on whether the chrono feature is enabled. With chrono (the default), you get chrono types. Without it, you get the native types from better_duck_core::types::date_native.
Variantchrono inner typeno-chrono inner typeDuckDB type
Date(…)chrono::NaiveDateDuckDateDATE
Time(…)chrono::NaiveTimeDuckTimeTIME
Timestamp(…)chrono::NaiveDateTimestd::time::SystemTimeTIMESTAMP
TimestampTz(…)chrono::DateTime<Utc>std::time::SystemTimeTIMESTAMPTZ
TimeTz(…)date_chrono::TimeTzDuckTimeTzTIME WITH TIME ZONE
Interval(…)chrono::Durationstd::time::DurationINTERVAL
TimeNs(…)chrono::NaiveTimeDuckTimeNsTIME_NS
There are also sub-precision timestamp variants (TimestampS, TimestampMs, TimestampNs) that all carry chrono::NaiveDateTime (or std::time::SystemTime without chrono) for second-, millisecond-, and nanosecond-precision timestamp columns respectively.

Composite variants

VariantInner Rust typeDuckDB type
List(Vec<DuckValue>)Vec<DuckValue>LIST
Array(Box<[DuckValue]>)Box<[DuckValue]>ARRAY (fixed-length)
Struct(HashMap<String, DuckValue>)HashMap<String, DuckValue>STRUCT
Map(HashMap<DuckValue, DuckValue>)HashMap<DuckValue, DuckValue>MAP
Union(Box<DuckValue>)Box<DuckValue> (active member)UNION

Special variants

VariantInner Rust typeDuckDB type
Enum(String)StringENUM
Uuid(DuckUuid)better_duck_core::types::uuid::DuckUuidUUID
Bit(DuckBit)better_duck_core::types::bit::DuckBitBIT
Bignum(DuckBignum)better_duck_core::types::bignum::DuckBignumBIGNUM
NullNULL

Pattern Matching

Because DuckValue is an enum, the idiomatic way to inspect a column value is with a match expression. The #[non_exhaustive] attribute means the compiler will require a wildcard arm — this is intentional so your code stays forward-compatible as new variants are added.
use better_duck_core::types::value::DuckValue;

match value {
    DuckValue::Int(n)    => println!("integer: {n}"),
    DuckValue::Text(s)   => println!("text: {s}"),
    DuckValue::Double(f) => println!("float: {f}"),
    DuckValue::Null      => println!("null"),
    _ => println!("other: {value:?}"),
}
The From<T> trait is implemented for all primitive Rust types, so you can convert native values into DuckValue directly and use them with execute_with:
use better_duck_core::types::value::DuckValue;

// Construct explicitly
let mut threshold = DuckValue::Double(8.0);

// Or via From<f64>
let mut threshold: DuckValue = 8.0f64.into();

let mut rows = conn.execute_with(
    "SELECT id, label FROM events WHERE score > $1",
    &mut [&mut threshold],
)?;
DuckValue also provides two convenience constructors and map-access helpers:
  • DuckValue::text(s) — creates a Text variant from any Into<String>
  • value.get(key) — looks up a key in a Map variant, returning Option<&DuckValue>
  • value.get_mut(key) — mutable lookup into a Map variant
  • value.contains_key(key) — key-existence check for Map variants

DuckValue as AppendAble

DuckValue implements the AppendAble trait, which means it can be passed directly to both Connection::execute_with (as a prepared-statement bind) and Appender::append (as a bulk-insert row column). Under the hood, DuckValue’s AppendAble impl delegates to DuckValueRef to avoid duplicating per-variant logic.
use better_duck_core::{connection::Connection, types::value::DuckValue};

let mut conn = Connection::open_in_memory()?;
conn.execute_batch("CREATE TABLE logs (level TEXT, code INTEGER);")?;

let mut app = conn.appender("logs", "main")?;

// Append a row with two DuckValue columns
app.append(&mut DuckValue::text("warn"))?;
app.append(&mut DuckValue::Int(404))?;
app.save()?;
See the AppendAble trait documentation for the full contract and instructions on implementing it for custom types.

Composite Types

Constructing composite DuckValue variants requires assembling the inner collection first.
use better_duck_core::types::value::DuckValue;

let list = DuckValue::List(vec![
    DuckValue::Int(1),
    DuckValue::Int(2),
    DuckValue::Int(3),
]);
When you need a statically-typed list, array, or map and are okay with all elements sharing the same Rust type, prefer using Vec<T>, Box<[T]>, or HashMap<K, V> directly with AppendAble rather than wrapping items in DuckValue. Those generic impls call T::duck_logical_type() at compile time to determine the DuckDB column type, so they work correctly even with empty collections — unlike DuckValue::List / Array / Map, whose element type can only be inferred from the first item (and which fail on empty collections).

Equality and Hashing

DuckValue implements PartialEq, Eq, and Hash, which is what allows DuckValue to be used as a HashMap key (required for the Map variant). A few details worth knowing:
  • FloatsFloat and Double variants are compared and hashed via canonical forms where NaN == NaN and -0.0 == +0.0.
  • Maps and Structs — hashed order-independently, so iteration order of the inner HashMap does not affect the hash value.
  • SystemTime (no-chrono timestamps) — hashed via duration_since(UNIX_EPOCH) for cross-platform stability.

Build docs developers (and LLMs) love