diesel/mysql/
value.rs

1use super::types::date_and_time::MysqlTime;
2use super::MysqlType;
3use crate::deserialize;
4use std::error::Error;
5
6/// Raw mysql value as received from the database
7#[derive(Clone, Debug)]
8pub struct MysqlValue<'a> {
9    raw: &'a [u8],
10    tpe: MysqlType,
11}
12
13impl<'a> MysqlValue<'a> {
14    /// Create a new instance of [MysqlValue] based on a byte buffer
15    /// and information about the type of the value represented by the
16    /// given buffer
17    #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
18    pub fn new(raw: &'a [u8], tpe: MysqlType) -> Self {
19        Self::new_internal(raw, tpe)
20    }
21
22    pub(in crate::mysql) fn new_internal(raw: &'a [u8], tpe: MysqlType) -> Self {
23        Self { raw, tpe }
24    }
25
26    /// Get the underlying raw byte representation
27    pub fn as_bytes(&self) -> &[u8] {
28        self.raw
29    }
30
31    /// Get the mysql type of the current value
32    pub fn value_type(&self) -> MysqlType {
33        self.tpe
34    }
35
36    /// Checks that the type code is valid, and interprets the data as a
37    /// `MysqlTime` pointer
38    // We use `ptr.read_unaligned()` to read the potential unaligned ptr,
39    // so clippy is clearly wrong here
40    // https://github.com/rust-lang/rust-clippy/issues/2881
41    #[allow(dead_code, clippy::cast_ptr_alignment)]
42    #[allow(unsafe_code)] // pointer cast
43    pub(crate) fn time_value(&self) -> deserialize::Result<MysqlTime> {
44        match self.tpe {
45            MysqlType::Time | MysqlType::Date | MysqlType::DateTime | MysqlType::Timestamp => {
46                self.too_short_buffer(std::mem::size_of::<MysqlTime>(), "Timestamp")?;
47                let ptr = self.raw.as_ptr() as *const MysqlTime;
48                let result = unsafe { ptr.read_unaligned() };
49                if result.neg {
50                    Err("Negative dates/times are not yet supported".into())
51                } else {
52                    Ok(result)
53                }
54            }
55            _ => Err(self.invalid_type_code("timestamp")),
56        }
57    }
58
59    /// Returns the numeric representation of this value, based on the type code.
60    /// Returns an error if the type code is not numeric.
61    pub(crate) fn numeric_value(&self) -> deserialize::Result<NumericRepresentation<'_>> {
62        Ok(match self.tpe {
63            MysqlType::UnsignedTiny | MysqlType::Tiny => {
64                NumericRepresentation::Tiny(self.raw[0].try_into()?)
65            }
66            MysqlType::UnsignedShort | MysqlType::Short => {
67                self.too_short_buffer(2, "Short")?;
68                NumericRepresentation::Small(i16::from_ne_bytes((&self.raw[..2]).try_into()?))
69            }
70            MysqlType::UnsignedLong | MysqlType::Long => {
71                self.too_short_buffer(4, "Long")?;
72                NumericRepresentation::Medium(i32::from_ne_bytes((&self.raw[..4]).try_into()?))
73            }
74            MysqlType::UnsignedLongLong | MysqlType::LongLong => {
75                self.too_short_buffer(8, "LongLong")?;
76                NumericRepresentation::Big(i64::from_ne_bytes(self.raw.try_into()?))
77            }
78            MysqlType::Float => {
79                self.too_short_buffer(4, "Float")?;
80                NumericRepresentation::Float(f32::from_ne_bytes(self.raw.try_into()?))
81            }
82            MysqlType::Double => {
83                self.too_short_buffer(8, "Double")?;
84                NumericRepresentation::Double(f64::from_ne_bytes(self.raw.try_into()?))
85            }
86
87            MysqlType::Numeric => NumericRepresentation::Decimal(self.raw),
88            _ => return Err(self.invalid_type_code("number")),
89        })
90    }
91
92    fn invalid_type_code(&self, expected: &str) -> Box<dyn Error + Send + Sync> {
93        format!(
94            "Invalid representation received for {}: {:?}",
95            expected, self.tpe
96        )
97        .into()
98    }
99
100    fn too_short_buffer(&self, expected: usize, tpe: &'static str) -> deserialize::Result<()> {
101        if self.raw.len() < expected {
102            Err(format!(
103                "Received a buffer with an invalid size while trying \
104             to read a {tpe} value: Expected at least {expected} bytes \
105             but got {}",
106                self.raw.len()
107            )
108            .into())
109        } else {
110            Ok(())
111        }
112    }
113}
114
115/// Represents all possible forms MySQL transmits integers
116#[derive(Debug, Clone, Copy)]
117#[non_exhaustive]
118pub enum NumericRepresentation<'a> {
119    /// Corresponds to `MYSQL_TYPE_TINY`
120    Tiny(i8),
121    /// Corresponds to `MYSQL_TYPE_SHORT`
122    Small(i16),
123    /// Corresponds to `MYSQL_TYPE_INT24` and `MYSQL_TYPE_LONG`
124    Medium(i32),
125    /// Corresponds to `MYSQL_TYPE_LONGLONG`
126    Big(i64),
127    /// Corresponds to `MYSQL_TYPE_FLOAT`
128    Float(f32),
129    /// Corresponds to `MYSQL_TYPE_DOUBLE`
130    Double(f64),
131    /// Corresponds to `MYSQL_TYPE_DECIMAL` and `MYSQL_TYPE_NEWDECIMAL`
132    Decimal(&'a [u8]),
133}
134
135#[test]
136fn invalid_reads() {
137    assert!(MysqlValue::new_internal(&[1], MysqlType::Timestamp)
138        .time_value()
139        .is_err());
140
141    assert!(MysqlValue::new_internal(&[1, 2], MysqlType::Long)
142        .numeric_value()
143        .is_err());
144
145    assert!(MysqlValue::new_internal(&[1, 2, 3, 4], MysqlType::LongLong)
146        .numeric_value()
147        .is_err());
148
149    assert!(MysqlValue::new_internal(&[1], MysqlType::Short)
150        .numeric_value()
151        .is_err());
152
153    assert!(MysqlValue::new_internal(&[1, 2, 3, 4], MysqlType::Double)
154        .numeric_value()
155        .is_err());
156
157    assert!(MysqlValue::new_internal(&[1, 2], MysqlType::Float)
158        .numeric_value()
159        .is_err());
160
161    assert!(MysqlValue::new_internal(&[1], MysqlType::Tiny)
162        .numeric_value()
163        .is_ok());
164}