Skip to main content

duckdb/types/
from_sql.rs

1use std::{error::Error, fmt};
2
3use cast;
4
5use super::{Decimal, TimeUnit, Value, ValueRef};
6
7/// Enum listing possible errors from [`FromSql`] trait.
8#[derive(Debug)]
9#[non_exhaustive]
10pub enum FromSqlError {
11    /// Error when an DuckDB value is requested, but the type of the result
12    /// cannot be converted to the requested Rust type.
13    InvalidType,
14
15    /// Error when the value returned by DuckDB cannot be stored into the
16    /// requested type.
17    OutOfRange(i128),
18
19    /// Error when an unsigned 128-bit value returned by DuckDB cannot be
20    /// stored into the requested type. This is reported for `UHUGEINT` sources;
21    /// same-magnitude signed sources use [`Self::OutOfRange`].
22    OutOfRangeUnsigned(u128),
23
24    /// `feature = "uuid"` Error returned when reading a `uuid` from a blob with
25    /// a size other than 16. Only available when the `uuid` feature is enabled.
26    #[cfg(feature = "uuid")]
27    InvalidUuidSize(usize),
28
29    /// An error case available for implementors of the [`FromSql`] trait.
30    Other(Box<dyn Error + Send + Sync + 'static>),
31}
32
33impl PartialEq for FromSqlError {
34    fn eq(&self, other: &Self) -> bool {
35        match (self, other) {
36            (Self::InvalidType, Self::InvalidType) => true,
37            (Self::OutOfRange(n1), Self::OutOfRange(n2)) => n1 == n2,
38            (Self::OutOfRangeUnsigned(n1), Self::OutOfRangeUnsigned(n2)) => n1 == n2,
39            #[cfg(feature = "uuid")]
40            (Self::InvalidUuidSize(s1), Self::InvalidUuidSize(s2)) => s1 == s2,
41            (..) => false,
42        }
43    }
44}
45
46impl fmt::Display for FromSqlError {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        match *self {
49            Self::InvalidType => write!(f, "Invalid type"),
50            Self::OutOfRange(i) => write!(f, "Value {i} out of range"),
51            Self::OutOfRangeUnsigned(i) => write!(f, "Unsigned value {i} out of range"),
52            #[cfg(feature = "uuid")]
53            Self::InvalidUuidSize(s) => {
54                write!(f, "Cannot read UUID value out of {s} byte blob")
55            }
56            Self::Other(ref err) => err.fmt(f),
57        }
58    }
59}
60
61impl Error for FromSqlError {
62    fn source(&self) -> Option<&(dyn Error + 'static)> {
63        if let Self::Other(err) = self {
64            Some(&**err)
65        } else {
66            None
67        }
68    }
69}
70
71/// Result type for implementors of the [`FromSql`] trait.
72pub type FromSqlResult<T> = Result<T, FromSqlError>;
73
74/// A trait for types that can be created from a DuckDB value.
75pub trait FromSql: Sized {
76    /// Converts DuckDB value into Rust value.
77    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>;
78}
79
80fn rounded_decimal(value: i128, scale: u8) -> Option<i128> {
81    let divisor = 10_i128.checked_pow(u32::from(scale))?;
82    let quotient = value / divisor;
83    let remainder = value % divisor;
84
85    // Mirror DuckDB's DECIMAL-to-INTEGER cast: round to nearest, with
86    // fractional ties rounded away from zero.
87    if remainder.unsigned_abs() * 2 < divisor as u128 {
88        return Some(quotient);
89    }
90
91    if value.is_negative() {
92        quotient.checked_sub(1)
93    } else {
94        quotient.checked_add(1)
95    }
96}
97
98fn rounded_decimal_result(value: i128, scale: u8) -> FromSqlResult<i128> {
99    rounded_decimal(value, scale).ok_or(FromSqlError::OutOfRange(value))
100}
101
102fn decimal_to_f32(decimal: Decimal) -> f32 {
103    // Preserve decimal scale, while inheriting normal binary float precision loss.
104    decimal.value() as f32 / 10_f32.powi(i32::from(decimal.scale()))
105}
106
107fn decimal_to_f64(decimal: Decimal) -> f64 {
108    // Preserve decimal scale, while inheriting normal binary float precision loss.
109    decimal.value() as f64 / 10_f64.powi(i32::from(decimal.scale()))
110}
111
112macro_rules! from_sql_numeric(
113    ($t:ident, $decimal:expr) => (
114        impl FromSql for $t {
115            #[inline]
116            fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
117                match value {
118                    ValueRef::TinyInt(i) => <$t as cast::From<i8>>::cast(i).into_result(FromSqlError::OutOfRange(i as i128)),
119                    ValueRef::SmallInt(i) => <$t as cast::From<i16>>::cast(i).into_result(FromSqlError::OutOfRange(i as i128)),
120                    ValueRef::Int(i) => <$t as cast::From<i32>>::cast(i).into_result(FromSqlError::OutOfRange(i as i128)),
121                    ValueRef::BigInt(i) => <$t as cast::From<i64>>::cast(i).into_result(FromSqlError::OutOfRange(i as i128)),
122                    ValueRef::HugeInt(i) => <$t as cast::From<i128>>::cast(i).into_result(FromSqlError::OutOfRange(i)),
123                    ValueRef::UHugeInt(i) => <$t as cast::From<u128>>::cast(i)
124                        .into_result(FromSqlError::OutOfRangeUnsigned(i)),
125
126                    ValueRef::UTinyInt(i) => <$t as cast::From<u8>>::cast(i).into_result(FromSqlError::OutOfRange(i as i128)),
127                    ValueRef::USmallInt(i) => <$t as cast::From<u16>>::cast(i).into_result(FromSqlError::OutOfRange(i as i128)),
128                    ValueRef::UInt(i) => <$t as cast::From<u32>>::cast(i).into_result(FromSqlError::OutOfRange(i as i128)),
129                    ValueRef::UBigInt(i) => <$t as cast::From<u64>>::cast(i).into_result(FromSqlError::OutOfRange(i as i128)),
130
131                    ValueRef::Float(i) => <$t as cast::From<f32>>::cast(i).into_result(FromSqlError::OutOfRange(i as i128)),
132                    ValueRef::Double(i) => <$t as cast::From<f64>>::cast(i).into_result(FromSqlError::OutOfRange(i as i128)),
133
134                    ValueRef::Decimal(decimal) => ($decimal)(decimal),
135
136                    ValueRef::Timestamp(_, i) => <$t as cast::From<i64>>::cast(i).into_result(FromSqlError::OutOfRange(i as i128)),
137                    ValueRef::Date32(i) => <$t as cast::From<i32>>::cast(i).into_result(FromSqlError::OutOfRange(i as i128)),
138                    ValueRef::Time64(TimeUnit::Microsecond, i) => <$t as cast::From<i64>>::cast(i).into_result(FromSqlError::OutOfRange(i as i128)),
139                    ValueRef::Text(_) => {
140                        let s = value.as_str()?;
141                        s.parse::<$t>().or_else(|_| {
142                            s.parse::<i128>()
143                                .map_err(|_| FromSqlError::InvalidType)
144                                .and_then(|i| Err(FromSqlError::OutOfRange(i)))
145                        })
146                    }
147                    _ => Err(FromSqlError::InvalidType),
148                }
149            }
150        }
151    )
152);
153
154macro_rules! from_sql_integral(
155    ($t:ident) => (
156        from_sql_numeric!($t, |decimal: Decimal| {
157            let rounded = rounded_decimal_result(decimal.value(), decimal.scale())?;
158            <$t as cast::From<i128>>::cast(rounded)
159                .into_result(FromSqlError::OutOfRange(rounded))
160        });
161    )
162);
163
164macro_rules! from_sql_float(
165    ($t:ident, $decimal_to_float:ident) => (
166        from_sql_numeric!($t, |decimal: Decimal| Ok($decimal_to_float(decimal)));
167    )
168);
169
170/// A trait to provide ok_or method for both Result and primitive types
171/// cast::From trait returns Result or the primitive, depending on the types
172trait IntoResult {
173    type Value;
174    fn into_result<E>(self, err: E) -> Result<Self::Value, E>;
175}
176
177/// A macro to implement the IntoResult trait for all integral types
178macro_rules! into_result_integral(
179    ($type_name:ident) => (
180        impl IntoResult for $type_name {
181            type Value = $type_name;
182
183            #[inline]
184            fn into_result<E>(self, _err: E) -> Result<Self::Value, E> {
185                Ok(self)
186            }
187        }
188    )
189);
190
191into_result_integral!(i8);
192into_result_integral!(i16);
193into_result_integral!(i32);
194into_result_integral!(i64);
195into_result_integral!(i128);
196into_result_integral!(isize);
197into_result_integral!(u8);
198into_result_integral!(u16);
199into_result_integral!(u32);
200into_result_integral!(u64);
201into_result_integral!(u128);
202into_result_integral!(usize);
203into_result_integral!(f32);
204into_result_integral!(f64);
205
206impl<T, E> IntoResult for Result<T, E> {
207    type Value = T;
208
209    #[inline]
210    fn into_result<E2>(self, err: E2) -> Result<Self::Value, E2> {
211        self.map_err(|_| err)
212    }
213}
214
215from_sql_integral!(i8);
216from_sql_integral!(i16);
217from_sql_integral!(i32);
218from_sql_integral!(i64);
219from_sql_integral!(i128);
220from_sql_integral!(isize);
221from_sql_integral!(u8);
222from_sql_integral!(u16);
223from_sql_integral!(u32);
224from_sql_integral!(u64);
225from_sql_integral!(u128);
226from_sql_integral!(usize);
227from_sql_float!(f32, decimal_to_f32);
228from_sql_float!(f64, decimal_to_f64);
229
230impl FromSql for bool {
231    #[inline]
232    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
233        match value {
234            ValueRef::Boolean(b) => Ok(b),
235            _ => i8::column_result(value).map(|i| i != 0),
236        }
237    }
238}
239
240impl FromSql for String {
241    #[inline]
242    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
243        match value {
244            #[cfg(feature = "chrono")]
245            ValueRef::Date32(_) => Ok(chrono::NaiveDate::column_result(value)?.format("%F").to_string()),
246            #[cfg(feature = "chrono")]
247            ValueRef::Time64(..) => Ok(chrono::NaiveTime::column_result(value)?.format("%T%.f").to_string()),
248            #[cfg(feature = "chrono")]
249            ValueRef::Timestamp(..) => Ok(chrono::NaiveDateTime::column_result(value)?
250                .format("%F %T%.f")
251                .to_string()),
252            _ => value.as_str().map(ToString::to_string),
253        }
254    }
255}
256
257impl FromSql for Box<str> {
258    #[inline]
259    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
260        value.as_str().map(Into::into)
261    }
262}
263
264impl FromSql for std::rc::Rc<str> {
265    #[inline]
266    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
267        value.as_str().map(Into::into)
268    }
269}
270
271impl FromSql for std::sync::Arc<str> {
272    #[inline]
273    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
274        value.as_str().map(Into::into)
275    }
276}
277
278impl FromSql for Vec<u8> {
279    #[inline]
280    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
281        value.as_blob().map(|b| b.to_vec())
282    }
283}
284
285#[cfg(feature = "uuid")]
286impl FromSql for uuid::Uuid {
287    #[inline]
288    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
289        match value {
290            ValueRef::Text(..) => value
291                .as_str()
292                .and_then(|s| Self::parse_str(s).map_err(|_| FromSqlError::InvalidUuidSize(s.len()))),
293            ValueRef::Blob(..) => value
294                .as_blob()
295                .and_then(|bytes| {
296                    uuid::Builder::from_slice(bytes).map_err(|_| FromSqlError::InvalidUuidSize(bytes.len()))
297                })
298                .map(|builder| builder.into_uuid()),
299            _ => Err(FromSqlError::InvalidType),
300        }
301    }
302}
303
304impl<T: FromSql> FromSql for Option<T> {
305    #[inline]
306    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
307        match value {
308            ValueRef::Null => Ok(None),
309            _ => FromSql::column_result(value).map(Some),
310        }
311    }
312}
313
314impl FromSql for Value {
315    #[inline]
316    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
317        Ok(value.into())
318    }
319}
320
321#[cfg(test)]
322mod test {
323    use super::{FromSql, FromSqlError};
324    use crate::{Connection, Error, Result};
325
326    #[test]
327    fn test_timestamp_raw() -> Result<()> {
328        let db = Connection::open_in_memory()?;
329        let sql = "BEGIN;
330                   CREATE TABLE ts (sec TIMESTAMP_S, milli TIMESTAMP_MS, micro TIMESTAMP_US, nano TIMESTAMP_NS );
331                   INSERT INTO ts VALUES (NULL,NULL,NULL,NULL );
332                   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' );
333                   -- INSERT INTO ts VALUES (NULL,NULL,NULL,1199145601889268321 );
334                   END;";
335        db.execute_batch(sql)?;
336        let v = db.query_row(
337            "SELECT sec, milli, micro, nano FROM ts WHERE sec is not null",
338            [],
339            |row| <(i64, i64, i64, i64)>::try_from(row),
340        )?;
341        assert_eq!(v, (1199145601, 1199145601594, 1199145601889260, 1199145601889268000));
342        Ok(())
343    }
344
345    #[test]
346    fn test_time64_raw() -> Result<()> {
347        let db = Connection::open_in_memory()?;
348        let sql = "BEGIN;
349                   CREATE TABLE time64 (t time);
350                   INSERT INTO time64 VALUES ('20:08:10.998');
351                   END;";
352        db.execute_batch(sql)?;
353        let v = db.query_row("SELECT * FROM time64", [], |row| <(i64,)>::try_from(row))?;
354        assert_eq!(v, (72490998000,));
355        Ok(())
356    }
357
358    #[test]
359    fn test_date32_raw() -> Result<()> {
360        let db = Connection::open_in_memory()?;
361        let sql = "BEGIN;
362                   CREATE TABLE date32 (d date);
363                   INSERT INTO date32 VALUES ('2008-01-01');
364                   END;";
365        db.execute_batch(sql)?;
366        let v = db.query_row("SELECT * FROM date32", [], |row| <(i32,)>::try_from(row))?;
367        assert_eq!(v, (13879,));
368        Ok(())
369    }
370
371    #[test]
372    fn test_unsigned_integer() -> Result<()> {
373        let db = Connection::open_in_memory()?;
374        let sql = "BEGIN;
375                   CREATE TABLE unsigned_int (u1 utinyint, u2 usmallint, u4 uinteger, u8 ubigint);
376                   INSERT INTO unsigned_int VALUES (255, 65535, 4294967295, 18446744073709551615);
377                   END;";
378        db.execute_batch(sql)?;
379        let v = db.query_row("SELECT * FROM unsigned_int", [], |row| {
380            <(u8, u16, u32, u64)>::try_from(row)
381        })?;
382        assert_eq!(v, (255, 65535, 4294967295, 18446744073709551615));
383        Ok(())
384    }
385
386    // This test asserts that i128s above/below the i64 max/min can written and retrieved properly.
387    #[test]
388    fn test_hugeint_max_min() -> Result<()> {
389        let db = Connection::open_in_memory()?;
390        db.execute("CREATE TABLE huge_int (u1 hugeint, u2 hugeint);", [])?;
391        // Min/Max value defined in here: https://duckdb.org/docs/sql/data_types/numeric
392        let i128max: i128 = i128::MAX;
393        let i128min: i128 = i128::MIN + 1;
394        db.execute("INSERT INTO huge_int VALUES (?, ?);", [&i128max, &i128min])?;
395        let v = db.query_row("SELECT * FROM huge_int", [], |row| <(i128, i128)>::try_from(row))?;
396        assert_eq!(v, (i128max, i128min));
397        Ok(())
398    }
399
400    #[test]
401    fn test_integral_ranges() -> Result<()> {
402        let db = Connection::open_in_memory()?;
403
404        fn check_ranges<T>(db: &Connection, out_of_range: &[i128], in_range: &[i128])
405        where
406            T: Into<i128> + FromSql + ::std::fmt::Debug,
407        {
408            for n in out_of_range {
409                let err = db.query_row("SELECT ?", [n], |r| r.get::<_, T>(0)).unwrap_err();
410                match err {
411                    Error::IntegralValueOutOfRange(_, value) => assert_eq!(*n, value),
412                    _ => panic!("unexpected error: {err}"),
413                }
414            }
415            for n in in_range {
416                assert_eq!(*n, db.query_row("SELECT ?", [n], |r| r.get::<_, T>(0)).unwrap().into());
417            }
418        }
419
420        check_ranges::<i8>(&db, &[-129, 128], &[-128, 0, 1, 127]);
421        check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]);
422        check_ranges::<i32>(
423            &db,
424            &[-2_147_483_649, 2_147_483_648],
425            &[-2_147_483_648, -1, 0, 1, 2_147_483_647],
426        );
427        check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]);
428        check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]);
429        check_ranges::<u32>(&db, &[-2, -1, 4_294_967_296], &[0, 1, 4_294_967_295]);
430
431        let err = db
432            .query_row("SELECT (-1)::HUGEINT", [], |r| r.get::<_, u128>(0))
433            .unwrap_err();
434        match err {
435            Error::IntegralValueOutOfRange(_, value) => assert_eq!(value, -1),
436            _ => panic!("unexpected error: {err}"),
437        }
438
439        let ubigint_max = u128::from(u64::MAX);
440        let value = db.query_row("SELECT (18446744073709551615)::UBIGINT", [], |r| r.get::<_, u128>(0))?;
441        assert_eq!(value, ubigint_max);
442
443        let u64_overflow = ubigint_max + 1;
444        let err = db
445            .query_row("SELECT (18446744073709551616)::UHUGEINT", [], |r| r.get::<_, u64>(0))
446            .unwrap_err();
447        match err {
448            Error::UnsignedIntegralValueOutOfRange(_, value) => assert_eq!(value, u64_overflow),
449            _ => panic!("unexpected error: {err}"),
450        }
451
452        assert_eq!(
453            FromSqlError::OutOfRangeUnsigned(u64_overflow).to_string(),
454            "Unsigned value 18446744073709551616 out of range"
455        );
456        Ok(())
457    }
458
459    // Don't need uuid crate if we only care about the string value of uuid
460    #[test]
461    fn test_uuid_string() -> Result<()> {
462        let db = Connection::open_in_memory()?;
463        let sql = "BEGIN;
464                   CREATE TABLE uuid (u uuid);
465                   INSERT INTO uuid VALUES ('10203040-5060-7080-0102-030405060708'),(NULL),('47183823-2574-4bfd-b411-99ed177d3e43');
466                   END;";
467        db.execute_batch(sql)?;
468        let v = db.query_row("SELECT u FROM uuid order by u desc nulls last limit 1", [], |row| {
469            <(String,)>::try_from(row)
470        })?;
471        assert_eq!(v, ("47183823-2574-4bfd-b411-99ed177d3e43".to_string(),));
472        let v = db.query_row(
473            "SELECT u FROM uuid where u>?::UUID",
474            ["10203040-5060-7080-0102-030405060708"],
475            |row| <(String,)>::try_from(row),
476        )?;
477        assert_eq!(v, ("47183823-2574-4bfd-b411-99ed177d3e43".to_string(),));
478        Ok(())
479    }
480
481    #[cfg(feature = "uuid")]
482    #[test]
483    fn test_uuid_from_string() -> crate::Result<()> {
484        let db = Connection::open_in_memory()?;
485        let sql = "BEGIN;
486                   CREATE TABLE uuid (u uuid);
487                   INSERT INTO uuid VALUES ('10203040-5060-7080-0102-030405060708'),(NULL),('47183823-2574-4bfd-b411-99ed177d3e43');
488                   END;";
489        db.execute_batch(sql)?;
490        let v = db.query_row("SELECT u FROM uuid order by u desc nulls last limit 1", [], |row| {
491            <(uuid::Uuid,)>::try_from(row)
492        })?;
493        assert_eq!(v.0.to_string(), "47183823-2574-4bfd-b411-99ed177d3e43");
494        Ok(())
495    }
496
497    #[test]
498    fn test_decimal_to_integer() -> Result<()> {
499        let db = Connection::open_in_memory()?;
500
501        assert_eq!(
502            db.query_row("SELECT 0.1::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
503            0
504        );
505        assert_eq!(
506            db.query_row("SELECT 0.4::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
507            0
508        );
509        assert_eq!(
510            db.query_row("SELECT 0.5::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
511            1
512        );
513        assert_eq!(
514            db.query_row("SELECT 0.6::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
515            1
516        );
517        assert_eq!(
518            db.query_row("SELECT 0.9::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
519            1
520        );
521
522        assert_eq!(
523            db.query_row("SELECT 1.5::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
524            2
525        );
526        assert_eq!(
527            db.query_row("SELECT 2.5::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
528            3
529        );
530        assert_eq!(
531            db.query_row("SELECT 3.5::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
532            4
533        );
534        assert_eq!(
535            db.query_row("SELECT 4.5::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
536            5
537        );
538        assert_eq!(
539            db.query_row("SELECT 5.5::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
540            6
541        );
542        assert_eq!(
543            db.query_row("SELECT 10.5::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
544            11
545        );
546        assert_eq!(
547            db.query_row("SELECT 99.5::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
548            100
549        );
550
551        assert_eq!(
552            db.query_row("SELECT -0.5::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
553            -1
554        );
555        assert_eq!(
556            db.query_row("SELECT -1.5::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
557            -2
558        );
559        assert_eq!(
560            db.query_row("SELECT -2.5::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
561            -3
562        );
563        assert_eq!(
564            db.query_row("SELECT -3.5::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
565            -4
566        );
567        assert_eq!(
568            db.query_row("SELECT -4.5::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
569            -5
570        );
571
572        assert_eq!(
573            db.query_row("SELECT -0.1::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
574            0
575        );
576        assert_eq!(
577            db.query_row("SELECT -0.4::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
578            0
579        );
580        assert_eq!(
581            db.query_row("SELECT -0.6::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
582            -1
583        );
584        assert_eq!(
585            db.query_row("SELECT -0.9::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
586            -1
587        );
588
589        assert_eq!(
590            db.query_row("SELECT 999.4::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
591            999
592        );
593        assert_eq!(
594            db.query_row("SELECT 999.5::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
595            1000
596        );
597        assert_eq!(
598            db.query_row("SELECT 999.6::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
599            1000
600        );
601
602        assert_eq!(
603            db.query_row("SELECT 123456.49::DECIMAL(18,3)", [], |row| row.get::<_, i64>(0))?,
604            123456
605        );
606        assert_eq!(
607            db.query_row("SELECT 123456.50::DECIMAL(18,3)", [], |row| row.get::<_, i64>(0))?,
608            123457
609        );
610        assert_eq!(
611            db.query_row("SELECT 123456.51::DECIMAL(18,3)", [], |row| row.get::<_, i64>(0))?,
612            123457
613        );
614
615        assert_eq!(
616            db.query_row("SELECT 0.49::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
617            0
618        );
619        assert_eq!(
620            db.query_row("SELECT 0.50::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
621            1
622        );
623        assert_eq!(
624            db.query_row("SELECT 0.51::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
625            1
626        );
627        assert_eq!(
628            db.query_row("SELECT -0.49::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
629            0
630        );
631        assert_eq!(
632            db.query_row("SELECT -0.50::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
633            -1
634        );
635        assert_eq!(
636            db.query_row("SELECT -0.51::DECIMAL(10,2)", [], |row| row.get::<_, i32>(0))?,
637            -1
638        );
639
640        assert_eq!(
641            db.query_row("SELECT 0.50::DECIMAL(38,2)", [], |row| row.get::<_, i32>(0))?,
642            1
643        );
644        assert_eq!(
645            db.query_row("SELECT -0.50::DECIMAL(38,2)", [], |row| row.get::<_, i32>(0))?,
646            -1
647        );
648        assert_eq!(
649            db.query_row("SELECT 0.500000000000000000000000000000::DECIMAL(38,30)", [], |row| row
650                .get::<_, i32>(0))?,
651            1
652        );
653        assert_eq!(
654            db.query_row("SELECT -0.500000000000000000000000000000::DECIMAL(38,30)", [], |row| {
655                row.get::<_, i32>(0)
656            })?,
657            -1
658        );
659
660        assert_eq!(
661            db.query_row("SELECT 126.4::DECIMAL(5,1)", [], |row| row.get::<_, i8>(0))?,
662            126
663        );
664        assert_eq!(
665            db.query_row("SELECT 126.6::DECIMAL(5,1)", [], |row| row.get::<_, i8>(0))?,
666            127
667        );
668
669        let err = db
670            .query_row("SELECT 999::DECIMAL(10,0)", [], |row| row.get::<_, i8>(0))
671            .unwrap_err();
672        match err {
673            Error::IntegralValueOutOfRange(_, _) => {} // Expected
674            _ => panic!("Expected IntegralValueOutOfRange error, got: {err}"),
675        }
676
677        Ok(())
678    }
679}