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) -> Self {
        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 && self.tpe != MysqlType::Time {
96                    Err("Negative date, datetime, and timestamp values are not 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::Tiny => NumericRepresentation::Tiny(self.read()?),
110            MysqlType::UnsignedTiny => NumericRepresentation::UnsignedTiny(self.read()?),
111            MysqlType::Short => NumericRepresentation::Small(self.read()?),
112            MysqlType::UnsignedShort => NumericRepresentation::UnsignedSmall(self.read()?),
113            MysqlType::Long => NumericRepresentation::Medium(self.read()?),
114            MysqlType::UnsignedLong => NumericRepresentation::UnsignedMedium(self.read()?),
115            MysqlType::LongLong => NumericRepresentation::Big(self.read()?),
116            MysqlType::UnsignedLongLong => NumericRepresentation::UnsignedBig(self.read()?),
117            MysqlType::Float => NumericRepresentation::Float(self.read()?),
118            MysqlType::Double => NumericRepresentation::Double(self.read()?),
119            MysqlType::Numeric => NumericRepresentation::Decimal(self.raw),
120            _ => return Err(self.invalid_type_code("number")),
121        })
122    }
123
124    fn invalid_type_code(&self, expected: &str) -> Box<dyn Error + Send + Sync> {
125        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Invalid representation received for {0}: {1:?}",
                expected, self.tpe))
    })format!(
126            "Invalid representation received for {}: {:?}",
127            expected, self.tpe
128        )
129        .into()
130    }
131
132    fn too_short_buffer(&self, expected: usize, tpe: &'static str) -> deserialize::Result<()> {
133        if self.raw.len() < expected {
134            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!(
135                "Received a buffer with an invalid size while trying \
136             to read a {tpe} value: Expected at least {expected} bytes \
137             but got {}",
138                self.raw.len()
139            )
140            .into())
141        } else {
142            Ok(())
143        }
144    }
145
146    /// Reads a `T` from the start of the buffer, in the byte order the server sent it in.
147    fn read<T: FromNeBytes>(&self) -> deserialize::Result<T> {
148        self.too_short_buffer(core::mem::size_of::<T>(), core::any::type_name::<T>())?;
149        Ok(T::from_ne_prefix(self.raw))
150    }
151}
152
153/// The numeric primitives the server transmits as native endian bytes.
154///
155/// Private, so that [`MysqlValue::read`] cannot be reached with any other type.
156trait FromNeBytes: Sized {
157    /// Reads `Self` from the first `size_of::<Self>()` bytes, panicking on a shorter buffer.
158    fn from_ne_prefix(buffer: &[u8]) -> Self;
159}
160
161macro_rules! impl_from_ne_bytes {
162    ($($t:ty),+ $(,)?) => {
163        $(
164            impl FromNeBytes for $t {
165                fn from_ne_prefix(buffer: &[u8]) -> Self {
166                    let mut bytes = [0_u8; core::mem::size_of::<Self>()];
167                    bytes.copy_from_slice(&buffer[..core::mem::size_of::<Self>()]);
168                    Self::from_ne_bytes(bytes)
169                }
170            }
171        )+
172    };
173}
174
175impl FromNeBytes for i8 {
    fn from_ne_prefix(buffer: &[u8]) -> Self {
        let mut bytes = [0_u8; core::mem::size_of::<Self>()];
        bytes.copy_from_slice(&buffer[..core::mem::size_of::<Self>()]);
        Self::from_ne_bytes(bytes)
    }
}
impl FromNeBytes for u8 {
    fn from_ne_prefix(buffer: &[u8]) -> Self {
        let mut bytes = [0_u8; core::mem::size_of::<Self>()];
        bytes.copy_from_slice(&buffer[..core::mem::size_of::<Self>()]);
        Self::from_ne_bytes(bytes)
    }
}
impl FromNeBytes for i16 {
    fn from_ne_prefix(buffer: &[u8]) -> Self {
        let mut bytes = [0_u8; core::mem::size_of::<Self>()];
        bytes.copy_from_slice(&buffer[..core::mem::size_of::<Self>()]);
        Self::from_ne_bytes(bytes)
    }
}
impl FromNeBytes for u16 {
    fn from_ne_prefix(buffer: &[u8]) -> Self {
        let mut bytes = [0_u8; core::mem::size_of::<Self>()];
        bytes.copy_from_slice(&buffer[..core::mem::size_of::<Self>()]);
        Self::from_ne_bytes(bytes)
    }
}
impl FromNeBytes for i32 {
    fn from_ne_prefix(buffer: &[u8]) -> Self {
        let mut bytes = [0_u8; core::mem::size_of::<Self>()];
        bytes.copy_from_slice(&buffer[..core::mem::size_of::<Self>()]);
        Self::from_ne_bytes(bytes)
    }
}
impl FromNeBytes for u32 {
    fn from_ne_prefix(buffer: &[u8]) -> Self {
        let mut bytes = [0_u8; core::mem::size_of::<Self>()];
        bytes.copy_from_slice(&buffer[..core::mem::size_of::<Self>()]);
        Self::from_ne_bytes(bytes)
    }
}
impl FromNeBytes for i64 {
    fn from_ne_prefix(buffer: &[u8]) -> Self {
        let mut bytes = [0_u8; core::mem::size_of::<Self>()];
        bytes.copy_from_slice(&buffer[..core::mem::size_of::<Self>()]);
        Self::from_ne_bytes(bytes)
    }
}
impl FromNeBytes for u64 {
    fn from_ne_prefix(buffer: &[u8]) -> Self {
        let mut bytes = [0_u8; core::mem::size_of::<Self>()];
        bytes.copy_from_slice(&buffer[..core::mem::size_of::<Self>()]);
        Self::from_ne_bytes(bytes)
    }
}
impl FromNeBytes for f32 {
    fn from_ne_prefix(buffer: &[u8]) -> Self {
        let mut bytes = [0_u8; core::mem::size_of::<Self>()];
        bytes.copy_from_slice(&buffer[..core::mem::size_of::<Self>()]);
        Self::from_ne_bytes(bytes)
    }
}
impl FromNeBytes for f64 {
    fn from_ne_prefix(buffer: &[u8]) -> Self {
        let mut bytes = [0_u8; core::mem::size_of::<Self>()];
        bytes.copy_from_slice(&buffer[..core::mem::size_of::<Self>()]);
        Self::from_ne_bytes(bytes)
    }
}impl_from_ne_bytes!(i8, u8, i16, u16, i32, u32, i64, u64, f32, f64);
176
177/// Represents all possible forms MySQL transmits integers
178#[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 {
            Self::Tiny(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Tiny",
                    &__self_0),
            Self::UnsignedTiny(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "UnsignedTiny", &__self_0),
            Self::Small(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Small",
                    &__self_0),
            Self::UnsignedSmall(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "UnsignedSmall", &__self_0),
            Self::Medium(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Medium",
                    &__self_0),
            Self::UnsignedMedium(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "UnsignedMedium", &__self_0),
            Self::Big(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Big",
                    &__self_0),
            Self::UnsignedBig(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "UnsignedBig", &__self_0),
            Self::Float(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Float",
                    &__self_0),
            Self::Double(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Double",
                    &__self_0),
            Self::Decimal(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Decimal", &__self_0),
        }
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl<'a> ::core::clone::TrivialClone for NumericRepresentation<'a> { }
#[automatically_derived]
impl<'a> ::core::clone::Clone for NumericRepresentation<'a> {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<i8>;
        let _: ::core::clone::AssertParamIsClone<u8>;
        let _: ::core::clone::AssertParamIsClone<i16>;
        let _: ::core::clone::AssertParamIsClone<u16>;
        let _: ::core::clone::AssertParamIsClone<i32>;
        let _: ::core::clone::AssertParamIsClone<u32>;
        let _: ::core::clone::AssertParamIsClone<i64>;
        let _: ::core::clone::AssertParamIsClone<u64>;
        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)]
179#[non_exhaustive]
180pub enum NumericRepresentation<'a> {
181    /// Corresponds to `MYSQL_TYPE_TINY`
182    Tiny(i8),
183    /// Corresponds to `MYSQL_TYPE_TINY` with the `UNSIGNED` flag set
184    UnsignedTiny(u8),
185    /// Corresponds to `MYSQL_TYPE_SHORT`
186    Small(i16),
187    /// Corresponds to `MYSQL_TYPE_SHORT` with the `UNSIGNED` flag set
188    UnsignedSmall(u16),
189    /// Corresponds to `MYSQL_TYPE_INT24` and `MYSQL_TYPE_LONG`
190    Medium(i32),
191    /// Corresponds to `MYSQL_TYPE_INT24` and `MYSQL_TYPE_LONG` with the `UNSIGNED` flag set
192    UnsignedMedium(u32),
193    /// Corresponds to `MYSQL_TYPE_LONGLONG`
194    Big(i64),
195    /// Corresponds to `MYSQL_TYPE_LONGLONG` with the `UNSIGNED` flag set
196    UnsignedBig(u64),
197    /// Corresponds to `MYSQL_TYPE_FLOAT`
198    Float(f32),
199    /// Corresponds to `MYSQL_TYPE_DOUBLE`
200    Double(f64),
201    /// Corresponds to `MYSQL_TYPE_DECIMAL` and `MYSQL_TYPE_NEWDECIMAL`
202    Decimal(&'a [u8]),
203}
204
205#[test]
206#[allow(unsafe_code, reason = "Test code")]
207fn invalid_reads() {
208    use crate::data_types::MysqlTimestampType;
209
210    assert!(
211        MysqlValue::new_internal(&[1], MysqlType::Timestamp)
212            .time_value()
213            .is_err()
214    );
215    let v = MysqlTime {
216        year: 2025,
217        month: 9,
218        day: 15,
219        hour: 22,
220        minute: 3,
221        second: 10,
222        second_part: 0,
223        neg: false,
224        time_type: MysqlTimestampType::MYSQL_TIMESTAMP_DATETIME,
225        time_zone_displacement: 0,
226    };
227    let mut bytes = [0; std::mem::size_of::<MysqlTime>()];
228    unsafe {
229        // SAFETY: Test code
230        // also the size matches and we want to get raw bytes
231        std::ptr::copy(
232            &v as *const MysqlTime as *const u8,
233            bytes.as_mut_ptr(),
234            bytes.len(),
235        );
236    }
237    let offset = std::mem::offset_of!(MysqlTime, neg);
238    bytes[offset] = 42;
239    assert!(
240        MysqlValue::new_internal(&bytes, MysqlType::Timestamp)
241            .time_value()
242            .is_err()
243    );
244
245    let negative_time = MysqlTime {
246        year: 0,
247        month: 0,
248        day: 0,
249        hour: 10,
250        minute: 11,
251        second: 12,
252        second_part: 0,
253        neg: true,
254        time_type: MysqlTimestampType::MYSQL_TIMESTAMP_TIME,
255        time_zone_displacement: 0,
256    };
257    let bytes = negative_time.serialize();
258    assert!(
259        MysqlValue::new_internal(&bytes, MysqlType::Time)
260            .time_value()
261            .unwrap()
262            .neg
263    );
264    assert!(
265        MysqlValue::new_internal(&bytes, MysqlType::Date)
266            .time_value()
267            .is_err()
268    );
269    assert!(
270        MysqlValue::new_internal(&bytes, MysqlType::DateTime)
271            .time_value()
272            .is_err()
273    );
274    assert!(
275        MysqlValue::new_internal(&bytes, MysqlType::Timestamp)
276            .time_value()
277            .is_err()
278    );
279
280    assert!(
281        MysqlValue::new_internal(&[1, 2], MysqlType::Long)
282            .numeric_value()
283            .is_err()
284    );
285
286    assert!(
287        MysqlValue::new_internal(&[1, 2, 3, 4], MysqlType::LongLong)
288            .numeric_value()
289            .is_err()
290    );
291
292    assert!(
293        MysqlValue::new_internal(&[1], MysqlType::Short)
294            .numeric_value()
295            .is_err()
296    );
297
298    assert!(
299        MysqlValue::new_internal(&[1, 2, 3, 4], MysqlType::Double)
300            .numeric_value()
301            .is_err()
302    );
303
304    assert!(
305        MysqlValue::new_internal(&[1, 2], MysqlType::Float)
306            .numeric_value()
307            .is_err()
308    );
309
310    assert!(
311        MysqlValue::new_internal(&[1], MysqlType::Tiny)
312            .numeric_value()
313            .is_ok()
314    );
315
316    assert!(
317        MysqlValue::new_internal(&[], MysqlType::Tiny)
318            .numeric_value()
319            .is_err()
320    );
321
322    assert!(
323        MysqlValue::new_internal(&[], MysqlType::UnsignedTiny)
324            .numeric_value()
325            .is_err()
326    );
327
328    // Every arm reads the leading bytes it needs and ignores any trailing ones.
329    assert!(
330        MysqlValue::new_internal(&[1, 2, 3], MysqlType::UnsignedShort)
331            .numeric_value()
332            .is_ok()
333    );
334
335    assert!(
336        MysqlValue::new_internal(&[1, 2, 3, 4, 5], MysqlType::UnsignedLong)
337            .numeric_value()
338            .is_ok()
339    );
340
341    assert!(
342        MysqlValue::new_internal(&[1, 2, 3, 4, 5, 6, 7, 8, 9], MysqlType::UnsignedLongLong)
343            .numeric_value()
344            .is_ok()
345    );
346}
347
348#[test]
349fn numeric_value_keeps_signedness() {
350    use super::NumericRepresentation as N;
351
352    assert!(matches!(
353        MysqlValue::new_internal(&[0xFF], MysqlType::Tiny).numeric_value(),
354        Ok(N::Tiny(-1))
355    ));
356    assert!(matches!(
357        MysqlValue::new_internal(&[200], MysqlType::UnsignedTiny).numeric_value(),
358        Ok(N::UnsignedTiny(200))
359    ));
360    assert!(matches!(
361        MysqlValue::new_internal(&(-1i16).to_ne_bytes(), MysqlType::Short).numeric_value(),
362        Ok(N::Small(-1))
363    ));
364    assert!(matches!(
365        MysqlValue::new_internal(&40000u16.to_ne_bytes(), MysqlType::UnsignedShort).numeric_value(),
366        Ok(N::UnsignedSmall(40000))
367    ));
368    assert!(matches!(
369        MysqlValue::new_internal(&(-1i32).to_ne_bytes(), MysqlType::Long).numeric_value(),
370        Ok(N::Medium(-1))
371    ));
372    assert!(matches!(
373        MysqlValue::new_internal(&u32::MAX.to_ne_bytes(), MysqlType::UnsignedLong).numeric_value(),
374        Ok(N::UnsignedMedium(u32::MAX))
375    ));
376    assert!(matches!(
377        MysqlValue::new_internal(&(-1i64).to_ne_bytes(), MysqlType::LongLong).numeric_value(),
378        Ok(N::Big(-1))
379    ));
380    assert!(matches!(
381        MysqlValue::new_internal(&u64::MAX.to_ne_bytes(), MysqlType::UnsignedLongLong)
382            .numeric_value(),
383        Ok(N::UnsignedBig(u64::MAX))
384    ));
385}