diesel/mysql/types/
primitives.rs

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