1use super::types::date_and_time::MysqlTime;
2use super::MysqlType;
3use crate::deserialize;
4use std::error::Error;
5
6#[derive(Clone, Debug)]
8pub struct MysqlValue<'a> {
9 raw: &'a [u8],
10 tpe: MysqlType,
11}
12
13impl<'a> MysqlValue<'a> {
14 #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
18 pub fn new(raw: &'a [u8], tpe: MysqlType) -> Self {
19 Self::new_internal(raw, tpe)
20 }
21
22 pub(in crate::mysql) fn new_internal(raw: &'a [u8], tpe: MysqlType) -> Self {
23 Self { raw, tpe }
24 }
25
26 pub fn as_bytes(&self) -> &[u8] {
28 self.raw
29 }
30
31 pub fn value_type(&self) -> MysqlType {
33 self.tpe
34 }
35
36 #[allow(dead_code, clippy::cast_ptr_alignment)]
42 #[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(std::mem::size_of::<MysqlTime>(), "Timestamp")?;
47 let ptr = self.raw.as_ptr() as *const MysqlTime;
48 let result = unsafe { ptr.read_unaligned() };
49 if result.neg {
50 Err("Negative dates/times are not yet supported".into())
51 } else {
52 Ok(result)
53 }
54 }
55 _ => Err(self.invalid_type_code("timestamp")),
56 }
57 }
58
59 pub(crate) fn numeric_value(&self) -> deserialize::Result<NumericRepresentation<'_>> {
62 Ok(match self.tpe {
63 MysqlType::UnsignedTiny | MysqlType::Tiny => {
64 NumericRepresentation::Tiny(self.raw[0].try_into()?)
65 }
66 MysqlType::UnsignedShort | MysqlType::Short => {
67 self.too_short_buffer(2, "Short")?;
68 NumericRepresentation::Small(i16::from_ne_bytes((&self.raw[..2]).try_into()?))
69 }
70 MysqlType::UnsignedLong | MysqlType::Long => {
71 self.too_short_buffer(4, "Long")?;
72 NumericRepresentation::Medium(i32::from_ne_bytes((&self.raw[..4]).try_into()?))
73 }
74 MysqlType::UnsignedLongLong | MysqlType::LongLong => {
75 self.too_short_buffer(8, "LongLong")?;
76 NumericRepresentation::Big(i64::from_ne_bytes(self.raw.try_into()?))
77 }
78 MysqlType::Float => {
79 self.too_short_buffer(4, "Float")?;
80 NumericRepresentation::Float(f32::from_ne_bytes(self.raw.try_into()?))
81 }
82 MysqlType::Double => {
83 self.too_short_buffer(8, "Double")?;
84 NumericRepresentation::Double(f64::from_ne_bytes(self.raw.try_into()?))
85 }
86
87 MysqlType::Numeric => NumericRepresentation::Decimal(self.raw),
88 _ => return Err(self.invalid_type_code("number")),
89 })
90 }
91
92 fn invalid_type_code(&self, expected: &str) -> Box<dyn Error + Send + Sync> {
93 format!(
94 "Invalid representation received for {}: {:?}",
95 expected, self.tpe
96 )
97 .into()
98 }
99
100 fn too_short_buffer(&self, expected: usize, tpe: &'static str) -> deserialize::Result<()> {
101 if self.raw.len() < expected {
102 Err(format!(
103 "Received a buffer with an invalid size while trying \
104 to read a {tpe} value: Expected at least {expected} bytes \
105 but got {}",
106 self.raw.len()
107 )
108 .into())
109 } else {
110 Ok(())
111 }
112 }
113}
114
115#[derive(Debug, Clone, Copy)]
117#[non_exhaustive]
118pub enum NumericRepresentation<'a> {
119 Tiny(i8),
121 Small(i16),
123 Medium(i32),
125 Big(i64),
127 Float(f32),
129 Double(f64),
131 Decimal(&'a [u8]),
133}
134
135#[test]
136fn invalid_reads() {
137 assert!(MysqlValue::new_internal(&[1], MysqlType::Timestamp)
138 .time_value()
139 .is_err());
140
141 assert!(MysqlValue::new_internal(&[1, 2], MysqlType::Long)
142 .numeric_value()
143 .is_err());
144
145 assert!(MysqlValue::new_internal(&[1, 2, 3, 4], MysqlType::LongLong)
146 .numeric_value()
147 .is_err());
148
149 assert!(MysqlValue::new_internal(&[1], MysqlType::Short)
150 .numeric_value()
151 .is_err());
152
153 assert!(MysqlValue::new_internal(&[1, 2, 3, 4], MysqlType::Double)
154 .numeric_value()
155 .is_err());
156
157 assert!(MysqlValue::new_internal(&[1, 2], MysqlType::Float)
158 .numeric_value()
159 .is_err());
160
161 assert!(MysqlValue::new_internal(&[1], MysqlType::Tiny)
162 .numeric_value()
163 .is_ok());
164}