1use super::types::date_and_time::MysqlTime;
2use super::MysqlType;
3
4use crate::deserialize;
5use std::error::Error;
6use std::mem::MaybeUninit;
7
8#[derive(Clone, Debug)]
10pub struct MysqlValue<'a> {
11 raw: &'a [u8],
12 tpe: MysqlType,
13}
14
15impl<'a> MysqlValue<'a> {
16 #[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 pub(in crate::mysql) fn new_internal(raw: &'a [u8], tpe: MysqlType) -> Self {
25 Self { raw, tpe }
26 }
27
28 pub fn as_bytes(&self) -> &[u8] {
30 self.raw
31 }
32
33 pub fn value_type(&self) -> MysqlType {
35 self.tpe
36 }
37
38 #[allow(unsafe_code)] pub(crate) fn time_value(&self) -> deserialize::Result<MysqlTime> {
44 match self.tpe {
45 MysqlType::Time | MysqlType::Date | MysqlType::DateTime | MysqlType::Timestamp => {
46 self.too_short_buffer(
51 #[cfg(feature = "mysql")]
52 std::mem::size_of::<mysqlclient_sys::MYSQL_TIME>(),
53 #[cfg(not(feature = "mysql"))]
54 std::mem::size_of::<MysqlTime>(),
55 "timestamp",
56 )?;
57 let len = std::cmp::min(std::mem::size_of::<MysqlTime>(), self.raw.len());
60 let mut out = MaybeUninit::<MysqlTime>::zeroed();
63 let neg_offset = std::mem::offset_of!(MysqlTime, neg);
66 if neg_offset < self.raw.len()
67 && self.raw[neg_offset] != 0
68 && self.raw[neg_offset] != 1
69 {
70 return Err(
71 "Received invalid value for `neg` in the `MysqlTime` datastructure".into(),
72 );
73 }
74 let result = unsafe {
75 std::ptr::copy_nonoverlapping(
80 self.raw.as_ptr(),
81 out.as_mut_ptr() as *mut u8,
82 len,
83 );
84 out.assume_init()
89 };
90 if result.neg {
91 Err("Negative dates/times are not yet supported".into())
92 } else {
93 Ok(result)
94 }
95 }
96 _ => Err(self.invalid_type_code("timestamp")),
97 }
98 }
99
100 pub(crate) fn numeric_value(&self) -> deserialize::Result<NumericRepresentation<'_>> {
103 Ok(match self.tpe {
104 MysqlType::UnsignedTiny | MysqlType::Tiny => {
105 NumericRepresentation::Tiny(self.raw[0].try_into()?)
106 }
107 MysqlType::UnsignedShort | MysqlType::Short => {
108 self.too_short_buffer(2, "Short")?;
109 NumericRepresentation::Small(i16::from_ne_bytes((&self.raw[..2]).try_into()?))
110 }
111 MysqlType::UnsignedLong | MysqlType::Long => {
112 self.too_short_buffer(4, "Long")?;
113 NumericRepresentation::Medium(i32::from_ne_bytes((&self.raw[..4]).try_into()?))
114 }
115 MysqlType::UnsignedLongLong | MysqlType::LongLong => {
116 self.too_short_buffer(8, "LongLong")?;
117 NumericRepresentation::Big(i64::from_ne_bytes(self.raw.try_into()?))
118 }
119 MysqlType::Float => {
120 self.too_short_buffer(4, "Float")?;
121 NumericRepresentation::Float(f32::from_ne_bytes(self.raw.try_into()?))
122 }
123 MysqlType::Double => {
124 self.too_short_buffer(8, "Double")?;
125 NumericRepresentation::Double(f64::from_ne_bytes(self.raw.try_into()?))
126 }
127
128 MysqlType::Numeric => NumericRepresentation::Decimal(self.raw),
129 _ => return Err(self.invalid_type_code("number")),
130 })
131 }
132
133 fn invalid_type_code(&self, expected: &str) -> Box<dyn Error + Send + Sync> {
134 format!(
135 "Invalid representation received for {}: {:?}",
136 expected, self.tpe
137 )
138 .into()
139 }
140
141 fn too_short_buffer(&self, expected: usize, tpe: &'static str) -> deserialize::Result<()> {
142 if self.raw.len() < expected {
143 Err(format!(
144 "Received a buffer with an invalid size while trying \
145 to read a {tpe} value: Expected at least {expected} bytes \
146 but got {}",
147 self.raw.len()
148 )
149 .into())
150 } else {
151 Ok(())
152 }
153 }
154}
155
156#[derive(Debug, Clone, Copy)]
158#[non_exhaustive]
159pub enum NumericRepresentation<'a> {
160 Tiny(i8),
162 Small(i16),
164 Medium(i32),
166 Big(i64),
168 Float(f32),
170 Double(f64),
172 Decimal(&'a [u8]),
174}
175
176#[test]
177#[allow(unsafe_code, reason = "Test code")]
178fn invalid_reads() {
179 use crate::data_types::MysqlTimestampType;
180
181 assert!(MysqlValue::new_internal(&[1], MysqlType::Timestamp)
182 .time_value()
183 .is_err());
184 let v = MysqlTime {
185 year: 2025,
186 month: 9,
187 day: 15,
188 hour: 22,
189 minute: 3,
190 second: 10,
191 second_part: 0,
192 neg: false,
193 time_type: MysqlTimestampType::MYSQL_TIMESTAMP_DATETIME,
194 time_zone_displacement: 0,
195 };
196 let mut bytes = [0; std::mem::size_of::<MysqlTime>()];
197 unsafe {
198 std::ptr::copy(
201 &v as *const MysqlTime as *const u8,
202 bytes.as_mut_ptr(),
203 bytes.len(),
204 );
205 }
206 let offset = std::mem::offset_of!(MysqlTime, neg);
207 bytes[offset] = 42;
208 assert!(MysqlValue::new_internal(&bytes, MysqlType::Timestamp)
209 .time_value()
210 .is_err());
211
212 assert!(MysqlValue::new_internal(&[1, 2], MysqlType::Long)
213 .numeric_value()
214 .is_err());
215
216 assert!(MysqlValue::new_internal(&[1, 2, 3, 4], MysqlType::LongLong)
217 .numeric_value()
218 .is_err());
219
220 assert!(MysqlValue::new_internal(&[1], MysqlType::Short)
221 .numeric_value()
222 .is_err());
223
224 assert!(MysqlValue::new_internal(&[1, 2, 3, 4], MysqlType::Double)
225 .numeric_value()
226 .is_err());
227
228 assert!(MysqlValue::new_internal(&[1, 2], MysqlType::Float)
229 .numeric_value()
230 .is_err());
231
232 assert!(MysqlValue::new_internal(&[1], MysqlType::Tiny)
233 .numeric_value()
234 .is_ok());
235}