msql_srv/value/
decode.rs

1use crate::myc::constants::ColumnType;
2use crate::myc::io::ReadMysqlExt;
3use byteorder::{LittleEndian, ReadBytesExt};
4use std::io;
5
6/// MySQL value as provided when executing prepared statements.
7#[derive(Debug, PartialEq, Copy, Clone)]
8pub struct Value<'a>(ValueInner<'a>);
9
10/// A representation of a concrete, typed MySQL value.
11#[derive(Debug, PartialEq, Copy, Clone)]
12pub enum ValueInner<'a> {
13    /// The MySQL `NULL` value.
14    NULL,
15    /// An untyped sequence of bytes (usually a text type or `MYSQL_TYPE_BLOB`).
16    Bytes(&'a [u8]),
17    /// A signed integer.
18    Int(i64),
19    /// An unsigned integer.
20    UInt(u64),
21    /// A floating point number.
22    Double(f64),
23    /// A [binary encoding](https://mariadb.com/kb/en/library/resultset-row/#date-binary-encoding)
24    /// of a `MYSQL_TYPE_DATE`.
25    Date(&'a [u8]),
26    /// A [binary encoding](https://mariadb.com/kb/en/library/resultset-row/#time-binary-encoding)
27    /// of a `MYSQL_TYPE_TIME`.
28    Time(&'a [u8]),
29    /// A [binary
30    /// encoding](https://mariadb.com/kb/en/library/resultset-row/#timestamp-binary-encoding) of a
31    /// `MYSQL_TYPE_TIMESTAMP` or `MYSQL_TYPE_DATETIME`.
32    Datetime(&'a [u8]),
33}
34
35impl<'a> Value<'a> {
36    /// Return the inner stored representation of this value.
37    ///
38    /// This may be useful for when you do not care about the exact data type used for a column,
39    /// but instead wish to introspect a value you are given at runtime. Note that the contained
40    /// value may be stored in a type that is more general than what the corresponding parameter
41    /// type allows (e.g., a `u8` will be stored as an `u64`).
42    pub fn into_inner(self) -> ValueInner<'a> {
43        self.0
44    }
45
46    pub(crate) fn null() -> Self {
47        Value(ValueInner::NULL)
48    }
49
50    /// Returns true if this is a NULL value
51    pub fn is_null(&self) -> bool {
52        matches!(self.0, ValueInner::NULL)
53    }
54
55    pub(crate) fn parse_from(
56        input: &mut &'a [u8],
57        ct: ColumnType,
58        unsigned: bool,
59    ) -> io::Result<Self> {
60        ValueInner::parse_from(input, ct, unsigned).map(Value)
61    }
62
63    pub(crate) fn bytes(input: &'a [u8]) -> Value<'a> {
64        Value(ValueInner::Bytes(input))
65    }
66}
67
68macro_rules! read_bytes {
69    ($input:expr, $len:expr) => {
70        if $len as usize > $input.len() {
71            Err(io::Error::new(
72                io::ErrorKind::UnexpectedEof,
73                "EOF while reading length-encoded string",
74            ))
75        } else {
76            let (bits, rest) = $input.split_at($len as usize);
77            *$input = rest;
78            Ok(bits)
79        }
80    };
81}
82
83impl<'a> ValueInner<'a> {
84    fn parse_from(input: &mut &'a [u8], ct: ColumnType, unsigned: bool) -> io::Result<Self> {
85        match ct {
86            ColumnType::MYSQL_TYPE_STRING
87            | ColumnType::MYSQL_TYPE_VAR_STRING
88            | ColumnType::MYSQL_TYPE_BLOB
89            | ColumnType::MYSQL_TYPE_TINY_BLOB
90            | ColumnType::MYSQL_TYPE_MEDIUM_BLOB
91            | ColumnType::MYSQL_TYPE_LONG_BLOB
92            | ColumnType::MYSQL_TYPE_SET
93            | ColumnType::MYSQL_TYPE_ENUM
94            | ColumnType::MYSQL_TYPE_DECIMAL
95            | ColumnType::MYSQL_TYPE_VARCHAR
96            | ColumnType::MYSQL_TYPE_BIT
97            | ColumnType::MYSQL_TYPE_NEWDECIMAL
98            | ColumnType::MYSQL_TYPE_GEOMETRY
99            | ColumnType::MYSQL_TYPE_JSON => {
100                let len = input.read_lenenc_int()?;
101                Ok(ValueInner::Bytes(read_bytes!(input, len)?))
102            }
103            ColumnType::MYSQL_TYPE_TINY => {
104                if unsigned {
105                    Ok(ValueInner::UInt(u64::from(input.read_u8()?)))
106                } else {
107                    Ok(ValueInner::Int(i64::from(input.read_i8()?)))
108                }
109            }
110            ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => {
111                if unsigned {
112                    Ok(ValueInner::UInt(u64::from(
113                        input.read_u16::<LittleEndian>()?,
114                    )))
115                } else {
116                    Ok(ValueInner::Int(i64::from(
117                        input.read_i16::<LittleEndian>()?,
118                    )))
119                }
120            }
121            ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
122                if unsigned {
123                    Ok(ValueInner::UInt(u64::from(
124                        input.read_u32::<LittleEndian>()?,
125                    )))
126                } else {
127                    Ok(ValueInner::Int(i64::from(
128                        input.read_i32::<LittleEndian>()?,
129                    )))
130                }
131            }
132            ColumnType::MYSQL_TYPE_LONGLONG => {
133                if unsigned {
134                    Ok(ValueInner::UInt(input.read_u64::<LittleEndian>()?))
135                } else {
136                    Ok(ValueInner::Int(input.read_i64::<LittleEndian>()?))
137                }
138            }
139            ColumnType::MYSQL_TYPE_FLOAT => {
140                let f = input.read_f32::<LittleEndian>()?;
141                println!("read {}", f);
142                Ok(ValueInner::Double(f64::from(f)))
143            }
144            ColumnType::MYSQL_TYPE_DOUBLE => {
145                Ok(ValueInner::Double(input.read_f64::<LittleEndian>()?))
146            }
147            ColumnType::MYSQL_TYPE_TIMESTAMP | ColumnType::MYSQL_TYPE_DATETIME => {
148                let len = input.read_u8()?;
149                Ok(ValueInner::Datetime(read_bytes!(input, len)?))
150            }
151            ColumnType::MYSQL_TYPE_DATE => {
152                let len = input.read_u8()?;
153                Ok(ValueInner::Date(read_bytes!(input, len)?))
154            }
155            ColumnType::MYSQL_TYPE_TIME => {
156                let len = input.read_u8()?;
157                Ok(ValueInner::Time(read_bytes!(input, len)?))
158            }
159            ColumnType::MYSQL_TYPE_NULL => Ok(ValueInner::NULL),
160            ct => Err(io::Error::new(
161                io::ErrorKind::InvalidInput,
162                format!("unknown column type {:?}", ct),
163            )),
164        }
165    }
166}
167
168// NOTE: these should all be TryFrom
169macro_rules! impl_into {
170    ($t:ty, $($variant:path),*) => {
171        impl<'a> From<Value<'a>> for $t {
172            fn from(val: Value<'a>) -> Self {
173                match val.0 {
174                    $($variant(v) => v as $t),*,
175                    v => panic!(concat!("invalid type conversion from {:?} to ", stringify!($t)), v)
176                }
177            }
178        }
179    }
180}
181
182impl_into!(u8, ValueInner::UInt, ValueInner::Int);
183impl_into!(u16, ValueInner::UInt, ValueInner::Int);
184impl_into!(u32, ValueInner::UInt, ValueInner::Int);
185impl_into!(u64, ValueInner::UInt);
186impl_into!(i8, ValueInner::UInt, ValueInner::Int);
187impl_into!(i16, ValueInner::UInt, ValueInner::Int);
188impl_into!(i32, ValueInner::UInt, ValueInner::Int);
189impl_into!(i64, ValueInner::Int);
190impl_into!(f32, ValueInner::Double);
191impl_into!(f64, ValueInner::Double);
192impl_into!(&'a [u8], ValueInner::Bytes);
193
194impl<'a> From<Value<'a>> for &'a str {
195    fn from(val: Value<'a>) -> Self {
196        if let ValueInner::Bytes(v) = val.0 {
197            ::std::str::from_utf8(v).unwrap()
198        } else {
199            panic!("invalid type conversion from {:?} to string", val)
200        }
201    }
202}
203
204use chrono::{NaiveDate, NaiveDateTime};
205impl<'a> From<Value<'a>> for NaiveDate {
206    fn from(val: Value<'a>) -> Self {
207        if let ValueInner::Date(mut v) = val.0 {
208            assert_eq!(v.len(), 4);
209            if let Some(d) = NaiveDate::from_ymd_opt(
210                i32::from(v.read_u16::<LittleEndian>().unwrap()),
211                u32::from(v.read_u8().unwrap()),
212                u32::from(v.read_u8().unwrap()),
213            ) {
214                return d;
215            }
216        }
217        panic!("invalid type conversion from {:?} to date", val)
218    }
219}
220
221impl<'a> From<Value<'a>> for NaiveDateTime {
222    fn from(val: Value<'a>) -> Self {
223        if let ValueInner::Datetime(mut v) = val.0 {
224            assert!(v.len() == 7 || v.len() == 11);
225            if let Some(d) = NaiveDate::from_ymd_opt(
226                i32::from(v.read_u16::<LittleEndian>().unwrap()),
227                u32::from(v.read_u8().unwrap()),
228                u32::from(v.read_u8().unwrap()),
229            ) {
230                let h = u32::from(v.read_u8().unwrap());
231                let m = u32::from(v.read_u8().unwrap());
232                let s = u32::from(v.read_u8().unwrap());
233
234                let d = if v.len() == 11 {
235                    let us = v.read_u32::<LittleEndian>().unwrap();
236                    d.and_hms_micro_opt(h, m, s, us)
237                } else {
238                    d.and_hms_opt(h, m, s)
239                };
240
241                if let Some(d) = d {
242                    return d;
243                }
244            }
245        }
246        panic!("invalid type conversion from {:?} to datetime", val)
247    }
248}
249
250use std::time::Duration;
251impl<'a> From<Value<'a>> for Duration {
252    fn from(val: Value<'a>) -> Self {
253        if let ValueInner::Time(mut v) = val.0 {
254            assert!(v.is_empty() || v.len() == 8 || v.len() == 12);
255
256            if v.is_empty() {
257                return Duration::from_secs(0);
258            }
259
260            let neg = v.read_u8().unwrap();
261            if neg != 0u8 {
262                unimplemented!();
263            }
264
265            let days = u64::from(v.read_u32::<LittleEndian>().unwrap());
266            let hours = u64::from(v.read_u8().unwrap());
267            let minutes = u64::from(v.read_u8().unwrap());
268            let seconds = u64::from(v.read_u8().unwrap());
269            let micros = if v.len() == 12 {
270                v.read_u32::<LittleEndian>().unwrap()
271            } else {
272                0
273            };
274
275            Duration::new(
276                days * 86_400 + hours * 3_600 + minutes * 60 + seconds,
277                micros * 1_000,
278            )
279        } else {
280            panic!("invalid type conversion from {:?} to datetime", val)
281        }
282    }
283}
284
285#[cfg(test)]
286#[allow(unused_imports)]
287mod tests {
288    use super::Value;
289    use crate::myc;
290    use crate::myc::io::WriteMysqlExt;
291    use crate::{Column, ColumnFlags, ColumnType};
292    use chrono::{self, TimeZone};
293    use myc::proto::MySerialize;
294    use std::time;
295
296    macro_rules! rt {
297        ($name:ident, $t:ty, $v:expr, $ct:expr) => {
298            rt!($name, $t, $v, $ct, false);
299        };
300        ($name:ident, $t:ty, $v:expr, $ct:expr, $sig:expr) => {
301            #[test]
302            fn $name() {
303                let mut data = Vec::new();
304                let mut col = Column {
305                    table: String::new(),
306                    column: String::new(),
307                    coltype: $ct,
308                    colflags: ColumnFlags::empty(),
309                };
310
311                if !$sig {
312                    col.colflags.insert(ColumnFlags::UNSIGNED_FLAG);
313                }
314
315                let v: $t = $v;
316                myc::value::Value::from(v).serialize(&mut data);
317                assert_eq!(
318                    Into::<$t>::into(Value::parse_from(&mut &data[..], $ct, !$sig).unwrap()),
319                    v
320                );
321            }
322        };
323    }
324
325    rt!(u8_one, u8, 1, ColumnType::MYSQL_TYPE_TINY, false);
326    rt!(i8_one, i8, 1, ColumnType::MYSQL_TYPE_TINY, true);
327    rt!(u8_one_short, u8, 1, ColumnType::MYSQL_TYPE_SHORT, false);
328    rt!(i8_one_short, i8, 1, ColumnType::MYSQL_TYPE_SHORT, true);
329    rt!(u8_one_long, u8, 1, ColumnType::MYSQL_TYPE_LONG, false);
330    rt!(i8_one_long, i8, 1, ColumnType::MYSQL_TYPE_LONG, true);
331    rt!(
332        u8_one_longlong,
333        u8,
334        1,
335        ColumnType::MYSQL_TYPE_LONGLONG,
336        false
337    );
338    rt!(
339        i8_one_longlong,
340        i8,
341        1,
342        ColumnType::MYSQL_TYPE_LONGLONG,
343        true
344    );
345    rt!(u16_one, u16, 1, ColumnType::MYSQL_TYPE_SHORT, false);
346    rt!(i16_one, i16, 1, ColumnType::MYSQL_TYPE_SHORT, true);
347    rt!(u16_one_long, u16, 1, ColumnType::MYSQL_TYPE_LONG, false);
348    rt!(i16_one_long, i16, 1, ColumnType::MYSQL_TYPE_LONG, true);
349    rt!(
350        u16_one_longlong,
351        u16,
352        1,
353        ColumnType::MYSQL_TYPE_LONGLONG,
354        false
355    );
356    rt!(
357        i16_one_longlong,
358        i16,
359        1,
360        ColumnType::MYSQL_TYPE_LONGLONG,
361        true
362    );
363    rt!(u32_one_long, u32, 1, ColumnType::MYSQL_TYPE_LONG, false);
364    rt!(i32_one_long, i32, 1, ColumnType::MYSQL_TYPE_LONG, true);
365    rt!(
366        u32_one_longlong,
367        u32,
368        1,
369        ColumnType::MYSQL_TYPE_LONGLONG,
370        false
371    );
372    rt!(
373        i32_one_longlong,
374        i32,
375        1,
376        ColumnType::MYSQL_TYPE_LONGLONG,
377        true
378    );
379    rt!(u64_one, u64, 1, ColumnType::MYSQL_TYPE_LONGLONG, false);
380    rt!(i64_one, i64, 1, ColumnType::MYSQL_TYPE_LONGLONG, true);
381
382    rt!(f32_one_float, f32, 1.0, ColumnType::MYSQL_TYPE_FLOAT, false);
383    rt!(f64_one, f64, 1.0, ColumnType::MYSQL_TYPE_DOUBLE, false);
384
385    rt!(
386        u8_max,
387        u8,
388        u8::max_value(),
389        ColumnType::MYSQL_TYPE_TINY,
390        false
391    );
392    rt!(
393        i8_max,
394        i8,
395        i8::max_value(),
396        ColumnType::MYSQL_TYPE_TINY,
397        true
398    );
399    rt!(
400        u16_max,
401        u16,
402        u16::max_value(),
403        ColumnType::MYSQL_TYPE_SHORT,
404        false
405    );
406    rt!(
407        i16_max,
408        i16,
409        i16::max_value(),
410        ColumnType::MYSQL_TYPE_SHORT,
411        true
412    );
413    rt!(
414        u32_max,
415        u32,
416        u32::max_value(),
417        ColumnType::MYSQL_TYPE_LONG,
418        false
419    );
420    rt!(
421        i32_max,
422        i32,
423        i32::max_value(),
424        ColumnType::MYSQL_TYPE_LONG,
425        true
426    );
427    rt!(
428        u64_max,
429        u64,
430        u64::max_value(),
431        ColumnType::MYSQL_TYPE_LONGLONG,
432        false
433    );
434    rt!(
435        i64_max,
436        i64,
437        i64::max_value(),
438        ColumnType::MYSQL_TYPE_LONGLONG,
439        true
440    );
441
442    rt!(
443        time,
444        chrono::NaiveDate,
445        chrono::Local::now().date_naive(),
446        ColumnType::MYSQL_TYPE_DATE
447    );
448    rt!(
449        datetime,
450        chrono::NaiveDateTime,
451        chrono::Utc
452            .with_ymd_and_hms(1989, 12, 7, 8, 0, 4)
453            .unwrap()
454            .naive_utc(),
455        ColumnType::MYSQL_TYPE_DATETIME
456    );
457    rt!(
458        dur,
459        time::Duration,
460        time::Duration::from_secs(1893),
461        ColumnType::MYSQL_TYPE_TIME
462    );
463    rt!(
464        dur_zero,
465        time::Duration,
466        time::Duration::from_secs(0),
467        ColumnType::MYSQL_TYPE_TIME
468    );
469    rt!(
470        bytes,
471        &[u8],
472        &[0x42, 0x00, 0x1a],
473        ColumnType::MYSQL_TYPE_BLOB
474    );
475    rt!(string, &str, "foobar", ColumnType::MYSQL_TYPE_STRING);
476}