1use super::types::date_and_time::MysqlTime;
2use super::MysqlType;
34use crate::deserialize;
5use std::error::Error;
6use std::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) -> 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}
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 }
2324pub(in crate::mysql) fn new_internal(raw: &'a [u8], tpe: MysqlType) -> Self {
25Self { raw, tpe }
26 }
2728/// Get the underlying raw byte representation
29pub fn as_bytes(&self) -> &'a [u8] {
30self.raw
31 }
3233/// Get the mysql type of the current value
34pub fn value_type(&self) -> MysqlType {
35self.tpe
36 }
3738/// Checks that the type code is valid, and interprets the data as a
39 /// `MysqlTime` pointer
40// We use `ptr::copy` to read the actual data
41 // and copy it over to the returned `MysqlTime` instance
42#[allow(unsafe_code)] // MaybeUninit + ptr copy
43pub(crate) fn time_value(&self) -> deserialize::Result<MysqlTime> {
44match self.tpe {
45 MysqlType::Time | MysqlType::Date | MysqlType::DateTime | MysqlType::Timestamp => {
46// we check for the size of the `MYSQL_TIME` type from `mysqlclient_sys` here as
47 // certain older libmysqlclient and newer libmariadb versions do not have all the
48 // same fields (and size) as the `MysqlTime` type from diesel. The later one is modeled after
49 // the type from newer libmysqlclient
50self.too_short_buffer(
51#[cfg(feature = "mysql")]
52std::mem::size_of::<mysqlclient_sys::MYSQL_TIME>(),
53#[cfg(not(feature = "mysql"))]
54std::mem::size_of::<MysqlTime>(),
55"timestamp",
56 )?;
57// To ensure we copy the right number of bytes we need to make sure to copy not more bytes than needed
58 // for `MysqlTime` and not more bytes than inside of the buffer
59let len = std::cmp::min(std::mem::size_of::<MysqlTime>(), self.raw.len());
60// Zero is a valid pattern for this type so we are fine with initializing all fields to zero
61 // If the provided byte buffer is too short we just use 0 as default value
62let mut out = MaybeUninit::<MysqlTime>::zeroed();
63// Make sure to check that the boolean is an actual bool value, so 0 or 1
64 // as anything else is UB in rust
65let neg_offset = const { builtin # offset_of(MysqlTime, neg) }std::mem::offset_of!(MysqlTime, neg);
66if neg_offset < self.raw.len()
67 && self.raw[neg_offset] != 0
68&& self.raw[neg_offset] != 1
69{
70return Err(
71"Received invalid value for `neg` in the `MysqlTime` datastructure".into(),
72 );
73 }
74let result = unsafe {
75// SAFETY: We copy over the bytes from our raw buffer to the `MysqlTime` instance
76 // This type is correctly aligned and we ensure that we do not copy more bytes than are there
77 // We are also sure that these ptr do not overlap as they are completely different
78 // instances
79std::ptr::copy_nonoverlapping(
80self.raw.as_ptr(),
81out.as_mut_ptr() as *mut u8,
82len,
83 );
84// SAFETY: all zero is a valid pattern for this type
85 // Otherwise any other bit pattern is also valid, beside
86 // neg being something other than 0 or 1
87 // We check for that above by looking at the byte before copying
88out.assume_init()
89 };
90if result.neg {
91Err("Negative dates/times are not yet supported".into())
92 } else {
93Ok(result)
94 }
95 }
96_ => Err(self.invalid_type_code("timestamp")),
97 }
98 }
99100/// Returns the numeric representation of this value, based on the type code.
101 /// Returns an error if the type code is not numeric.
102pub(crate) fn numeric_value(&self) -> deserialize::Result<NumericRepresentation<'_>> {
103Ok(match self.tpe {
104 MysqlType::Tiny => NumericRepresentation::Tiny(self.read()?),
105 MysqlType::UnsignedTiny => NumericRepresentation::UnsignedTiny(self.read()?),
106 MysqlType::Short => NumericRepresentation::Small(self.read()?),
107 MysqlType::UnsignedShort => NumericRepresentation::UnsignedSmall(self.read()?),
108 MysqlType::Long => NumericRepresentation::Medium(self.read()?),
109 MysqlType::UnsignedLong => NumericRepresentation::UnsignedMedium(self.read()?),
110 MysqlType::LongLong => NumericRepresentation::Big(self.read()?),
111 MysqlType::UnsignedLongLong => NumericRepresentation::UnsignedBig(self.read()?),
112 MysqlType::Float => NumericRepresentation::Float(self.read()?),
113 MysqlType::Double => NumericRepresentation::Double(self.read()?),
114 MysqlType::Numeric => NumericRepresentation::Decimal(self.raw),
115_ => return Err(self.invalid_type_code("number")),
116 })
117 }
118119fn invalid_type_code(&self, expected: &str) -> Box<dyn Error + Send + Sync> {
120::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Invalid representation received for {0}: {1:?}",
expected, self.tpe))
})format!(
121"Invalid representation received for {}: {:?}",
122 expected, self.tpe
123 )124 .into()
125 }
126127fn too_short_buffer(&self, expected: usize, tpe: &'static str) -> deserialize::Result<()> {
128if self.raw.len() < expected {
129Err(::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!(
130"Received a buffer with an invalid size while trying \
131 to read a {tpe} value: Expected at least {expected} bytes \
132 but got {}",
133self.raw.len()
134 )135 .into())
136 } else {
137Ok(())
138 }
139 }
140141/// Reads a `T` from the start of the buffer, in the byte order the server sent it in.
142fn read<T: FromNeBytes>(&self) -> deserialize::Result<T> {
143self.too_short_buffer(core::mem::size_of::<T>(), core::any::type_name::<T>())?;
144Ok(T::from_ne_prefix(self.raw))
145 }
146}
147148/// The numeric primitives the server transmits as native endian bytes.
149///
150/// Private, so that [`MysqlValue::read`] cannot be reached with any other type.
151trait FromNeBytes: Sized {
152/// Reads `Self` from the first `size_of::<Self>()` bytes, panicking on a shorter buffer.
153fn from_ne_prefix(buffer: &[u8]) -> Self;
154}
155156macro_rules! impl_from_ne_bytes {
157 ($($t:ty),+ $(,)?) => {
158 $(
159impl FromNeBytes for $t {
160fn from_ne_prefix(buffer: &[u8]) -> Self {
161let mut bytes = [0_u8; core::mem::size_of::<Self>()];
162 bytes.copy_from_slice(&buffer[..core::mem::size_of::<Self>()]);
163Self::from_ne_bytes(bytes)
164 }
165 }
166 )+
167 };
168}
169170impl 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);
171172/// Represents all possible forms MySQL transmits integers
173#[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::UnsignedTiny(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"UnsignedTiny", &__self_0),
NumericRepresentation::Small(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Small",
&__self_0),
NumericRepresentation::UnsignedSmall(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"UnsignedSmall", &__self_0),
NumericRepresentation::Medium(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Medium",
&__self_0),
NumericRepresentation::UnsignedMedium(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"UnsignedMedium", &__self_0),
NumericRepresentation::Big(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Big",
&__self_0),
NumericRepresentation::UnsignedBig(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"UnsignedBig", &__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]
#[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) -> NumericRepresentation<'a> {
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)]
174#[non_exhaustive]
175pub enum NumericRepresentation<'a> {
176/// Corresponds to `MYSQL_TYPE_TINY`
177Tiny(i8),
178/// Corresponds to `MYSQL_TYPE_TINY` with the `UNSIGNED` flag set
179UnsignedTiny(u8),
180/// Corresponds to `MYSQL_TYPE_SHORT`
181Small(i16),
182/// Corresponds to `MYSQL_TYPE_SHORT` with the `UNSIGNED` flag set
183UnsignedSmall(u16),
184/// Corresponds to `MYSQL_TYPE_INT24` and `MYSQL_TYPE_LONG`
185Medium(i32),
186/// Corresponds to `MYSQL_TYPE_INT24` and `MYSQL_TYPE_LONG` with the `UNSIGNED` flag set
187UnsignedMedium(u32),
188/// Corresponds to `MYSQL_TYPE_LONGLONG`
189Big(i64),
190/// Corresponds to `MYSQL_TYPE_LONGLONG` with the `UNSIGNED` flag set
191UnsignedBig(u64),
192/// Corresponds to `MYSQL_TYPE_FLOAT`
193Float(f32),
194/// Corresponds to `MYSQL_TYPE_DOUBLE`
195Double(f64),
196/// Corresponds to `MYSQL_TYPE_DECIMAL` and `MYSQL_TYPE_NEWDECIMAL`
197Decimal(&'a [u8]),
198}
199200#[test]
201#[allow(unsafe_code, reason = "Test code")]
202fn invalid_reads() {
203use crate::data_types::MysqlTimestampType;
204205assert!(MysqlValue::new_internal(&[1], MysqlType::Timestamp)
206 .time_value()
207 .is_err());
208let v = MysqlTime {
209 year: 2025,
210 month: 9,
211 day: 15,
212 hour: 22,
213 minute: 3,
214 second: 10,
215 second_part: 0,
216 neg: false,
217 time_type: MysqlTimestampType::MYSQL_TIMESTAMP_DATETIME,
218 time_zone_displacement: 0,
219 };
220let mut bytes = [0; std::mem::size_of::<MysqlTime>()];
221unsafe {
222// SAFETY: Test code
223 // also the size matches and we want to get raw bytes
224std::ptr::copy(
225&v as *const MysqlTime as *const u8,
226 bytes.as_mut_ptr(),
227 bytes.len(),
228 );
229 }
230let offset = std::mem::offset_of!(MysqlTime, neg);
231 bytes[offset] = 42;
232assert!(MysqlValue::new_internal(&bytes, MysqlType::Timestamp)
233 .time_value()
234 .is_err());
235236assert!(MysqlValue::new_internal(&[1, 2], MysqlType::Long)
237 .numeric_value()
238 .is_err());
239240assert!(MysqlValue::new_internal(&[1, 2, 3, 4], MysqlType::LongLong)
241 .numeric_value()
242 .is_err());
243244assert!(MysqlValue::new_internal(&[1], MysqlType::Short)
245 .numeric_value()
246 .is_err());
247248assert!(MysqlValue::new_internal(&[1, 2, 3, 4], MysqlType::Double)
249 .numeric_value()
250 .is_err());
251252assert!(MysqlValue::new_internal(&[1, 2], MysqlType::Float)
253 .numeric_value()
254 .is_err());
255256assert!(MysqlValue::new_internal(&[1], MysqlType::Tiny)
257 .numeric_value()
258 .is_ok());
259260assert!(MysqlValue::new_internal(&[], MysqlType::Tiny)
261 .numeric_value()
262 .is_err());
263264assert!(MysqlValue::new_internal(&[], MysqlType::UnsignedTiny)
265 .numeric_value()
266 .is_err());
267268// Every arm reads the leading bytes it needs and ignores any trailing ones.
269assert!(
270 MysqlValue::new_internal(&[1, 2, 3], MysqlType::UnsignedShort)
271 .numeric_value()
272 .is_ok()
273 );
274275assert!(
276 MysqlValue::new_internal(&[1, 2, 3, 4, 5], MysqlType::UnsignedLong)
277 .numeric_value()
278 .is_ok()
279 );
280281assert!(
282 MysqlValue::new_internal(&[1, 2, 3, 4, 5, 6, 7, 8, 9], MysqlType::UnsignedLongLong)
283 .numeric_value()
284 .is_ok()
285 );
286}
287288#[test]
289fn numeric_value_keeps_signedness() {
290use super::NumericRepresentation as N;
291292assert!(matches!(
293 MysqlValue::new_internal(&[0xFF], MysqlType::Tiny).numeric_value(),
294Ok(N::Tiny(-1))
295 ));
296assert!(matches!(
297 MysqlValue::new_internal(&[200], MysqlType::UnsignedTiny).numeric_value(),
298Ok(N::UnsignedTiny(200))
299 ));
300assert!(matches!(
301 MysqlValue::new_internal(&(-1i16).to_ne_bytes(), MysqlType::Short).numeric_value(),
302Ok(N::Small(-1))
303 ));
304assert!(matches!(
305 MysqlValue::new_internal(&40000u16.to_ne_bytes(), MysqlType::UnsignedShort).numeric_value(),
306Ok(N::UnsignedSmall(40000))
307 ));
308assert!(matches!(
309 MysqlValue::new_internal(&(-1i32).to_ne_bytes(), MysqlType::Long).numeric_value(),
310Ok(N::Medium(-1))
311 ));
312assert!(matches!(
313 MysqlValue::new_internal(&u32::MAX.to_ne_bytes(), MysqlType::UnsignedLong).numeric_value(),
314Ok(N::UnsignedMedium(u32::MAX))
315 ));
316assert!(matches!(
317 MysqlValue::new_internal(&(-1i64).to_ne_bytes(), MysqlType::LongLong).numeric_value(),
318Ok(N::Big(-1))
319 ));
320assert!(matches!(
321 MysqlValue::new_internal(&u64::MAX.to_ne_bytes(), MysqlType::UnsignedLongLong)
322 .numeric_value(),
323Ok(N::UnsignedBig(u64::MAX))
324 ));
325}