Skip to main content

diesel/mysql_like/types/
primitives.rs

1use crate::Queryable;
2use crate::deserialize::FromSqlRef;
3use crate::deserialize::{self, FromSql};
4use crate::mysql_like::MysqlLikeBackend;
5use crate::mysql_like::{MysqlValue, NumericRepresentation};
6use crate::result::Error::DeserializationError;
7use crate::sql_types::{BigInt, Binary, Double, Float, Integer, SmallInt, Text};
8use core::error::Error;
9use core::str::{self, FromStr};
10
11pub(super) fn decimal_to_integer<T>(bytes: &[u8]) -> deserialize::Result<T>
12where
13    T: FromStr,
14    T::Err: Error + Send + Sync + 'static,
15{
16    let string = str::from_utf8(bytes)?;
17    let mut split = string.split('.');
18    let integer_portion = split.next().unwrap_or_default();
19    let _decimal_portion = split.next().unwrap_or_default();
20    if split.next().is_some() {
21        Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Invalid decimal format: {0:?}",
                string))
    })format!("Invalid decimal format: {string:?}").into())
22    } else {
23        Ok(integer_portion.parse()?)
24    }
25}
26
27pub(super) fn overflow() -> Box<dyn Error + Send + Sync> {
28    Box::new(DeserializationError(
29        "Numeric overflow/underflow occurred".into(),
30    ))
31}
32
33/// Converts between the integer widths MySQL transmits, reporting an error
34/// rather than wrapping when the value does not fit the target.
35pub(super) fn narrow<T, U: TryFrom<T>>(value: T) -> deserialize::Result<U> {
36    U::try_from(value).map_err(|_| overflow())
37}
38
39#[allow(clippy::cast_possible_truncation)] // that's what we want here
40pub(super) fn f32_to_i64(f: f32) -> deserialize::Result<i64> {
41    if f <= i64::MAX as f32 && f >= i64::MIN as f32 {
42        Ok(f.trunc() as i64)
43    } else {
44        Err(overflow())
45    }
46}
47
48#[allow(clippy::cast_possible_truncation)] // that's what we want here
49pub(super) fn f64_to_i64(f: f64) -> deserialize::Result<i64> {
50    if f <= i64::MAX as f64 && f >= i64::MIN as f64 {
51        Ok(f.trunc() as i64)
52    } else {
53        Err(overflow())
54    }
55}
56
57impl<DB: MysqlLikeBackend> FromSql<SmallInt, DB> for i16 {
58    fn from_sql(value: MysqlValue<'_>) -> deserialize::Result<Self> {
59        match value.numeric_value()? {
60            NumericRepresentation::Tiny(x) => Ok(x.into()),
61            NumericRepresentation::UnsignedTiny(x) => Ok(x.into()),
62            NumericRepresentation::Small(x) => Ok(x),
63            NumericRepresentation::UnsignedSmall(x) => narrow(x),
64            NumericRepresentation::Medium(x) => narrow(x),
65            NumericRepresentation::UnsignedMedium(x) => narrow(x),
66            NumericRepresentation::Big(x) => narrow(x),
67            NumericRepresentation::UnsignedBig(x) => narrow(x),
68            NumericRepresentation::Float(x) => narrow(f32_to_i64(x)?),
69            NumericRepresentation::Double(x) => narrow(f64_to_i64(x)?),
70            NumericRepresentation::Decimal(bytes) => decimal_to_integer(bytes),
71        }
72    }
73}
74
75impl<DB: MysqlLikeBackend> FromSql<Integer, DB> for i32 {
76    fn from_sql(value: MysqlValue<'_>) -> deserialize::Result<Self> {
77        match value.numeric_value()? {
78            NumericRepresentation::Tiny(x) => Ok(x.into()),
79            NumericRepresentation::UnsignedTiny(x) => Ok(x.into()),
80            NumericRepresentation::Small(x) => Ok(x.into()),
81            NumericRepresentation::UnsignedSmall(x) => Ok(x.into()),
82            NumericRepresentation::Medium(x) => Ok(x),
83            NumericRepresentation::UnsignedMedium(x) => narrow(x),
84            NumericRepresentation::Big(x) => narrow(x),
85            NumericRepresentation::UnsignedBig(x) => narrow(x),
86            NumericRepresentation::Float(x) => narrow(f32_to_i64(x)?),
87            NumericRepresentation::Double(x) => narrow(f64_to_i64(x)?),
88            NumericRepresentation::Decimal(bytes) => decimal_to_integer(bytes),
89        }
90    }
91}
92
93impl<DB: MysqlLikeBackend> FromSql<BigInt, DB> for i64 {
94    fn from_sql(value: MysqlValue<'_>) -> deserialize::Result<Self> {
95        match value.numeric_value()? {
96            NumericRepresentation::Tiny(x) => Ok(x.into()),
97            NumericRepresentation::UnsignedTiny(x) => Ok(x.into()),
98            NumericRepresentation::Small(x) => Ok(x.into()),
99            NumericRepresentation::UnsignedSmall(x) => Ok(x.into()),
100            NumericRepresentation::Medium(x) => Ok(x.into()),
101            NumericRepresentation::UnsignedMedium(x) => Ok(x.into()),
102            NumericRepresentation::Big(x) => Ok(x),
103            NumericRepresentation::UnsignedBig(x) => narrow(x),
104            NumericRepresentation::Float(x) => f32_to_i64(x),
105            NumericRepresentation::Double(x) => f64_to_i64(x),
106            NumericRepresentation::Decimal(bytes) => decimal_to_integer(bytes),
107        }
108    }
109}
110
111impl<DB: MysqlLikeBackend> FromSql<Float, DB> for f32 {
112    fn from_sql(value: MysqlValue<'_>) -> deserialize::Result<Self> {
113        // Precision loss beyond the mantissa is intended for a float column.
114        match value.numeric_value()? {
115            NumericRepresentation::Tiny(x) => Ok(x.into()),
116            NumericRepresentation::UnsignedTiny(x) => Ok(x.into()),
117            NumericRepresentation::Small(x) => Ok(x.into()),
118            NumericRepresentation::UnsignedSmall(x) => Ok(x.into()),
119            NumericRepresentation::Medium(x) => Ok(x as Self),
120            NumericRepresentation::UnsignedMedium(x) => Ok(x as Self),
121            NumericRepresentation::Big(x) => Ok(x as Self),
122            NumericRepresentation::UnsignedBig(x) => Ok(x as Self),
123            NumericRepresentation::Float(x) => Ok(x),
124            // there is currently no way to do this in a better way
125            #[allow(clippy::cast_possible_truncation)]
126            NumericRepresentation::Double(x) => Ok(x as Self),
127            NumericRepresentation::Decimal(bytes) => Ok(str::from_utf8(bytes)?.parse()?),
128        }
129    }
130}
131
132impl<DB: MysqlLikeBackend> FromSql<Double, DB> for f64 {
133    fn from_sql(value: MysqlValue<'_>) -> deserialize::Result<Self> {
134        // Precision loss beyond the mantissa is intended for a double column.
135        match value.numeric_value()? {
136            NumericRepresentation::Tiny(x) => Ok(x.into()),
137            NumericRepresentation::UnsignedTiny(x) => Ok(x.into()),
138            NumericRepresentation::Small(x) => Ok(x.into()),
139            NumericRepresentation::UnsignedSmall(x) => Ok(x.into()),
140            NumericRepresentation::Medium(x) => Ok(x.into()),
141            NumericRepresentation::UnsignedMedium(x) => Ok(x.into()),
142            NumericRepresentation::Big(x) => Ok(x as Self),
143            NumericRepresentation::UnsignedBig(x) => Ok(x as Self),
144            NumericRepresentation::Float(x) => Ok(x.into()),
145            NumericRepresentation::Double(x) => Ok(x),
146            NumericRepresentation::Decimal(bytes) => Ok(str::from_utf8(bytes)?.parse()?),
147        }
148    }
149}
150
151/// The returned pointer is *only* valid for the lifetime to the argument of
152/// `from_sql`. This impl is intended for uses where you want to write a new
153/// impl in terms of `String`, but don't want to allocate. We have to return a
154/// raw pointer instead of a reference with a lifetime due to the structure of
155/// `FromSql`
156impl<DB: MysqlLikeBackend> FromSql<Text, DB> for *const str {
157    fn from_sql(value: MysqlValue<'_>) -> deserialize::Result<Self> {
158        let string = str::from_utf8(value.as_bytes())?;
159        Ok(string as *const str)
160    }
161}
162
163impl<'a, DB: MysqlLikeBackend> FromSqlRef<'a, Text, DB> for &'a str {
164    fn from_sql(bytes: &'a mut MysqlValue<'_>) -> deserialize::Result<Self> {
165        let string = str::from_utf8(bytes.as_bytes())?;
166        Ok(string)
167    }
168}
169
170impl<DB: MysqlLikeBackend> Queryable<Text, DB> for *const str {
171    type Row = Self;
172
173    fn build(row: Self::Row) -> deserialize::Result<Self> {
174        Ok(row)
175    }
176}
177
178/// The returned pointer is *only* valid for the lifetime to the argument of
179/// `from_sql`. This impl is intended for uses where you want to write a new
180/// impl in terms of `Vec<u8>`, but don't want to allocate. We have to return a
181/// raw pointer instead of a reference with a lifetime due to the structure of
182/// `FromSql`
183impl<DB: MysqlLikeBackend> FromSql<Binary, DB> for *const [u8] {
184    fn from_sql(value: MysqlValue<'_>) -> deserialize::Result<Self> {
185        Ok(value.as_bytes() as *const [u8])
186    }
187}
188
189impl<'a, DB: MysqlLikeBackend> FromSqlRef<'a, Binary, DB> for &'a [u8] {
190    fn from_sql(bytes: &'a mut MysqlValue<'_>) -> deserialize::Result<Self> {
191        Ok(bytes.as_bytes())
192    }
193}
194
195impl<DB: MysqlLikeBackend> Queryable<Binary, DB> for *const [u8] {
196    type Row = Self;
197
198    fn build(row: Self::Row) -> deserialize::Result<Self> {
199        Ok(row)
200    }
201}