opensrv_mysql/value/
decode.rs

1// Copyright 2021 Datafuse Labs.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::io;
16
17use byteorder::{LittleEndian, ReadBytesExt};
18
19use crate::myc::constants::ColumnType;
20use crate::myc::io::ReadMysqlExt;
21
22/// MySQL value as provided when executing prepared statements.
23#[derive(Debug, PartialEq, Copy, Clone)]
24pub struct Value<'a>(ValueInner<'a>);
25
26/// A representation of a concrete, typed MySQL value.
27#[derive(Debug, PartialEq, Copy, Clone)]
28pub enum ValueInner<'a> {
29    /// The MySQL `NULL` value.
30    NULL,
31    /// An untyped sequence of bytes (usually a text type or `MYSQL_TYPE_BLOB`).
32    Bytes(&'a [u8]),
33    /// A signed integer.
34    Int(i64),
35    /// An unsigned integer.
36    UInt(u64),
37    /// A floating point number.
38    Double(f64),
39    /// A [binary encoding](https://mariadb.com/kb/en/library/resultset-row/#date-binary-encoding)
40    /// of a `MYSQL_TYPE_DATE`.
41    Date(&'a [u8]),
42    /// A [binary encoding](https://mariadb.com/kb/en/library/resultset-row/#time-binary-encoding)
43    /// of a `MYSQL_TYPE_TIME`.
44    Time(&'a [u8]),
45    /// A [binary
46    /// encoding](https://mariadb.com/kb/en/library/resultset-row/#timestamp-binary-encoding) of a
47    /// `MYSQL_TYPE_TIMESTAMP` or `MYSQL_TYPE_DATETIME`.
48    Datetime(&'a [u8]),
49}
50
51impl<'a> Value<'a> {
52    /// Return the inner stored representation of this value.
53    ///
54    /// This may be useful for when you do not care about the exact data type used for a column,
55    /// but instead wish to introspect a value you are given at runtime. Note that the contained
56    /// value may be stored in a type that is more general than what the corresponding parameter
57    /// type allows (e.g., a `u8` will be stored as an `u64`).
58    pub fn into_inner(self) -> ValueInner<'a> {
59        self.0
60    }
61
62    pub(crate) fn null() -> Self {
63        Value(ValueInner::NULL)
64    }
65
66    /// Returns true if this is a NULL value
67    pub fn is_null(&self) -> bool {
68        matches!(self.0, ValueInner::NULL)
69    }
70
71    pub(crate) fn parse_from(
72        input: &mut &'a [u8],
73        ct: ColumnType,
74        unsigned: bool,
75    ) -> io::Result<Self> {
76        ValueInner::parse_from(input, ct, unsigned).map(Value)
77    }
78
79    pub(crate) fn bytes(input: &'a [u8]) -> Value<'a> {
80        Value(ValueInner::Bytes(input))
81    }
82}
83
84macro_rules! read_bytes {
85    ($input:expr, $len:expr) => {
86        if $len as usize > $input.len() {
87            Err(io::Error::new(
88                io::ErrorKind::UnexpectedEof,
89                "EOF while reading length-encoded string",
90            ))
91        } else {
92            let (bits, rest) = $input.split_at($len as usize);
93            *$input = rest;
94            Ok(bits)
95        }
96    };
97}
98
99impl<'a> ValueInner<'a> {
100    fn parse_from(input: &mut &'a [u8], ct: ColumnType, unsigned: bool) -> io::Result<Self> {
101        match ct {
102            ColumnType::MYSQL_TYPE_STRING
103            | ColumnType::MYSQL_TYPE_VAR_STRING
104            | ColumnType::MYSQL_TYPE_BLOB
105            | ColumnType::MYSQL_TYPE_TINY_BLOB
106            | ColumnType::MYSQL_TYPE_MEDIUM_BLOB
107            | ColumnType::MYSQL_TYPE_LONG_BLOB
108            | ColumnType::MYSQL_TYPE_SET
109            | ColumnType::MYSQL_TYPE_ENUM
110            | ColumnType::MYSQL_TYPE_DECIMAL
111            | ColumnType::MYSQL_TYPE_VARCHAR
112            | ColumnType::MYSQL_TYPE_BIT
113            | ColumnType::MYSQL_TYPE_NEWDECIMAL
114            | ColumnType::MYSQL_TYPE_GEOMETRY
115            | ColumnType::MYSQL_TYPE_JSON => {
116                let len = input.read_lenenc_int()?;
117                Ok(ValueInner::Bytes(read_bytes!(input, len)?))
118            }
119            ColumnType::MYSQL_TYPE_TINY => {
120                if unsigned {
121                    Ok(ValueInner::UInt(u64::from(input.read_u8()?)))
122                } else {
123                    Ok(ValueInner::Int(i64::from(input.read_i8()?)))
124                }
125            }
126            ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => {
127                if unsigned {
128                    Ok(ValueInner::UInt(u64::from(
129                        input.read_u16::<LittleEndian>()?,
130                    )))
131                } else {
132                    Ok(ValueInner::Int(i64::from(
133                        input.read_i16::<LittleEndian>()?,
134                    )))
135                }
136            }
137            ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
138                if unsigned {
139                    Ok(ValueInner::UInt(u64::from(
140                        input.read_u32::<LittleEndian>()?,
141                    )))
142                } else {
143                    Ok(ValueInner::Int(i64::from(
144                        input.read_i32::<LittleEndian>()?,
145                    )))
146                }
147            }
148            ColumnType::MYSQL_TYPE_LONGLONG => {
149                if unsigned {
150                    Ok(ValueInner::UInt(input.read_u64::<LittleEndian>()?))
151                } else {
152                    Ok(ValueInner::Int(input.read_i64::<LittleEndian>()?))
153                }
154            }
155            ColumnType::MYSQL_TYPE_FLOAT => {
156                let f = input.read_f32::<LittleEndian>()?;
157                Ok(ValueInner::Double(f64::from(f)))
158            }
159            ColumnType::MYSQL_TYPE_DOUBLE => {
160                Ok(ValueInner::Double(input.read_f64::<LittleEndian>()?))
161            }
162            ColumnType::MYSQL_TYPE_TIMESTAMP | ColumnType::MYSQL_TYPE_DATETIME => {
163                let len = input.read_u8()?;
164                Ok(ValueInner::Datetime(read_bytes!(input, len)?))
165            }
166            ColumnType::MYSQL_TYPE_DATE => {
167                let len = input.read_u8()?;
168                Ok(ValueInner::Date(read_bytes!(input, len)?))
169            }
170            ColumnType::MYSQL_TYPE_TIME => {
171                let len = input.read_u8()?;
172                Ok(ValueInner::Time(read_bytes!(input, len)?))
173            }
174            ColumnType::MYSQL_TYPE_NULL => Ok(ValueInner::NULL),
175            ct => Err(io::Error::new(
176                io::ErrorKind::InvalidInput,
177                format!("unknown column type {:?}", ct),
178            )),
179        }
180    }
181}
182
183// NOTE: these should all be TryFrom
184macro_rules! impl_into {
185    ($t:ty, $($variant:path),*) => {
186        impl<'a> From<Value<'a>> for $t {
187            fn from(val: Value<'a>) -> Self {
188                match val.0 {
189                    $($variant(v) => v as $t),*,
190                    v => panic!(concat!("invalid type conversion from {:?} to ", stringify!($t)), v)
191                }
192            }
193        }
194    }
195}
196
197impl_into!(u8, ValueInner::UInt, ValueInner::Int);
198impl_into!(u16, ValueInner::UInt, ValueInner::Int);
199impl_into!(u32, ValueInner::UInt, ValueInner::Int);
200impl_into!(u64, ValueInner::UInt);
201impl_into!(i8, ValueInner::UInt, ValueInner::Int);
202impl_into!(i16, ValueInner::UInt, ValueInner::Int);
203impl_into!(i32, ValueInner::UInt, ValueInner::Int);
204impl_into!(i64, ValueInner::Int);
205impl_into!(f32, ValueInner::Double);
206impl_into!(f64, ValueInner::Double);
207impl_into!(&'a [u8], ValueInner::Bytes);
208
209impl<'a> From<Value<'a>> for &'a str {
210    fn from(val: Value<'a>) -> Self {
211        if let ValueInner::Bytes(v) = val.0 {
212            ::std::str::from_utf8(v).unwrap()
213        } else {
214            panic!("invalid type conversion from {:?} to string", val)
215        }
216    }
217}
218
219use chrono::{NaiveDate, NaiveDateTime};
220impl<'a> From<Value<'a>> for NaiveDate {
221    fn from(val: Value<'a>) -> Self {
222        if let ValueInner::Date(mut v) = val.0 {
223            assert_eq!(v.len(), 4);
224            NaiveDate::from_ymd_opt(
225                i32::from(v.read_u16::<LittleEndian>().unwrap()),
226                u32::from(v.read_u8().unwrap()),
227                u32::from(v.read_u8().unwrap()),
228            )
229            .unwrap()
230        } else {
231            panic!("invalid type conversion from {:?} to date", val)
232        }
233    }
234}
235
236impl<'a> From<Value<'a>> for NaiveDateTime {
237    fn from(val: Value<'a>) -> Self {
238        to_naive_datetime(val).unwrap()
239    }
240}
241
242pub fn to_naive_datetime(val: Value) -> Result<NaiveDateTime, io::Error> {
243    let ValueInner::Datetime(v) = val.0 else {
244        return Err(io::Error::new(
245            io::ErrorKind::InvalidData,
246            format!("invalid type conversion from {:?} to datetime", val),
247        ));
248    };
249
250    let len = v.len();
251
252    let v = &mut io::Cursor::new(v);
253
254    // unwrap safety: guarded by `v.len()` check
255    fn read_ymd(v: &mut io::Cursor<&[u8]>) -> (i32, u32, u32) {
256        let y = i32::from(v.read_u16::<LittleEndian>().unwrap());
257        let m = u32::from(v.read_u8().unwrap());
258        let d = u32::from(v.read_u8().unwrap());
259        (y, m, d)
260    }
261
262    // unwrap safety: guarded by `v.len()` check
263    fn read_hms(v: &mut io::Cursor<&[u8]>) -> (u32, u32, u32) {
264        let h = u32::from(v.read_u8().unwrap());
265        let m = u32::from(v.read_u8().unwrap());
266        let s = u32::from(v.read_u8().unwrap());
267        (h, m, s)
268    }
269
270    // Timestamp binary encoding:
271    // https://mariadb.com/kb/en/resultset-row/#timestamp-binary-encoding
272    let d = match len {
273        0 => {
274            return Err(io::Error::new(
275                io::ErrorKind::InvalidData,
276                "'0000-00-00 00:00:00' is a valid timestamp value but not representable by NaiveDateTime!",
277            ))
278        }
279        4 => {
280            let (y, m, d) = read_ymd(v);
281            NaiveDate::from_ymd_opt(y, m, d).and_then(|x| x.and_hms_opt(0, 0, 0))
282        }
283        7 => {
284            let (y, m, d) = read_ymd(v);
285            NaiveDate::from_ymd_opt(y, m, d).and_then(|x| {
286                let (h, m, s) = read_hms(v);
287                x.and_hms_opt(h, m, s)
288            })
289        }
290        11 => {
291            let (y, m, d) = read_ymd(v);
292            NaiveDate::from_ymd_opt(y, m, d).and_then(|x| {
293                let (h, m, s) = read_hms(v);
294
295                // unwrap safety: guarded by `v.len()` check
296                let us = v.read_u32::<LittleEndian>().unwrap();
297
298                x.and_hms_micro_opt(h, m, s, us)
299            })
300        }
301        _ => {
302            return Err(io::Error::new(
303                io::ErrorKind::InvalidData,
304                format!("illegal timestamp value length: {}", len),
305            ))
306        }
307    };
308
309    d.ok_or_else(|| {
310        io::Error::new(
311            io::ErrorKind::InvalidData,
312            format!("invalid data conversion from {:?} to datetime", val),
313        )
314    })
315}
316
317use std::time::Duration;
318
319impl<'a> From<Value<'a>> for Duration {
320    fn from(val: Value<'a>) -> Self {
321        if let ValueInner::Time(mut v) = val.0 {
322            assert!(v.is_empty() || v.len() == 8 || v.len() == 12);
323
324            if v.is_empty() {
325                return Duration::from_secs(0);
326            }
327
328            let neg = v.read_u8().unwrap();
329            if neg != 0u8 {
330                unimplemented!();
331            }
332
333            let days = u64::from(v.read_u32::<LittleEndian>().unwrap());
334            let hours = u64::from(v.read_u8().unwrap());
335            let minutes = u64::from(v.read_u8().unwrap());
336            let seconds = u64::from(v.read_u8().unwrap());
337            let micros = if v.len() == 12 {
338                v.read_u32::<LittleEndian>().unwrap()
339            } else {
340                0
341            };
342
343            Duration::new(
344                days * 86_400 + hours * 3_600 + minutes * 60 + seconds,
345                micros * 1_000,
346            )
347        } else {
348            panic!("invalid type conversion from {:?} to datetime", val)
349        }
350    }
351}