Skip to main content

diesel/mysql_like/
value.rs

1use super::MysqlType;
2use super::types::date_and_time::MysqlTime;
3
4use crate::deserialize;
5use core::error::Error;
6use core::mem::MaybeUninit;
7
8/// Raw mysql value as received from the database
9#[derive(#[automatically_derived]
impl<'a> ::core::clone::Clone for MysqlValue<'a> {
    #[inline]
    fn clone(&self) -> MysqlValue<'a> {
        MysqlValue {
            raw: ::core::clone::Clone::clone(&self.raw),
            tpe: ::core::clone::Clone::clone(&self.tpe),
        }
    }
}Clone, #[automatically_derived]
impl<'a> ::core::fmt::Debug for MysqlValue<'a> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "MysqlValue",
            "raw", &self.raw, "tpe", &&self.tpe)
    }
}Debug)]
10pub struct MysqlValue<'a> {
11    raw: &'a [u8],
12    tpe: MysqlType,
13}
14
15impl<'a> MysqlValue<'a> {
16    /// Create a new instance of [MysqlValue] based on a byte buffer
17    /// and information about the type of the value represented by the
18    /// given buffer
19    #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
20    pub fn new(raw: &'a [u8], tpe: MysqlType) -> Self {
21        Self::new_internal(raw, tpe)
22    }
23
24    #[cfg(any(
25        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
26        feature = "mysql",
27        feature = "mariadb"
28    ))]
29    pub(crate) fn new_internal(raw: &'a [u8], tpe: MysqlType) -> Self {
30        Self { raw, tpe }
31    }
32
33    /// Get the underlying raw byte representation
34    pub fn as_bytes(&self) -> &'a [u8] {
35        self.raw
36    }
37
38    /// Get the mysql type of the current value
39    pub fn value_type(&self) -> MysqlType {
40        self.tpe
41    }
42
43    /// Checks that the type code is valid, and interprets the data as a
44    /// `MysqlTime` pointer
45    // We use `ptr::copy` to read the actual data
46    // and copy it over to the returned `MysqlTime` instance
47    #[allow(unsafe_code)] // MaybeUninit + ptr copy
48    pub(crate) fn time_value(&self) -> deserialize::Result<MysqlTime> {
49        match self.tpe {
50            MysqlType::Time | MysqlType::Date | MysqlType::DateTime | MysqlType::Timestamp => {
51                // we check for the size of the `MYSQL_TIME` type from `mysqlclient_sys` here as
52                // certain older libmysqlclient and newer libmariadb versions do not have all the
53                // same fields (and size) as the `MysqlTime` type from diesel. The later one is modeled after
54                // the type from newer libmysqlclient
55                self.too_short_buffer(
56                    #[cfg(feature = "mysql")]
57                    core::mem::size_of::<mysqlclient_sys::MYSQL_TIME>(),
58                    #[cfg(not(feature = "mysql"))]
59                    core::mem::size_of::<MysqlTime>(),
60                    "timestamp",
61                )?;
62                // To ensure we copy the right number of bytes we need to make sure to copy not more bytes than needed
63                // for `MysqlTime` and not more bytes than inside of the buffer
64                let len = core::cmp::min(core::mem::size_of::<MysqlTime>(), self.raw.len());
65                // Zero is a valid pattern for this type so we are fine with initializing all fields to zero
66                // If the provided byte buffer is too short we just use 0 as default value
67                let mut out = MaybeUninit::<MysqlTime>::zeroed();
68                // Make sure to check that the boolean is an actual bool value, so 0 or 1
69                // as anything else is UB in rust
70                let neg_offset = const { builtin # offset_of(MysqlTime, neg) }std::mem::offset_of!(MysqlTime, neg);
71                if neg_offset < self.raw.len()
72                    && self.raw[neg_offset] != 0
73                    && self.raw[neg_offset] != 1
74                {
75                    return Err(
76                        "Received invalid value for `neg` in the `MysqlTime` datastructure".into(),
77                    );
78                }
79                let result = unsafe {
80                    // SAFETY: We copy over the bytes from our raw buffer to the `MysqlTime` instance
81                    // This type is correctly aligned and we ensure that we do not copy more bytes than are there
82                    // We are also sure that these ptr do not overlap as they are completely different
83                    // instances
84                    core::ptr::copy_nonoverlapping(
85                        self.raw.as_ptr(),
86                        out.as_mut_ptr() as *mut u8,
87                        len,
88                    );
89                    // SAFETY: all zero is a valid pattern for this type
90                    // Otherwise any other bit pattern is also valid, beside
91                    // neg being something other than 0 or 1
92                    // We check for that above by looking at the byte before copying
93                    out.assume_init()
94                };
95                if result.neg {
96                    Err("Negative dates/times are not yet supported".into())
97                } else {
98                    Ok(result)
99                }
100            }
101            _ => Err(self.invalid_type_code("timestamp")),
102        }
103    }
104
105    /// Returns the numeric representation of this value, based on the type code.
106    /// Returns an error if the type code is not numeric.
107    pub(crate) fn numeric_value(&self) -> deserialize::Result<NumericRepresentation<'_>> {
108        Ok(match self.tpe {
109            MysqlType::UnsignedTiny | MysqlType::Tiny => {
110                NumericRepresentation::Tiny(self.raw[0].try_into()?)
111            }
112            MysqlType::UnsignedShort | MysqlType::Short => {
113                self.too_short_buffer(2, "Short")?;
114                NumericRepresentation::Small(i16::from_ne_bytes((&self.raw[..2]).try_into()?))
115            }
116            MysqlType::UnsignedLong | MysqlType::Long => {
117                self.too_short_buffer(4, "Long")?;
118                NumericRepresentation::Medium(i32::from_ne_bytes((&self.raw[..4]).try_into()?))
119            }
120            MysqlType::UnsignedLongLong | MysqlType::LongLong => {
121                self.too_short_buffer(8, "LongLong")?;
122                NumericRepresentation::Big(i64::from_ne_bytes(self.raw.try_into()?))
123            }
124            MysqlType::Float => {
125                self.too_short_buffer(4, "Float")?;
126                NumericRepresentation::Float(f32::from_ne_bytes(self.raw.try_into()?))
127            }
128            MysqlType::Double => {
129                self.too_short_buffer(8, "Double")?;
130                NumericRepresentation::Double(f64::from_ne_bytes(self.raw.try_into()?))
131            }
132
133            MysqlType::Numeric => NumericRepresentation::Decimal(self.raw),
134            _ => return Err(self.invalid_type_code("number")),
135        })
136    }
137
138    fn invalid_type_code(&self, expected: &str) -> Box<dyn Error + Send + Sync> {
139        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Invalid representation received for {0}: {1:?}",
                expected, self.tpe))
    })format!(
140            "Invalid representation received for {}: {:?}",
141            expected, self.tpe
142        )
143        .into()
144    }
145
146    fn too_short_buffer(&self, expected: usize, tpe: &'static str) -> deserialize::Result<()> {
147        if self.raw.len() < expected {
148            Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Received a buffer with an invalid size while trying to read a {1} value: Expected at least {2} bytes but got {0}",
                self.raw.len(), tpe, expected))
    })format!(
149                "Received a buffer with an invalid size while trying \
150             to read a {tpe} value: Expected at least {expected} bytes \
151             but got {}",
152                self.raw.len()
153            )
154            .into())
155        } else {
156            Ok(())
157        }
158    }
159}
160
161/// Represents all possible forms MySQL transmits integers
162#[derive(#[automatically_derived]
impl<'a> ::core::fmt::Debug for NumericRepresentation<'a> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            NumericRepresentation::Tiny(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Tiny",
                    &__self_0),
            NumericRepresentation::Small(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Small",
                    &__self_0),
            NumericRepresentation::Medium(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Medium",
                    &__self_0),
            NumericRepresentation::Big(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Big",
                    &__self_0),
            NumericRepresentation::Float(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Float",
                    &__self_0),
            NumericRepresentation::Double(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Double",
                    &__self_0),
            NumericRepresentation::Decimal(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Decimal", &__self_0),
        }
    }
}Debug, #[automatically_derived]
impl<'a> ::core::clone::Clone for NumericRepresentation<'a> {
    #[inline]
    fn clone(&self) -> NumericRepresentation<'a> {
        let _: ::core::clone::AssertParamIsClone<i8>;
        let _: ::core::clone::AssertParamIsClone<i16>;
        let _: ::core::clone::AssertParamIsClone<i32>;
        let _: ::core::clone::AssertParamIsClone<i64>;
        let _: ::core::clone::AssertParamIsClone<f32>;
        let _: ::core::clone::AssertParamIsClone<f64>;
        let _: ::core::clone::AssertParamIsClone<&'a [u8]>;
        *self
    }
}Clone, #[automatically_derived]
impl<'a> ::core::marker::Copy for NumericRepresentation<'a> { }Copy)]
163#[non_exhaustive]
164pub enum NumericRepresentation<'a> {
165    /// Corresponds to `MYSQL_TYPE_TINY`
166    Tiny(i8),
167    /// Corresponds to `MYSQL_TYPE_SHORT`
168    Small(i16),
169    /// Corresponds to `MYSQL_TYPE_INT24` and `MYSQL_TYPE_LONG`
170    Medium(i32),
171    /// Corresponds to `MYSQL_TYPE_LONGLONG`
172    Big(i64),
173    /// Corresponds to `MYSQL_TYPE_FLOAT`
174    Float(f32),
175    /// Corresponds to `MYSQL_TYPE_DOUBLE`
176    Double(f64),
177    /// Corresponds to `MYSQL_TYPE_DECIMAL` and `MYSQL_TYPE_NEWDECIMAL`
178    Decimal(&'a [u8]),
179}
180
181#[test]
182#[allow(unsafe_code, reason = "Test code")]
183fn invalid_reads() {
184    use crate::data_types::MysqlTimestampType;
185
186    assert!(
187        MysqlValue::new_internal(&[1], MysqlType::Timestamp)
188            .time_value()
189            .is_err()
190    );
191    let v = MysqlTime {
192        year: 2025,
193        month: 9,
194        day: 15,
195        hour: 22,
196        minute: 3,
197        second: 10,
198        second_part: 0,
199        neg: false,
200        time_type: MysqlTimestampType::MYSQL_TIMESTAMP_DATETIME,
201        time_zone_displacement: 0,
202    };
203    let mut bytes = [0; std::mem::size_of::<MysqlTime>()];
204    unsafe {
205        // SAFETY: Test code
206        // also the size matches and we want to get raw bytes
207        std::ptr::copy(
208            &v as *const MysqlTime as *const u8,
209            bytes.as_mut_ptr(),
210            bytes.len(),
211        );
212    }
213    let offset = std::mem::offset_of!(MysqlTime, neg);
214    bytes[offset] = 42;
215    assert!(
216        MysqlValue::new_internal(&bytes, MysqlType::Timestamp)
217            .time_value()
218            .is_err()
219    );
220
221    assert!(
222        MysqlValue::new_internal(&[1, 2], MysqlType::Long)
223            .numeric_value()
224            .is_err()
225    );
226
227    assert!(
228        MysqlValue::new_internal(&[1, 2, 3, 4], MysqlType::LongLong)
229            .numeric_value()
230            .is_err()
231    );
232
233    assert!(
234        MysqlValue::new_internal(&[1], MysqlType::Short)
235            .numeric_value()
236            .is_err()
237    );
238
239    assert!(
240        MysqlValue::new_internal(&[1, 2, 3, 4], MysqlType::Double)
241            .numeric_value()
242            .is_err()
243    );
244
245    assert!(
246        MysqlValue::new_internal(&[1, 2], MysqlType::Float)
247            .numeric_value()
248            .is_err()
249    );
250
251    assert!(
252        MysqlValue::new_internal(&[1], MysqlType::Tiny)
253            .numeric_value()
254            .is_ok()
255    );
256}