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