1use super::MysqlType;
2use super::types::date_and_time::MysqlTime;
34use crate::deserialize;
5use core::error::Error;
6use core::mem::MaybeUninit;
78/// 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}
1415impl<'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")]
20pub fn new(raw: &'a [u8], tpe: MysqlType) -> Self {
21Self::new_internal(raw, tpe)
22 }
2324#[cfg(any(
25 feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
26 feature = "mysql",
27 feature = "mariadb"
28))]
29pub(crate) fn new_internal(raw: &'a [u8], tpe: MysqlType) -> Self {
30Self { raw, tpe }
31 }
3233/// Get the underlying raw byte representation
34pub fn as_bytes(&self) -> &'a [u8] {
35self.raw
36 }
3738/// Get the mysql type of the current value
39pub fn value_type(&self) -> MysqlType {
40self.tpe
41 }
4243/// 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
48pub(crate) fn time_value(&self) -> deserialize::Result<MysqlTime> {
49match 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
55self.too_short_buffer(
56#[cfg(feature = "mysql")]
57core::mem::size_of::<mysqlclient_sys::MYSQL_TIME>(),
58#[cfg(not(feature = "mysql"))]
59core::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
64let 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
67let 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
70let neg_offset = const { builtin # offset_of(MysqlTime, neg) }std::mem::offset_of!(MysqlTime, neg);
71if neg_offset < self.raw.len()
72 && self.raw[neg_offset] != 0
73&& self.raw[neg_offset] != 1
74{
75return Err(
76"Received invalid value for `neg` in the `MysqlTime` datastructure".into(),
77 );
78 }
79let 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
84core::ptr::copy_nonoverlapping(
85self.raw.as_ptr(),
86out.as_mut_ptr() as *mut u8,
87len,
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
93out.assume_init()
94 };
95if result.neg && self.tpe != MysqlType::Time {
96Err("Negative date, datetime, and timestamp values are not supported".into())
97 } else {
98Ok(result)
99 }
100 }
101_ => Err(self.invalid_type_code("timestamp")),
102 }
103 }
104105/// Returns the numeric representation of this value, based on the type code.
106 /// Returns an error if the type code is not numeric.
107pub(crate) fn numeric_value(&self) -> deserialize::Result<NumericRepresentation<'_>> {
108Ok(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 }
123124fn 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 }
131132fn too_short_buffer(&self, expected: usize, tpe: &'static str) -> deserialize::Result<()> {
133if self.raw.len() < expected {
134Err(::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 {}",
138self.raw.len()
139 )140 .into())
141 } else {
142Ok(())
143 }
144 }
145146/// Reads a `T` from the start of the buffer, in the byte order the server sent it in.
147fn read<T: FromNeBytes>(&self) -> deserialize::Result<T> {
148self.too_short_buffer(core::mem::size_of::<T>(), core::any::type_name::<T>())?;
149Ok(T::from_ne_prefix(self.raw))
150 }
151}
152153/// 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.
158fn from_ne_prefix(buffer: &[u8]) -> Self;
159}
160161macro_rules! impl_from_ne_bytes {
162 ($($t:ty),+ $(,)?) => {
163 $(
164impl FromNeBytes for $t {
165fn from_ne_prefix(buffer: &[u8]) -> Self {
166let mut bytes = [0_u8; core::mem::size_of::<Self>()];
167 bytes.copy_from_slice(&buffer[..core::mem::size_of::<Self>()]);
168Self::from_ne_bytes(bytes)
169 }
170 }
171 )+
172 };
173}
174175impl 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);
176177/// 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`
182Tiny(i8),
183/// Corresponds to `MYSQL_TYPE_TINY` with the `UNSIGNED` flag set
184UnsignedTiny(u8),
185/// Corresponds to `MYSQL_TYPE_SHORT`
186Small(i16),
187/// Corresponds to `MYSQL_TYPE_SHORT` with the `UNSIGNED` flag set
188UnsignedSmall(u16),
189/// Corresponds to `MYSQL_TYPE_INT24` and `MYSQL_TYPE_LONG`
190Medium(i32),
191/// Corresponds to `MYSQL_TYPE_INT24` and `MYSQL_TYPE_LONG` with the `UNSIGNED` flag set
192UnsignedMedium(u32),
193/// Corresponds to `MYSQL_TYPE_LONGLONG`
194Big(i64),
195/// Corresponds to `MYSQL_TYPE_LONGLONG` with the `UNSIGNED` flag set
196UnsignedBig(u64),
197/// Corresponds to `MYSQL_TYPE_FLOAT`
198Float(f32),
199/// Corresponds to `MYSQL_TYPE_DOUBLE`
200Double(f64),
201/// Corresponds to `MYSQL_TYPE_DECIMAL` and `MYSQL_TYPE_NEWDECIMAL`
202Decimal(&'a [u8]),
203}
204205#[test]
206#[allow(unsafe_code, reason = "Test code")]
207fn invalid_reads() {
208use crate::data_types::MysqlTimestampType;
209210assert!(
211 MysqlValue::new_internal(&[1], MysqlType::Timestamp)
212 .time_value()
213 .is_err()
214 );
215let 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 };
227let mut bytes = [0; std::mem::size_of::<MysqlTime>()];
228unsafe {
229// SAFETY: Test code
230 // also the size matches and we want to get raw bytes
231std::ptr::copy(
232&v as *const MysqlTime as *const u8,
233 bytes.as_mut_ptr(),
234 bytes.len(),
235 );
236 }
237let offset = std::mem::offset_of!(MysqlTime, neg);
238 bytes[offset] = 42;
239assert!(
240 MysqlValue::new_internal(&bytes, MysqlType::Timestamp)
241 .time_value()
242 .is_err()
243 );
244245let 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 };
257let bytes = negative_time.serialize();
258assert!(
259 MysqlValue::new_internal(&bytes, MysqlType::Time)
260 .time_value()
261 .unwrap()
262 .neg
263 );
264assert!(
265 MysqlValue::new_internal(&bytes, MysqlType::Date)
266 .time_value()
267 .is_err()
268 );
269assert!(
270 MysqlValue::new_internal(&bytes, MysqlType::DateTime)
271 .time_value()
272 .is_err()
273 );
274assert!(
275 MysqlValue::new_internal(&bytes, MysqlType::Timestamp)
276 .time_value()
277 .is_err()
278 );
279280assert!(
281 MysqlValue::new_internal(&[1, 2], MysqlType::Long)
282 .numeric_value()
283 .is_err()
284 );
285286assert!(
287 MysqlValue::new_internal(&[1, 2, 3, 4], MysqlType::LongLong)
288 .numeric_value()
289 .is_err()
290 );
291292assert!(
293 MysqlValue::new_internal(&[1], MysqlType::Short)
294 .numeric_value()
295 .is_err()
296 );
297298assert!(
299 MysqlValue::new_internal(&[1, 2, 3, 4], MysqlType::Double)
300 .numeric_value()
301 .is_err()
302 );
303304assert!(
305 MysqlValue::new_internal(&[1, 2], MysqlType::Float)
306 .numeric_value()
307 .is_err()
308 );
309310assert!(
311 MysqlValue::new_internal(&[1], MysqlType::Tiny)
312 .numeric_value()
313 .is_ok()
314 );
315316assert!(
317 MysqlValue::new_internal(&[], MysqlType::Tiny)
318 .numeric_value()
319 .is_err()
320 );
321322assert!(
323 MysqlValue::new_internal(&[], MysqlType::UnsignedTiny)
324 .numeric_value()
325 .is_err()
326 );
327328// Every arm reads the leading bytes it needs and ignores any trailing ones.
329assert!(
330 MysqlValue::new_internal(&[1, 2, 3], MysqlType::UnsignedShort)
331 .numeric_value()
332 .is_ok()
333 );
334335assert!(
336 MysqlValue::new_internal(&[1, 2, 3, 4, 5], MysqlType::UnsignedLong)
337 .numeric_value()
338 .is_ok()
339 );
340341assert!(
342 MysqlValue::new_internal(&[1, 2, 3, 4, 5, 6, 7, 8, 9], MysqlType::UnsignedLongLong)
343 .numeric_value()
344 .is_ok()
345 );
346}
347348#[test]
349fn numeric_value_keeps_signedness() {
350use super::NumericRepresentation as N;
351352assert!(matches!(
353 MysqlValue::new_internal(&[0xFF], MysqlType::Tiny).numeric_value(),
354Ok(N::Tiny(-1))
355 ));
356assert!(matches!(
357 MysqlValue::new_internal(&[200], MysqlType::UnsignedTiny).numeric_value(),
358Ok(N::UnsignedTiny(200))
359 ));
360assert!(matches!(
361 MysqlValue::new_internal(&(-1i16).to_ne_bytes(), MysqlType::Short).numeric_value(),
362Ok(N::Small(-1))
363 ));
364assert!(matches!(
365 MysqlValue::new_internal(&40000u16.to_ne_bytes(), MysqlType::UnsignedShort).numeric_value(),
366Ok(N::UnsignedSmall(40000))
367 ));
368assert!(matches!(
369 MysqlValue::new_internal(&(-1i32).to_ne_bytes(), MysqlType::Long).numeric_value(),
370Ok(N::Medium(-1))
371 ));
372assert!(matches!(
373 MysqlValue::new_internal(&u32::MAX.to_ne_bytes(), MysqlType::UnsignedLong).numeric_value(),
374Ok(N::UnsignedMedium(u32::MAX))
375 ));
376assert!(matches!(
377 MysqlValue::new_internal(&(-1i64).to_ne_bytes(), MysqlType::LongLong).numeric_value(),
378Ok(N::Big(-1))
379 ));
380assert!(matches!(
381 MysqlValue::new_internal(&u64::MAX.to_ne_bytes(), MysqlType::UnsignedLongLong)
382 .numeric_value(),
383Ok(N::UnsignedBig(u64::MAX))
384 ));
385}