Skip to main content

duckdb/types/
mod.rs

1//! [`ToSql`] and [`FromSql`] are also implemented for `Option<T>` where `T`
2//! implements [`ToSql`] or [`FromSql`] for the cases where you want to know if
3//! a value was NULL (which gets translated to `None`).
4//!
5//! # Dynamic value compatibility notes
6//!
7//! [`Type`], [`Value`], and [`ValueRef`] are non-exhaustive. Downstream code
8//! matching these enums should include a wildcard arm so new DuckDB types can
9//! be added without another source break.
10//!
11//! Top-level `UHUGEINT` result values use [`Value::UHugeInt`] and
12//! [`ValueRef::UHugeInt`] when DuckDB reports `UHUGEINT` logical metadata for
13//! the result column. Nested `UHUGEINT` values inside lists, structs, maps,
14//! arrays, and unions do not currently carry DuckDB child logical type metadata
15//! through the borrowed container API, so values above `i128::MAX` cannot
16//! recover their `u128` value from those metadata-less carriers.
17//!
18//! Parameter-derived scale-zero Arrow decimal result columns are intentionally
19//! not guessed. DuckDB can report `Invalid` logical metadata for a bare bound
20//! parameter (`SELECT ?`) even when the bound value is `u128`. In that case
21//! `duckdb-rs` preserves the existing signed [`Value::HugeInt`] fallback instead
22//! of inferring `UHUGEINT`, `HUGEINT`, or `DECIMAL` from parameter names,
23//! aliases, or expression text. Cast the result expression so DuckDB reports
24//! stable logical metadata when a dynamic `UHUGEINT` carrier is required.
25//!
26//! DuckDB `DECIMAL` values use [`Decimal`], a full-domain carrier storing
27//! DuckDB's decimal width, scale, and scaled integer payload.
28//! Reading a [`Decimal`] as `f32` or `f64` preserves its fractional component
29//! but still inherits normal binary floating-point precision loss. Read
30//! [`Decimal`] directly when exact decimal transport matters.
31//! Enable the `rust_decimal` feature for compatibility impls that convert to
32//! and from `rust_decimal::Decimal` when the value fits that crate's smaller
33//! decimal domain, including compatibility reads from `FLOAT`, `DOUBLE`, and
34//! text columns.
35//!
36//! Arrow decimal result values use [`Value::Decimal`] and [`ValueRef::Decimal`]
37//! directly except for scale-zero `Decimal128(38, 0)`, which is also the Arrow
38//! shape DuckDB uses for `HUGEINT` and `UHUGEINT`. Top-level result columns use
39//! DuckDB logical metadata to distinguish those cases when DuckDB reports it.
40//! Narrower scale-zero `DECIMAL` widths are unambiguous and materialize as
41//! [`Decimal`] rather than the legacy signed [`Value::HugeInt`] fallback.
42//! Metadata-less scale-zero `Decimal128(38, 0)` values preserve the existing
43//! signed [`Value::HugeInt`] fallback. This affects parameter-derived
44//! expressions where DuckDB reports `Invalid` metadata, and nested container
45//! children where the borrowed container API does not carry DuckDB child
46//! logical metadata.
47//!
48//! DuckDB `GEOMETRY` values use [`Value::Geometry`] and
49//! [`ValueRef::Geometry`], backed by WKB bytes. CRS data is exposed as logical
50//! type metadata through [`crate::core::LogicalTypeHandle::geometry_crs`]
51//! rather than on each value.
52//! Nested `GEOMETRY` values inside lists, structs, maps, arrays, and unions do
53//! not currently carry DuckDB child logical type metadata through the borrowed
54//! container API, so they materialize through their Arrow binary carrier as
55//! [`Value::Blob`] values.
56
57pub use self::{
58    decimal::{Decimal, DecimalError},
59    from_sql::{FromSql, FromSqlError, FromSqlResult},
60    ordered_map::OrderedMap,
61    string::DuckString,
62    to_sql::{ToSql, ToSqlOutput},
63    value::Value,
64    value_ref::{EnumType, ListType, TimeUnit, ValueRef},
65};
66pub(crate) use decimal::to_duckdb_decimal;
67pub(crate) use value_ref::{binding_unsupported_value, value_ref_from_value};
68
69use arrow::datatypes::DataType;
70use std::fmt;
71
72use crate::ffi;
73
74#[cfg(feature = "chrono")]
75mod chrono;
76mod from_sql;
77#[cfg(feature = "serde_json")]
78mod serde_json;
79mod to_sql;
80#[cfg(feature = "url")]
81mod url;
82mod value;
83mod value_ref;
84
85mod decimal;
86mod ordered_map;
87mod string;
88
89pub(crate) fn to_duckdb_hugeint(i: i128) -> ffi::duckdb_hugeint {
90    ffi::duckdb_hugeint {
91        lower: i as u64,
92        upper: (i >> 64) as i64,
93    }
94}
95
96pub(crate) fn to_duckdb_uhugeint(i: u128) -> ffi::duckdb_uhugeint {
97    ffi::duckdb_uhugeint {
98        lower: i as u64,
99        upper: (i >> 64) as u64,
100    }
101}
102
103/// Empty struct that can be used to fill in a query parameter as `NULL`.
104///
105/// ## Example
106///
107/// ```rust,no_run
108/// # use duckdb::{Connection, Result};
109/// # use duckdb::types::{Null};
110///
111/// fn insert_null(conn: &Connection) -> Result<usize> {
112///     conn.execute("INSERT INTO people (name) VALUES (?)", [Null])
113/// }
114/// ```
115#[derive(Copy, Clone)]
116pub struct Null;
117
118/// DuckDB data types.
119/// See [Fundamental Datatypes](https://duckdb.org/docs/sql/data_types/overview).
120#[derive(Clone, Debug, PartialEq, Eq)]
121#[non_exhaustive]
122pub enum Type {
123    /// NULL
124    Null,
125    /// BOOLEAN
126    Boolean,
127    /// TINYINT
128    TinyInt,
129    /// SMALLINT
130    SmallInt,
131    /// INT
132    Int,
133    /// BIGINT
134    BigInt,
135    /// HUGEINT
136    HugeInt,
137    /// UHUGEINT
138    UHugeInt,
139    /// UTINYINT
140    UTinyInt,
141    /// USMALLINT
142    USmallInt,
143    /// UINT
144    UInt,
145    /// UBIGINT
146    UBigInt,
147    /// FLOAT
148    Float,
149    /// DOUBLE
150    Double,
151    /// DECIMAL
152    Decimal,
153    /// TIMESTAMP
154    Timestamp,
155    /// Text
156    Text,
157    /// BLOB
158    Blob,
159    /// GEOMETRY
160    Geometry,
161    /// DATE32
162    Date32,
163    /// TIME64
164    Time64,
165    /// INTERVAL
166    Interval,
167    /// LIST
168    List(Box<Type>),
169    /// ENUM
170    Enum,
171    /// STRUCT
172    Struct(Vec<(String, Type)>),
173    /// MAP
174    Map(Box<Type>, Box<Type>),
175    /// ARRAY
176    Array(Box<Type>, u32),
177    /// UNION
178    Union,
179    /// VARIANT. See [`LogicalTypeId::Variant`](crate::core::LogicalTypeId::Variant).
180    Variant,
181    /// Any
182    Any,
183}
184
185impl From<&DataType> for Type {
186    fn from(value: &DataType) -> Self {
187        match value {
188            DataType::Null => Self::Null,
189            DataType::Boolean => Self::Boolean,
190            DataType::Int8 => Self::TinyInt,
191            DataType::Int16 => Self::SmallInt,
192            DataType::Int32 => Self::Int,
193            DataType::Int64 => Self::BigInt,
194            DataType::UInt8 => Self::UTinyInt,
195            DataType::UInt16 => Self::USmallInt,
196            DataType::UInt32 => Self::UInt,
197            DataType::UInt64 => Self::UBigInt,
198            // DataType::Float16 => Self::Float16,
199            DataType::Float32 => Self::Float,  // Single precision (4 bytes)
200            DataType::Float64 => Self::Double, // Double precision (8 bytes)
201            DataType::Timestamp(_, _) => Self::Timestamp,
202            DataType::Date32 => Self::Date32,
203            // DataType::Date64 => Self::Date64,
204            // DataType::Time32(_) => Self::Time32,
205            DataType::Time64(_) => Self::Time64,
206            // DataType::Duration(_) => Self::Duration,
207            // DataType::Interval(_) => Self::Interval,
208            DataType::Binary => Self::Blob,
209            DataType::FixedSizeBinary(_) => Self::Blob,
210            // DataType::LargeBinary => Self::LargeBinary,
211            DataType::LargeUtf8 | DataType::Utf8 => Self::Text,
212            DataType::List(inner) => Self::List(Box::new(Self::from(inner.data_type()))),
213            DataType::FixedSizeList(field, size) => Self::Array(
214                Box::new(Self::from(field.data_type())),
215                (*size)
216                    .try_into()
217                    .expect("Arrow FixedSizeList size must be non-negative"),
218            ),
219            // DataType::LargeList(_) => Self::LargeList,
220            DataType::Struct(inner) => {
221                let capacity = inner.len();
222                let mut struct_vec = Vec::with_capacity(capacity);
223                struct_vec.extend(inner.iter().map(|f| (f.name().to_owned(), Self::from(f.data_type()))));
224                Self::Struct(struct_vec)
225            }
226            DataType::LargeList(inner) => Self::List(Box::new(Self::from(inner.data_type()))),
227            DataType::Union(_, _) => Self::Union,
228            // Type is a broad value category: Decimal256 is decimal-shaped
229            // metadata, even though DuckDB value paths only carry widths <= 38.
230            DataType::Decimal32(..) | DataType::Decimal64(..) | DataType::Decimal128(..) | DataType::Decimal256(..) => {
231                Self::Decimal
232            }
233            DataType::Map(field, ..) => {
234                let data_type = field.data_type();
235                match data_type {
236                    DataType::Struct(fields) if fields.len() == 2 => Self::Map(
237                        Box::new(Self::from(fields[0].data_type())),
238                        Box::new(Self::from(fields[1].data_type())),
239                    ),
240                    DataType::Struct(fields) => {
241                        panic!(
242                            "Arrow Map child struct must have exactly two fields, got {}",
243                            fields.len()
244                        )
245                    }
246                    _ => panic!("Arrow Map child type must be Struct(key, value), got {data_type}"),
247                }
248            }
249            res => unimplemented!("{}", res),
250        }
251    }
252}
253
254impl fmt::Display for Type {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        match *self {
257            Self::Null => f.pad("Null"),
258            Self::Boolean => f.pad("Boolean"),
259            Self::TinyInt => f.pad("TinyInt"),
260            Self::SmallInt => f.pad("SmallInt"),
261            Self::Int => f.pad("Int"),
262            Self::BigInt => f.pad("BigInt"),
263            Self::HugeInt => f.pad("HugeInt"),
264            Self::UHugeInt => f.pad("UHugeInt"),
265            Self::UTinyInt => f.pad("UTinyInt"),
266            Self::USmallInt => f.pad("USmallInt"),
267            Self::UInt => f.pad("UInt"),
268            Self::UBigInt => f.pad("UBigInt"),
269            Self::Float => f.pad("Float"),
270            Self::Double => f.pad("Double"),
271            Self::Decimal => f.pad("Decimal"),
272            Self::Timestamp => f.pad("Timestamp"),
273            Self::Text => f.pad("Text"),
274            Self::Blob => f.pad("Blob"),
275            Self::Geometry => f.pad("Geometry"),
276            Self::Date32 => f.pad("Date32"),
277            Self::Time64 => f.pad("Time64"),
278            Self::Interval => f.pad("Interval"),
279            Self::Struct(..) => f.pad("Struct"),
280            Self::List(..) => f.pad("List"),
281            Self::Enum => f.pad("Enum"),
282            Self::Map(..) => f.pad("Map"),
283            Self::Array(..) => f.pad("Array"),
284            Self::Union => f.pad("Union"),
285            Self::Variant => f.pad("Variant"),
286            Self::Any => f.pad("Any"),
287        }
288    }
289}
290
291#[cfg(test)]
292mod test {
293    use super::{Type, Value};
294    use crate::{Connection, Result};
295    use arrow::datatypes::DataType;
296
297    fn checked_memory_handle() -> Result<Connection> {
298        let db = Connection::open_in_memory()?;
299        db.execute_batch("CREATE TABLE foo (b BLOB, t TEXT, i INTEGER, f FLOAT, n BLOB)")?;
300        Ok(db)
301    }
302
303    #[test]
304    fn test_blob() -> Result<()> {
305        let db = checked_memory_handle()?;
306
307        let v1234 = vec![1u8, 2, 3, 4];
308        db.execute("INSERT INTO foo(b) VALUES (?)", [&v1234])?;
309
310        let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
311        assert_eq!(v, v1234);
312        Ok(())
313    }
314
315    #[test]
316    fn test_empty_blob() -> Result<()> {
317        let db = checked_memory_handle()?;
318
319        let empty = vec![];
320        db.execute("INSERT INTO foo(b) VALUES (?)", [&empty])?;
321
322        let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.get(0))?;
323        assert_eq!(v, empty);
324        Ok(())
325    }
326
327    #[test]
328    fn test_geometry_value_binds_as_wkb_blob() -> Result<()> {
329        let db = checked_memory_handle()?;
330        let wkb = vec![1_u8, 1, 0, 0, 0];
331
332        let value = Value::Geometry(wkb.clone());
333        let bound: Vec<u8> = db.query_row("SELECT ?::BLOB", [value], |r| r.get(0))?;
334
335        assert_eq!(bound, wkb);
336        Ok(())
337    }
338
339    #[test]
340    fn test_geometry_value_binds_through_st_geomfromwkb() -> Result<()> {
341        let db = checked_memory_handle()?;
342        let wkb = vec![
343            0x01, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xF8, 0x7F, 0, 0, 0, 0, 0, 0, 0xF8, 0x7F,
344        ];
345
346        let value = Value::Geometry(wkb.clone());
347        let bound: Value = db.query_row("SELECT ST_GeomFromWKB(?)", [value], |r| r.get(0))?;
348
349        assert_eq!(bound, Value::Geometry(wkb));
350        Ok(())
351    }
352
353    #[test]
354    fn test_str() -> Result<()> {
355        let db = checked_memory_handle()?;
356
357        let s = "hello, world!";
358        db.execute("INSERT INTO foo(t) VALUES (?)", [&s])?;
359
360        let from: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
361        assert_eq!(from, s);
362        Ok(())
363    }
364
365    #[test]
366    fn test_string() -> Result<()> {
367        let db = checked_memory_handle()?;
368
369        let s = "hello, world!";
370        let result = db.execute("INSERT INTO foo(t) VALUES (?)", [s.to_owned()]);
371        if let Err(e) = result {
372            panic!("exe error: {e}")
373        }
374
375        let from: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
376        assert_eq!(from, s);
377        Ok(())
378    }
379
380    #[test]
381    fn test_value() -> Result<()> {
382        let db = checked_memory_handle()?;
383
384        db.execute("INSERT INTO foo(i) VALUES (?)", [Value::BigInt(10)])?;
385
386        assert_eq!(10i64, db.query_row::<i64, _, _>("SELECT i FROM foo", [], |r| r.get(0))?);
387        Ok(())
388    }
389
390    #[test]
391    fn arrow_decimal_types_match_duckdb_decimal_classification() {
392        assert_eq!(Type::from(&DataType::Decimal32(9, 2)), Type::Decimal);
393        assert_eq!(Type::from(&DataType::Decimal64(18, 2)), Type::Decimal);
394        assert_eq!(Type::from(&DataType::Decimal128(38, 2)), Type::Decimal);
395        assert_eq!(Type::from(&DataType::Decimal256(76, 10)), Type::Decimal);
396    }
397
398    #[test]
399    fn test_option() -> Result<()> {
400        let db = checked_memory_handle()?;
401
402        let s = "hello, world!";
403        let b = Some(vec![1u8, 2, 3, 4]);
404
405        db.execute("INSERT INTO foo(t) VALUES (?)", [&s])?;
406        db.execute("INSERT INTO foo(b) VALUES (?)", [&b])?;
407
408        let mut stmt = db.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")?;
409        let mut rows = stmt.query([])?;
410
411        {
412            let row1 = rows.next()?.unwrap();
413            let s1: Option<String> = row1.get_unwrap(0);
414            let b1: Option<Vec<u8>> = row1.get_unwrap(1);
415            assert_eq!(s, s1.unwrap());
416            assert!(b1.is_none());
417        }
418
419        {
420            let row2 = rows.next()?.unwrap();
421            let s2: Option<String> = row2.get_unwrap(0);
422            let b2: Option<Vec<u8>> = row2.get_unwrap(1);
423            assert!(s2.is_none());
424            assert_eq!(b, b2);
425        }
426        Ok(())
427    }
428
429    #[test]
430    fn test_dynamic_type() -> Result<()> {
431        use super::Value;
432        let db = checked_memory_handle()?;
433
434        db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)", [])?;
435
436        let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo")?;
437        let mut rows = stmt.query([])?;
438        let row = rows.next()?.unwrap();
439        // NOTE: this is different from SQLite
440        // assert_eq!(Value::Blob(vec![1, 2]), row.get::<_, Value>(0)?);
441        assert_eq!(Value::Blob(vec![120, 48, 49, 48, 50]), row.get::<_, Value>(0)?);
442        assert_eq!(Value::Text(String::from("text")), row.get::<_, Value>(1)?);
443        assert_eq!(Value::Int(1), row.get::<_, Value>(2)?);
444        match row.get::<_, Value>(3)? {
445            Value::Float(val) => assert!((1.5 - val).abs() < f32::EPSILON),
446            x => panic!("Invalid Value {x:?}"),
447        }
448        assert_eq!(Value::Null, row.get::<_, Value>(4)?);
449        Ok(())
450    }
451}