Type System
quack-rs describes DuckDB column types with two types: TypeId, a plain enum of DuckDB's
type ids, and LogicalType, an owned handle for full type descriptions such as
DECIMAL(18, 3), LIST(VARCHAR) or a STRUCT. This page also maps each DuckDB type to its
Rust type and vector read/write methods.
TypeId
TypeId is an enum covering DuckDB's column types (the GEOMETRY and
VARIANT types added in DuckDB 1.5.x are exposed behind the duckdb-1-5-3
feature — see Known Limitations). The list below
names the variants; it is a listing, not compilable code:
use quack_rs::types::TypeId;
TypeId::Boolean
TypeId::TinyInt // i8
TypeId::SmallInt // i16
TypeId::Integer // i32
TypeId::BigInt // i64
TypeId::UTinyInt // u8
TypeId::USmallInt // u16
TypeId::UInteger // u32
TypeId::UBigInt // u64
TypeId::HugeInt // i128
TypeId::UHugeInt // u128
TypeId::Float // f32
TypeId::Double // f64
TypeId::Timestamp
TypeId::TimestampTz
TypeId::TimestampS
TypeId::TimestampMs
TypeId::TimestampNs
TypeId::Date
TypeId::Time
TypeId::TimeTz
TypeId::Interval
TypeId::Varchar
TypeId::Blob
TypeId::Decimal
TypeId::Enum
TypeId::List
TypeId::Struct
TypeId::Map
TypeId::Uuid
TypeId::Union
TypeId::Bit
TypeId::Array
TypeId::TimeNs
TypeId::Any
TypeId::Varint // SQL name BIGNUM (VARINT before DuckDB 1.4)
TypeId::SqlNull
TypeId::IntegerLiteral
TypeId::StringLiteral
TypeId::Geometry // duckdb-1-5-3
TypeId::Variant // duckdb-1-5-3
TypeId implements Copy, Clone, Debug, PartialEq, Eq, Hash and Display.
SQL name
#![allow(unused)] fn main() { use quack_rs::types::TypeId; assert_eq!(TypeId::BigInt.sql_name(), "BIGINT"); assert_eq!(TypeId::Varchar.sql_name(), "VARCHAR"); assert_eq!(format!("{}", TypeId::Timestamp), "TIMESTAMP"); }
DuckDB constant
TypeId::to_duckdb_type() returns the DUCKDB_TYPE_* integer constant from libduckdb-sys.
You rarely need this directly — it's called internally by LogicalType::new.
Reverse conversion
TypeId::from_duckdb_type(raw) converts a raw DUCKDB_TYPE constant back into a TypeId.
It panics if the value does not match any known constant; TypeId::try_from_duckdb_type(raw)
returns None instead.
#![allow(unused)] fn main() { use quack_rs::types::TypeId; let type_id = TypeId::from_duckdb_type(libduckdb_sys::DUCKDB_TYPE_DUCKDB_TYPE_BIGINT); assert_eq!(type_id, TypeId::BigInt); }
LogicalType
LogicalType is an RAII wrapper around DuckDB's duckdb_logical_type. Creating one calls
into DuckDB, so this block is compiled but not run:
#![allow(unused)] fn main() { use quack_rs::types::{LogicalType, TypeId}; let lt = LogicalType::new(TypeId::Varchar); // lt.as_raw() returns the duckdb_logical_type pointer // Drop calls duckdb_destroy_logical_type automatically }
Pitfall L7:
duckdb_create_logical_typeallocates memory that must be freed withduckdb_destroy_logical_type.LogicalType'sDropimplementation does this automatically, preventing the memory leak that occurs when calling the DuckDB C API directly. See Pitfall L7.
For a type a TypeId fully describes, pass the TypeId to a builder's param or returns
method; the builder creates and destroys the LogicalType internally. For a parameterized
type (DECIMAL, LIST, MAP, STRUCT, UNION, ENUM, ARRAY), build a LogicalType and
pass it to param_logical or returns_logical.
Constructors
| Constructor | Creates |
|---|---|
LogicalType::new(type_id) | Simple type from a TypeId |
LogicalType::from_raw(ptr) | Takes ownership of a raw duckdb_logical_type handle (unsafe) |
LogicalType::decimal(width, scale) | DECIMAL(width, scale) |
LogicalType::list(element_type) | LIST<element_type> from a TypeId |
LogicalType::list_from_logical(element) | LIST<element> from an existing LogicalType |
LogicalType::map(key, value) | MAP<key, value> from TypeIds |
LogicalType::map_from_logical(key, value) | MAP<key, value> from existing LogicalTypes |
LogicalType::struct_type(fields) | STRUCT from &[(&str, TypeId)] |
LogicalType::struct_type_from_logical(fields) | STRUCT from &[(&str, LogicalType)] |
LogicalType::union_type(members) | UNION from &[(&str, TypeId)] |
LogicalType::union_type_from_logical(members) | UNION from &[(&str, LogicalType)] |
LogicalType::enum_type(members) | ENUM from &[&str] |
LogicalType::array(element_type, size) | ARRAY<element_type>[size] from a TypeId |
LogicalType::array_from_logical(element, size) | ARRAY<element>[size] from an existing LogicalType |
Every constructor except from_raw panics on invalid input (for example a composite
TypeId passed to new, or a DECIMAL width outside 1–38) and has a try_* counterpart
(try_new, try_decimal, try_struct_type, …) that returns
Result<LogicalType, LogicalTypeError> instead. UNION types accept at most
MAX_UNION_MEMBERS (255) members.
Introspection methods
All introspection methods are unsafe: they call into DuckDB, so the handle must be valid
and the C API initialized.
| Method | Returns | Applicable to |
|---|---|---|
get_type_id() | TypeId | Any |
get_alias() | Option<String> | Any |
set_alias(alias) | () | Any |
decimal_width() | u8 | DECIMAL |
decimal_scale() | u8 | DECIMAL |
decimal_internal_type() | TypeId | DECIMAL |
enum_internal_type() | TypeId | ENUM |
enum_dictionary_size() | u32 | ENUM |
enum_dictionary_value(index) | String | ENUM |
list_child_type() | LogicalType | LIST |
map_key_type() | LogicalType | MAP |
map_value_type() | LogicalType | MAP |
struct_child_count() | u64 | STRUCT |
struct_child_name(index) | String | STRUCT |
struct_child_type(index) | LogicalType | STRUCT |
union_member_count() | u64 | UNION |
union_member_name(index) | String | UNION |
union_member_type(index) | LogicalType | UNION |
array_size() | u64 | ARRAY |
array_child_type() | LogicalType | ARRAY |
Rust type ↔ DuckDB type mapping
When reading from or writing to vectors, use the corresponding VectorReader/VectorWriter
method. The most common types:
| DuckDB type | TypeId | Reader method | Writer method |
|---|---|---|---|
BOOLEAN | Boolean | read_bool | write_bool |
TINYINT | TinyInt | read_i8 | write_i8 |
SMALLINT | SmallInt | read_i16 | write_i16 |
INTEGER | Integer | read_i32 | write_i32 |
BIGINT | BigInt | read_i64 | write_i64 |
UTINYINT | UTinyInt | read_u8 | write_u8 |
USMALLINT | USmallInt | read_u16 | write_u16 |
UINTEGER | UInteger | read_u32 | write_u32 |
UBIGINT | UBigInt | read_u64 | write_u64 |
FLOAT | Float | read_f32 | write_f32 |
DOUBLE | Double | read_f64 | write_f64 |
HUGEINT | HugeInt | read_i128 | write_i128 |
UHUGEINT | UHugeInt | read_u128 | write_u128 |
VARCHAR | Varchar | read_str | write_varchar |
BLOB | Blob | read_blob | write_blob |
UUID | Uuid | read_uuid | write_uuid |
DATE | Date | read_date | write_date |
TIME | Time | read_time | write_time |
TIMESTAMP | Timestamp | read_timestamp | write_timestamp |
INTERVAL | Interval | read_interval | write_interval |
The timestamp variants (TIMESTAMP_S, _MS, _NS, TIMESTAMPTZ), TIMETZ and DECIMAL
have matching read_*/write_* methods as well.
NULLs are handled separately — see NULL Handling & Strings.