1use crate::myc::constants::ColumnType;
2use crate::myc::io::ReadMysqlExt;
3use byteorder::{LittleEndian, ReadBytesExt};
4use std::io;
5
6#[derive(Debug, PartialEq, Copy, Clone)]
8pub struct Value<'a>(ValueInner<'a>);
9
10#[derive(Debug, PartialEq, Copy, Clone)]
12pub enum ValueInner<'a> {
13 NULL,
15 Bytes(&'a [u8]),
17 Int(i64),
19 UInt(u64),
21 Double(f64),
23 Date(&'a [u8]),
26 Time(&'a [u8]),
29 Datetime(&'a [u8]),
33}
34
35impl<'a> Value<'a> {
36 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 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
168macro_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}