duckdb/types/
from_sql.rs

1extern crate cast;
2
3use super::{TimeUnit, Value, ValueRef};
4use std::{error::Error, fmt};
5
6/// Enum listing possible errors from [`FromSql`] trait.
7#[derive(Debug)]
8#[non_exhaustive]
9pub enum FromSqlError {
10    /// Error when an DuckDB value is requested, but the type of the result
11    /// cannot be converted to the requested Rust type.
12    InvalidType,
13
14    /// Error when the value returned by DuckDB cannot be stored into the
15    /// requested type.
16    OutOfRange(i128),
17
18    /// `feature = "uuid"` Error returned when reading a `uuid` from a blob with
19    /// a size other than 16. Only available when the `uuid` feature is enabled.
20    #[cfg(feature = "uuid")]
21    InvalidUuidSize(usize),
22
23    /// An error case available for implementors of the [`FromSql`] trait.
24    Other(Box<dyn Error + Send + Sync + 'static>),
25}
26
27impl PartialEq for FromSqlError {
28    fn eq(&self, other: &FromSqlError) -> bool {
29        match (self, other) {
30            (FromSqlError::InvalidType, FromSqlError::InvalidType) => true,
31            (FromSqlError::OutOfRange(n1), FromSqlError::OutOfRange(n2)) => n1 == n2,
32            #[cfg(feature = "uuid")]
33            (FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(s2)) => s1 == s2,
34            (..) => false,
35        }
36    }
37}
38
39impl fmt::Display for FromSqlError {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        match *self {
42            FromSqlError::InvalidType => write!(f, "Invalid type"),
43            FromSqlError::OutOfRange(i) => write!(f, "Value {i} out of range"),
44            #[cfg(feature = "uuid")]
45            FromSqlError::InvalidUuidSize(s) => {
46                write!(f, "Cannot read UUID value out of {s} byte blob")
47            }
48            FromSqlError::Other(ref err) => err.fmt(f),
49        }
50    }
51}
52
53impl Error for FromSqlError {
54    fn source(&self) -> Option<&(dyn Error + 'static)> {
55        if let FromSqlError::Other(ref err) = self {
56            Some(&**err)
57        } else {
58            None
59        }
60    }
61}
62
63/// Result type for implementors of the [`FromSql`] trait.
64pub type FromSqlResult<T> = Result<T, FromSqlError>;
65
66/// A trait for types that can be created from a DuckDB value.
67pub trait FromSql: Sized {
68    /// Converts DuckDB value into Rust value.
69    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>;
70}
71
72macro_rules! from_sql_integral(
73    ($t:ident) => (
74        impl FromSql for $t {
75            #[inline]
76            fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
77                match value {
78                    // TODO: Update all cast operation same to HugeInt
79                    ValueRef::TinyInt(i) => Ok(<$t as cast::From<i8>>::cast(i).unwrap()),
80                    ValueRef::SmallInt(i) => Ok(<$t as cast::From<i16>>::cast(i).unwrap()),
81                    ValueRef::Int(i) => Ok(<$t as cast::From<i32>>::cast(i).unwrap()),
82                    ValueRef::BigInt(i) => Ok(<$t as cast::From<i64>>::cast(i).unwrap()),
83                    ValueRef::HugeInt(i) => {
84                        let v = <$t as cast::From<i128>>::cast(i);
85                        if v.is_ok() {
86                            Ok(v.unwrap())
87                        } else {
88                            Err(FromSqlError::OutOfRange(i))
89                        }
90                    },
91
92                    ValueRef::UTinyInt(i) => Ok(<$t as cast::From<u8>>::cast(i).unwrap()),
93                    ValueRef::USmallInt(i) => Ok(<$t as cast::From<u16>>::cast(i).unwrap()),
94                    ValueRef::UInt(i) => Ok(<$t as cast::From<u32>>::cast(i).unwrap()),
95                    ValueRef::UBigInt(i) => Ok(<$t as cast::From<u64>>::cast(i).unwrap()),
96
97                    ValueRef::Float(i) => Ok(<$t as cast::From<f32>>::cast(i).unwrap()),
98                    ValueRef::Double(i) => Ok(<$t as cast::From<f64>>::cast(i).unwrap()),
99
100                    // TODO: more efficient way?
101                    ValueRef::Decimal(i) => Ok(i.to_string().parse::<$t>().unwrap()),
102
103                    ValueRef::Timestamp(_, i) => Ok(<$t as cast::From<i64>>::cast(i).unwrap()),
104                    ValueRef::Date32(i) => Ok(<$t as cast::From<i32>>::cast(i).unwrap()),
105                    ValueRef::Time64(TimeUnit::Microsecond, i) => Ok(<$t as cast::From<i64>>::cast(i).unwrap()),
106                    ValueRef::Text(_) => {
107                        let v = value.as_str()?.parse::<$t>();
108                        match v {
109                            Ok(i) => Ok(i),
110                            Err(_) => {
111                                let v = value.as_str()?.parse::<i128>();
112                                match v {
113                                    Ok(i) => Err(FromSqlError::OutOfRange(i)),
114                                    _ => Err(FromSqlError::InvalidType),
115                                }
116                            },
117                        }
118                    }
119                    _ => Err(FromSqlError::InvalidType),
120                }
121            }
122        }
123    )
124);
125
126/// A trait for to implement unwrap method for primitive types
127/// cast::From trait returns Result or the primitive, and for
128/// Result we need to unwrap() for the column_result function
129/// We implement unwrap() for all the primitive types so
130/// We can always call unwrap() for the cast() function.
131trait Unwrap {
132    fn unwrap(self) -> Self;
133    fn is_ok(&self) -> bool;
134}
135
136macro_rules! unwrap_integral(
137    ($t:ident) => (
138        impl Unwrap for $t {
139            #[inline]
140            fn unwrap(self) -> Self {
141                self
142            }
143
144            #[inline]
145            fn is_ok(&self) -> bool {
146                true
147            }
148        }
149    )
150);
151
152unwrap_integral!(i8);
153unwrap_integral!(i16);
154unwrap_integral!(i32);
155unwrap_integral!(i64);
156unwrap_integral!(i128);
157unwrap_integral!(isize);
158unwrap_integral!(u8);
159unwrap_integral!(u16);
160unwrap_integral!(u32);
161unwrap_integral!(u64);
162unwrap_integral!(usize);
163unwrap_integral!(f32);
164unwrap_integral!(f64);
165
166from_sql_integral!(i8);
167from_sql_integral!(i16);
168from_sql_integral!(i32);
169from_sql_integral!(i64);
170from_sql_integral!(i128);
171from_sql_integral!(isize);
172from_sql_integral!(u8);
173from_sql_integral!(u16);
174from_sql_integral!(u32);
175from_sql_integral!(u64);
176from_sql_integral!(usize);
177from_sql_integral!(f32);
178from_sql_integral!(f64);
179
180impl FromSql for bool {
181    #[inline]
182    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
183        match value {
184            ValueRef::Boolean(b) => Ok(b),
185            _ => i8::column_result(value).map(|i| i != 0),
186        }
187    }
188}
189
190impl FromSql for String {
191    #[inline]
192    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
193        match value {
194            #[cfg(feature = "chrono")]
195            ValueRef::Date32(_) => Ok(chrono::NaiveDate::column_result(value)?.format("%F").to_string()),
196            #[cfg(feature = "chrono")]
197            ValueRef::Time64(..) => Ok(chrono::NaiveTime::column_result(value)?.format("%T%.f").to_string()),
198            #[cfg(feature = "chrono")]
199            ValueRef::Timestamp(..) => Ok(chrono::NaiveDateTime::column_result(value)?
200                .format("%F %T%.f")
201                .to_string()),
202            _ => value.as_str().map(ToString::to_string),
203        }
204    }
205}
206
207impl FromSql for Box<str> {
208    #[inline]
209    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
210        value.as_str().map(Into::into)
211    }
212}
213
214impl FromSql for std::rc::Rc<str> {
215    #[inline]
216    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
217        value.as_str().map(Into::into)
218    }
219}
220
221impl FromSql for std::sync::Arc<str> {
222    #[inline]
223    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
224        value.as_str().map(Into::into)
225    }
226}
227
228impl FromSql for Vec<u8> {
229    #[inline]
230    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
231        value.as_blob().map(|b| b.to_vec())
232    }
233}
234
235#[cfg(feature = "uuid")]
236impl FromSql for uuid::Uuid {
237    #[inline]
238    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
239        match value {
240            ValueRef::Text(..) => value
241                .as_str()
242                .and_then(|s| uuid::Uuid::parse_str(s).map_err(|_| FromSqlError::InvalidUuidSize(s.len()))),
243            ValueRef::Blob(..) => value
244                .as_blob()
245                .and_then(|bytes| {
246                    uuid::Builder::from_slice(bytes).map_err(|_| FromSqlError::InvalidUuidSize(bytes.len()))
247                })
248                .map(|builder| builder.into_uuid()),
249            _ => Err(FromSqlError::InvalidType),
250        }
251    }
252}
253
254impl<T: FromSql> FromSql for Option<T> {
255    #[inline]
256    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
257        match value {
258            ValueRef::Null => Ok(None),
259            _ => FromSql::column_result(value).map(Some),
260        }
261    }
262}
263
264impl FromSql for Value {
265    #[inline]
266    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
267        Ok(value.into())
268    }
269}
270
271#[cfg(test)]
272mod test {
273    use super::FromSql;
274    use crate::{Connection, Error, Result};
275
276    #[test]
277    fn test_timestamp_raw() -> Result<()> {
278        let db = Connection::open_in_memory()?;
279        let sql = "BEGIN;
280                   CREATE TABLE ts (sec TIMESTAMP_S, milli TIMESTAMP_MS, micro TIMESTAMP_US, nano TIMESTAMP_NS );
281                   INSERT INTO ts VALUES (NULL,NULL,NULL,NULL );
282                   INSERT INTO ts VALUES ('2008-01-01 00:00:01','2008-01-01 00:00:01.594','2008-01-01 00:00:01.88926','2008-01-01 00:00:01.889268000' );
283                   -- INSERT INTO ts VALUES (NULL,NULL,NULL,1199145601889268321 );
284                   END;";
285        db.execute_batch(sql)?;
286        let v = db.query_row(
287            "SELECT sec, milli, micro, nano FROM ts WHERE sec is not null",
288            [],
289            |row| <(i64, i64, i64, i64)>::try_from(row),
290        )?;
291        assert_eq!(v, (1199145601, 1199145601594, 1199145601889260, 1199145601889268000));
292        Ok(())
293    }
294
295    #[test]
296    fn test_time64_raw() -> Result<()> {
297        let db = Connection::open_in_memory()?;
298        let sql = "BEGIN;
299                   CREATE TABLE time64 (t time);
300                   INSERT INTO time64 VALUES ('20:08:10.998');
301                   END;";
302        db.execute_batch(sql)?;
303        let v = db.query_row("SELECT * FROM time64", [], |row| <(i64,)>::try_from(row))?;
304        assert_eq!(v, (72490998000,));
305        Ok(())
306    }
307
308    #[test]
309    fn test_date32_raw() -> Result<()> {
310        let db = Connection::open_in_memory()?;
311        let sql = "BEGIN;
312                   CREATE TABLE date32 (d date);
313                   INSERT INTO date32 VALUES ('2008-01-01');
314                   END;";
315        db.execute_batch(sql)?;
316        let v = db.query_row("SELECT * FROM date32", [], |row| <(i32,)>::try_from(row))?;
317        assert_eq!(v, (13879,));
318        Ok(())
319    }
320
321    #[test]
322    fn test_unsigned_integer() -> Result<()> {
323        let db = Connection::open_in_memory()?;
324        let sql = "BEGIN;
325                   CREATE TABLE unsigned_int (u1 utinyint, u2 usmallint, u4 uinteger, u8 ubigint);
326                   INSERT INTO unsigned_int VALUES (255, 65535, 4294967295, 18446744073709551615);
327                   END;";
328        db.execute_batch(sql)?;
329        let v = db.query_row("SELECT * FROM unsigned_int", [], |row| {
330            <(u8, u16, u32, u64)>::try_from(row)
331        })?;
332        assert_eq!(v, (255, 65535, 4294967295, 18446744073709551615));
333        Ok(())
334    }
335
336    // This test asserts that i128s above/below the i64 max/min can written and retrieved properly.
337    #[test]
338    fn test_hugeint_max_min() -> Result<()> {
339        let db = Connection::open_in_memory()?;
340        db.execute("CREATE TABLE huge_int (u1 hugeint, u2 hugeint);", [])?;
341        // Min/Max value defined in here: https://duckdb.org/docs/sql/data_types/numeric
342        let i128max: i128 = i128::MAX;
343        let i128min: i128 = i128::MIN + 1;
344        db.execute("INSERT INTO huge_int VALUES (?, ?);", [&i128max, &i128min])?;
345        let v = db.query_row("SELECT * FROM huge_int", [], |row| <(i128, i128)>::try_from(row))?;
346        assert_eq!(v, (i128max, i128min));
347        Ok(())
348    }
349
350    #[test]
351    fn test_integral_ranges() -> Result<()> {
352        let db = Connection::open_in_memory()?;
353
354        fn check_ranges<T>(db: &Connection, out_of_range: &[i128], in_range: &[i128])
355        where
356            T: Into<i128> + FromSql + ::std::fmt::Debug,
357        {
358            for n in out_of_range {
359                let err = db.query_row("SELECT ?", [n], |r| r.get::<_, T>(0)).unwrap_err();
360                match err {
361                    Error::IntegralValueOutOfRange(_, value) => assert_eq!(*n, value),
362                    _ => panic!("unexpected error: {err}"),
363                }
364            }
365            for n in in_range {
366                assert_eq!(*n, db.query_row("SELECT ?", [n], |r| r.get::<_, T>(0)).unwrap().into());
367            }
368        }
369
370        check_ranges::<i8>(&db, &[-129, 128], &[-128, 0, 1, 127]);
371        check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]);
372        check_ranges::<i32>(
373            &db,
374            &[-2_147_483_649, 2_147_483_648],
375            &[-2_147_483_648, -1, 0, 1, 2_147_483_647],
376        );
377        check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]);
378        check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]);
379        check_ranges::<u32>(&db, &[-2, -1, 4_294_967_296], &[0, 1, 4_294_967_295]);
380        Ok(())
381    }
382
383    // Don't need uuid crate if we only care about the string value of uuid
384    #[test]
385    fn test_uuid_string() -> Result<()> {
386        let db = Connection::open_in_memory()?;
387        let sql = "BEGIN;
388                   CREATE TABLE uuid (u uuid);
389                   INSERT INTO uuid VALUES ('10203040-5060-7080-0102-030405060708'),(NULL),('47183823-2574-4bfd-b411-99ed177d3e43');
390                   END;";
391        db.execute_batch(sql)?;
392        let v = db.query_row("SELECT u FROM uuid order by u desc nulls last limit 1", [], |row| {
393            <(String,)>::try_from(row)
394        })?;
395        assert_eq!(v, ("47183823-2574-4bfd-b411-99ed177d3e43".to_string(),));
396        let v = db.query_row(
397            "SELECT u FROM uuid where u>?::UUID",
398            ["10203040-5060-7080-0102-030405060708"],
399            |row| <(String,)>::try_from(row),
400        )?;
401        assert_eq!(v, ("47183823-2574-4bfd-b411-99ed177d3e43".to_string(),));
402        Ok(())
403    }
404
405    #[cfg(feature = "uuid")]
406    #[test]
407    fn test_uuid_from_string() -> crate::Result<()> {
408        let db = Connection::open_in_memory()?;
409        let sql = "BEGIN;
410                   CREATE TABLE uuid (u uuid);
411                   INSERT INTO uuid VALUES ('10203040-5060-7080-0102-030405060708'),(NULL),('47183823-2574-4bfd-b411-99ed177d3e43');
412                   END;";
413        db.execute_batch(sql)?;
414        let v = db.query_row("SELECT u FROM uuid order by u desc nulls last limit 1", [], |row| {
415            <(uuid::Uuid,)>::try_from(row)
416        })?;
417        assert_eq!(v.0.to_string(), "47183823-2574-4bfd-b411-99ed177d3e43");
418        Ok(())
419    }
420}