1use crate::myc::constants::{ColumnFlags, ColumnType};
2use crate::myc::io::WriteMysqlExt;
3use crate::Column;
4use byteorder::{LittleEndian, WriteBytesExt};
5use std::io::{self, ErrorKind::Other, Write};
6
7pub trait ToMysqlValue {
9 fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()>;
11
12 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()>;
14
15 fn is_null(&self) -> bool {
17 false
18 }
19}
20
21macro_rules! mysql_text_trivial {
22 () => {
23 fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
24 w.write_lenenc_str(format!("{}", self).as_bytes())
25 .map(|_| ())
26 }
27 };
28}
29
30use std::fmt;
31fn bad<V: fmt::Debug>(v: V, c: &Column) -> io::Error {
32 io::Error::new(
33 io::ErrorKind::InvalidData,
34 format!("tried to use {:?} as {:?}", v, c.coltype),
35 )
36}
37
38impl<T> ToMysqlValue for Option<T>
39where
40 T: ToMysqlValue,
41{
42 fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
43 if let Some(ref v) = *self {
44 v.to_mysql_text(w)
45 } else {
46 w.write_u8(0xFB)
47 }
48 }
49
50 fn to_mysql_bin<W: Write>(&self, w: &mut W, ct: &Column) -> io::Result<()> {
51 if let Some(ref v) = *self {
52 v.to_mysql_bin(w, ct)
53 } else {
54 unreachable!();
56 }
57 }
58
59 fn is_null(&self) -> bool {
60 self.is_none()
61 }
62}
63
64macro_rules! like_try_into {
68 ($self:ident, $source:ty = $target:ty, $w:ident, $m:ident, $c:ident) => {{
69 let min = <$target>::min_value() as $source;
70 let max = <$target>::max_value() as $source;
71 if *$self <= max && *$self >= min {
72 $w.$m(*$self as $target)
73 } else {
74 Err(bad($self, $c))
75 }
76 }};
77 ($self:ident, $source:ty => $target:ty, $w:ident, $m:ident, $c:ident) => {{
78 let min = <$target>::min_value() as $source;
79 let max = <$target>::max_value() as $source;
80 if *$self <= max && *$self >= min {
81 $w.$m::<LittleEndian>(*$self as $target)
82 } else {
83 Err(bad($self, $c))
84 }
85 }};
86}
87
88macro_rules! forgiving_numeric {
89 ($t:ty) => {
90 impl ToMysqlValue for $t {
91 mysql_text_trivial!();
92 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
93 let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
94 match c.coltype {
95 ColumnType::MYSQL_TYPE_LONGLONG => {
96 if signed {
97 like_try_into!(self, $t => i64, w, write_i64, c)
98 } else {
99 like_try_into!(self, $t => u64, w, write_u64, c)
100 }
101 }
102 ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
103 if signed {
104 like_try_into!(self, $t => i32, w, write_i32, c)
105 } else {
106 like_try_into!(self, $t => u32, w, write_u32, c)
107 }
108 }
109 ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => {
110 if signed {
111 like_try_into!(self, $t => i16, w, write_i16, c)
112 } else {
113 like_try_into!(self, $t => u16, w, write_u16, c)
114 }
115 }
116 ColumnType::MYSQL_TYPE_TINY => {
117 if signed {
118 like_try_into!(self, $t = i8, w, write_i8, c)
119 } else {
120 like_try_into!(self, $t = u8, w, write_u8, c)
121 }
122 }
123 _ => Err(bad(self, c)),
124 }
125 }
126 }
127 };
128}
129
130forgiving_numeric!(usize);
131forgiving_numeric!(isize);
132
133impl ToMysqlValue for u8 {
134 mysql_text_trivial!();
135 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
136 let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
137 match c.coltype {
138 ColumnType::MYSQL_TYPE_LONGLONG => {
139 if signed {
140 w.write_i64::<LittleEndian>(i64::from(*self))
141 } else {
142 w.write_u64::<LittleEndian>(u64::from(*self))
143 }
144 }
145 ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
146 if signed {
147 w.write_i32::<LittleEndian>(i32::from(*self))
148 } else {
149 w.write_u32::<LittleEndian>(u32::from(*self))
150 }
151 }
152 ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => {
153 if signed {
154 w.write_i16::<LittleEndian>(i16::from(*self))
155 } else {
156 w.write_u16::<LittleEndian>(u16::from(*self))
157 }
158 }
159 ColumnType::MYSQL_TYPE_TINY => {
160 assert!(!signed);
161 w.write_u8(*self)
162 }
163 _ => Err(bad(self, c)),
164 }
165 }
166}
167
168impl ToMysqlValue for i8 {
169 mysql_text_trivial!();
170 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
171 let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
172 match c.coltype {
173 ColumnType::MYSQL_TYPE_LONGLONG => {
174 if signed {
175 w.write_i64::<LittleEndian>(i64::from(*self))
176 } else {
177 w.write_u64::<LittleEndian>(*self as u64)
178 }
179 }
180 ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
181 if signed {
182 w.write_i32::<LittleEndian>(i32::from(*self))
183 } else {
184 w.write_u32::<LittleEndian>(*self as u32)
185 }
186 }
187 ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => {
188 if signed {
189 w.write_i16::<LittleEndian>(i16::from(*self))
190 } else {
191 w.write_u16::<LittleEndian>(*self as u16)
192 }
193 }
194 ColumnType::MYSQL_TYPE_TINY => {
195 assert!(signed);
196 w.write_i8(*self)
197 }
198 _ => Err(bad(self, c)),
199 }
200 }
201}
202
203impl ToMysqlValue for u16 {
204 mysql_text_trivial!();
205 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
206 let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
207 match c.coltype {
208 ColumnType::MYSQL_TYPE_LONGLONG => {
209 if signed {
210 w.write_i64::<LittleEndian>(i64::from(*self))
211 } else {
212 w.write_u64::<LittleEndian>(u64::from(*self))
213 }
214 }
215 ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
216 if signed {
217 w.write_i32::<LittleEndian>(i32::from(*self))
218 } else {
219 w.write_u32::<LittleEndian>(u32::from(*self))
220 }
221 }
222 ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => {
223 assert!(!signed);
224 w.write_u16::<LittleEndian>(*self)
225 }
226 _ => Err(bad(self, c)),
227 }
228 }
229}
230
231impl ToMysqlValue for i16 {
232 mysql_text_trivial!();
233 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
234 let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
235 match c.coltype {
236 ColumnType::MYSQL_TYPE_LONGLONG => {
237 if signed {
238 w.write_i64::<LittleEndian>(i64::from(*self))
239 } else {
240 w.write_u64::<LittleEndian>(*self as u64)
241 }
242 }
243 ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
244 if signed {
245 w.write_i32::<LittleEndian>(i32::from(*self))
246 } else {
247 w.write_u32::<LittleEndian>(*self as u32)
248 }
249 }
250 ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => {
251 assert!(signed);
252 w.write_i16::<LittleEndian>(*self)
253 }
254 _ => Err(bad(self, c)),
255 }
256 }
257}
258
259impl ToMysqlValue for u32 {
260 mysql_text_trivial!();
261 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
262 let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
263 match c.coltype {
264 ColumnType::MYSQL_TYPE_LONGLONG => {
265 if signed {
266 w.write_i64::<LittleEndian>(i64::from(*self))
267 } else {
268 w.write_u64::<LittleEndian>(u64::from(*self))
269 }
270 }
271 ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
272 assert!(!signed);
273 w.write_u32::<LittleEndian>(*self)
274 }
275 _ => Err(bad(self, c)),
276 }
277 }
278}
279
280impl ToMysqlValue for i32 {
281 mysql_text_trivial!();
282 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
283 let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
284 match c.coltype {
285 ColumnType::MYSQL_TYPE_LONGLONG => {
286 if signed {
287 w.write_i64::<LittleEndian>(i64::from(*self))
288 } else {
289 w.write_u64::<LittleEndian>(*self as u64)
290 }
291 }
292 ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
293 assert!(signed);
294 w.write_i32::<LittleEndian>(*self)
295 }
296 _ => Err(bad(self, c)),
297 }
298 }
299}
300
301impl ToMysqlValue for u64 {
302 mysql_text_trivial!();
303 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
304 let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
305 match c.coltype {
306 ColumnType::MYSQL_TYPE_LONGLONG => {
307 assert!(!signed);
308 w.write_u64::<LittleEndian>(*self)
309 }
310 _ => Err(bad(self, c)),
311 }
312 }
313}
314
315impl ToMysqlValue for i64 {
316 mysql_text_trivial!();
317 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
318 let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
319 match c.coltype {
320 ColumnType::MYSQL_TYPE_LONGLONG => {
321 assert!(signed);
322 w.write_i64::<LittleEndian>(*self)
323 }
324 _ => Err(bad(self, c)),
325 }
326 }
327}
328
329impl ToMysqlValue for f32 {
330 mysql_text_trivial!();
331 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
332 match c.coltype {
333 ColumnType::MYSQL_TYPE_DOUBLE => w.write_f64::<LittleEndian>(f64::from(*self)),
334 ColumnType::MYSQL_TYPE_FLOAT => w.write_f32::<LittleEndian>(*self),
335 _ => Err(bad(self, c)),
336 }
337 }
338}
339
340impl ToMysqlValue for f64 {
341 mysql_text_trivial!();
342 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
343 match c.coltype {
344 ColumnType::MYSQL_TYPE_DOUBLE => w.write_f64::<LittleEndian>(*self),
345 _ => Err(bad(self, c)),
346 }
347 }
348}
349
350impl ToMysqlValue for String {
351 fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
352 self.as_bytes().to_mysql_text(w)
353 }
354 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
355 self.as_bytes().to_mysql_bin(w, c)
356 }
357}
358
359impl ToMysqlValue for str {
360 fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
361 self.as_bytes().to_mysql_text(w)
362 }
363 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
364 self.as_bytes().to_mysql_bin(w, c)
365 }
366}
367
368impl ToMysqlValue for [u8] {
369 fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
370 w.write_lenenc_str(self).map(|_| ())
371 }
372 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
373 match c.coltype {
374 ColumnType::MYSQL_TYPE_STRING
375 | ColumnType::MYSQL_TYPE_VAR_STRING
376 | ColumnType::MYSQL_TYPE_BLOB
377 | ColumnType::MYSQL_TYPE_TINY_BLOB
378 | ColumnType::MYSQL_TYPE_MEDIUM_BLOB
379 | ColumnType::MYSQL_TYPE_LONG_BLOB
380 | ColumnType::MYSQL_TYPE_SET
381 | ColumnType::MYSQL_TYPE_ENUM
382 | ColumnType::MYSQL_TYPE_DECIMAL
383 | ColumnType::MYSQL_TYPE_VARCHAR
384 | ColumnType::MYSQL_TYPE_BIT
385 | ColumnType::MYSQL_TYPE_NEWDECIMAL
386 | ColumnType::MYSQL_TYPE_GEOMETRY
387 | ColumnType::MYSQL_TYPE_JSON => w.write_lenenc_str(self).map(|_| ()),
388 _ => Err(bad(self, c)),
389 }
390 }
391}
392
393impl ToMysqlValue for Vec<u8> {
394 fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
395 (self[..]).to_mysql_text(w)
396 }
397 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
398 (self[..]).to_mysql_bin(w, c)
399 }
400}
401
402impl<'a, T> ToMysqlValue for &'a T
403where
404 T: ToMysqlValue + ?Sized,
405{
406 fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
407 (*self).to_mysql_text(w)
408 }
409 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
410 (*self).to_mysql_bin(w, c)
411 }
412}
413
414use chrono::{self, Datelike, NaiveDate, NaiveDateTime, Timelike};
415impl ToMysqlValue for NaiveDate {
416 fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
417 w.write_lenenc_str(
418 format!("{:04}-{:02}-{:02}", self.year(), self.month(), self.day()).as_bytes(),
419 )
420 .map(|_| ())
421 }
422 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
423 match c.coltype {
424 ColumnType::MYSQL_TYPE_DATE => {
425 w.write_u8(4u8)?;
426 w.write_u16::<LittleEndian>(self.year() as u16)?;
427 w.write_u8(self.month() as u8)?;
428 w.write_u8(self.day() as u8)
429 }
430 _ => Err(bad(self, c)),
431 }
432 }
433}
434
435impl ToMysqlValue for NaiveDateTime {
436 fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
437 let us = self.nanosecond() / 1_000;
438
439 if us != 0 {
440 w.write_lenenc_str(
441 format!(
442 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:06}",
443 self.year(),
444 self.month(),
445 self.day(),
446 self.hour(),
447 self.minute(),
448 self.second(),
449 us
450 )
451 .as_bytes(),
452 )
453 .map(|_| ())
454 } else {
455 w.write_lenenc_str(
456 format!(
457 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
458 self.year(),
459 self.month(),
460 self.day(),
461 self.hour(),
462 self.minute(),
463 self.second()
464 )
465 .as_bytes(),
466 )
467 .map(|_| ())
468 }
469 }
470 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
471 match c.coltype {
472 ColumnType::MYSQL_TYPE_DATETIME | ColumnType::MYSQL_TYPE_TIMESTAMP => {
473 let us = self.nanosecond() / 1_000;
474
475 if us != 0 {
476 w.write_u8(11u8)?;
477 } else {
478 w.write_u8(7u8)?;
479 }
480 w.write_u16::<LittleEndian>(self.year() as u16)?;
481 w.write_u8(self.month() as u8)?;
482 w.write_u8(self.day() as u8)?;
483 w.write_u8(self.hour() as u8)?;
484 w.write_u8(self.minute() as u8)?;
485 w.write_u8(self.second() as u8)?;
486
487 if us != 0 {
488 w.write_u32::<LittleEndian>(us)?;
489 }
490 Ok(())
491 }
492 _ => Err(bad(self, c)),
493 }
494 }
495}
496
497use std::time::Duration;
498impl ToMysqlValue for Duration {
499 fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
500 let s = self.as_secs();
501 let h = s / 3600;
505 let m = (s % 3600) / 60;
506 let s = s % 60;
507 let us = self.subsec_micros();
508 if us != 0 {
509 w.write_lenenc_str(format!("{:02}:{:02}:{:02}.{:06}", h, m, s, us).as_bytes())
510 .map(|_| ())
511 } else {
512 w.write_lenenc_str(format!("{:02}:{:02}:{:02}", h, m, s).as_bytes())
513 .map(|_| ())
514 }
515 }
516
517 #[allow(clippy::many_single_char_names)]
518 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
519 let s = self.as_secs();
520 let d = s / (24 * 3600);
521 assert!(d <= 34);
522 let h = (s % (24 * 3600)) / 3600;
523 let m = (s % 3600) / 60;
524 let s = s % 60;
525 let us = self.subsec_micros();
526
527 match c.coltype {
528 ColumnType::MYSQL_TYPE_TIME => {
529 if self.as_secs() == 0 && us == 0 {
530 w.write_u8(0u8)?;
531 } else {
532 if us != 0 {
533 w.write_u8(12u8)?;
534 } else {
535 w.write_u8(8u8)?;
536 }
537
538 w.write_u8(0u8)?; w.write_u32::<LittleEndian>(d as u32)?;
540 w.write_u8(h as u8)?;
541 w.write_u8(m as u8)?;
542 w.write_u8(s as u8)?;
543
544 if us != 0 {
545 w.write_u32::<LittleEndian>(us)?;
546 }
547 }
548 Ok(())
549 }
550 _ => Err(bad(self, c)),
551 }
552 }
553}
554
555impl ToMysqlValue for myc::value::Value {
556 #[allow(clippy::many_single_char_names)]
557 fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
558 match *self {
559 myc::value::Value::NULL => None::<u8>.to_mysql_text(w),
560 myc::value::Value::Bytes(ref bytes) => bytes.to_mysql_text(w),
561 myc::value::Value::Int(n) => n.to_mysql_text(w),
562 myc::value::Value::UInt(n) => n.to_mysql_text(w),
563 myc::value::Value::Float(f) => f.to_mysql_text(w),
564 myc::value::Value::Double(f) => f.to_mysql_text(w),
565 myc::value::Value::Date(y, mo, d, h, mi, s, us) => {
566 NaiveDate::from_ymd_opt(i32::from(y), u32::from(mo), u32::from(d))
567 .ok_or_else(|| {
568 io::Error::new(Other, format!("invalid date: y {} mo {} d {}", y, mo, d))
569 })?
570 .and_hms_micro_opt(u32::from(h), u32::from(mi), u32::from(s), us)
571 .ok_or_else(|| {
572 io::Error::new(
573 Other,
574 format!("invalid date: h {} mi {} s {} us {}", h, mi, s, us),
575 )
576 })?
577 .to_mysql_text(w)
578 }
579 myc::value::Value::Time(neg, d, h, m, s, us) => {
580 if neg {
581 return Err(io::Error::new(Other, "negative times not yet supported"));
582 }
583 (chrono::Duration::days(i64::from(d))
584 + chrono::Duration::hours(i64::from(h))
585 + chrono::Duration::minutes(i64::from(m))
586 + chrono::Duration::seconds(i64::from(s))
587 + chrono::Duration::microseconds(i64::from(us)))
588 .to_std()
589 .expect("only positive times at the moment")
590 .to_mysql_text(w)
591 }
592 }
593 }
594
595 #[allow(clippy::many_single_char_names)]
596 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
597 match *self {
598 myc::value::Value::NULL => unreachable!(),
599 myc::value::Value::Bytes(ref bytes) => bytes.to_mysql_bin(w, c),
600 myc::value::Value::Int(n) => {
601 let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
607 if signed {
608 if n >= i64::from(i8::min_value()) && n <= i64::from(i8::max_value()) {
609 (n as i8).to_mysql_bin(w, c)
610 } else if n >= i64::from(i16::min_value()) && n <= i64::from(i16::max_value()) {
611 (n as i16).to_mysql_bin(w, c)
612 } else if n >= i64::from(i32::min_value()) && n <= i64::from(i32::max_value()) {
613 (n as i32).to_mysql_bin(w, c)
614 } else {
615 n.to_mysql_bin(w, c)
616 }
617 } else if n < 0 {
618 Err(bad(self, c))
619 } else if n <= i64::from(u8::max_value()) {
620 (n as u8).to_mysql_bin(w, c)
621 } else if n <= i64::from(u16::max_value()) {
622 (n as u16).to_mysql_bin(w, c)
623 } else if n <= i64::from(u32::max_value()) {
624 (n as u32).to_mysql_bin(w, c)
625 } else {
626 (n as u64).to_mysql_bin(w, c)
628 }
629 }
630 myc::value::Value::UInt(n) => {
631 n.to_mysql_bin(w, c)
633 }
634 myc::value::Value::Float(f) => f.to_mysql_bin(w, c),
635 myc::value::Value::Double(f) => f.to_mysql_bin(w, c),
636 myc::value::Value::Date(y, mo, d, h, mi, s, us) => {
637 NaiveDate::from_ymd_opt(i32::from(y), u32::from(mo), u32::from(d))
638 .ok_or_else(|| {
639 io::Error::new(Other, format!("invalid date: y {} mo {} d {}", y, mo, d))
640 })?
641 .and_hms_micro_opt(u32::from(h), u32::from(mi), u32::from(s), us)
642 .ok_or_else(|| {
643 io::Error::new(
644 Other,
645 format!("invalid date: h {} mi {} s {} us {}", h, mi, s, us),
646 )
647 })?
648 .to_mysql_bin(w, c)
649 }
650 myc::value::Value::Time(neg, d, h, m, s, us) => {
651 if neg {
652 return Err(io::Error::new(Other, "negative times not yet supported"));
653 }
654 (chrono::Duration::days(i64::from(d))
655 + chrono::Duration::hours(i64::from(h))
656 + chrono::Duration::minutes(i64::from(m))
657 + chrono::Duration::seconds(i64::from(s))
658 + chrono::Duration::microseconds(i64::from(us)))
659 .to_std()
660 .expect("only positive times at the moment")
661 .to_mysql_bin(w, c)
662 }
663 }
664 }
665
666 fn is_null(&self) -> bool {
667 matches!(*self, myc::value::Value::NULL)
668 }
669}
670
671#[cfg(test)]
672#[allow(unused_imports)]
673mod tests {
674 use super::ToMysqlValue;
675 use crate::myc::value;
676 use crate::myc::value::convert::from_value;
677 use crate::myc::value::Value;
678 use crate::{Column, ColumnFlags, ColumnType};
679 use chrono::{self, TimeZone};
680 use std::time;
681
682 mod roundtrip_text {
683 use super::*;
684
685 use myc::{
686 io::ParseBuf,
687 proto::MyDeserialize,
688 value::{convert::FromValue, TextValue, ValueDeserializer},
689 };
690
691 macro_rules! rt {
692 ($name:ident, $t:ty, $v:expr) => {
693 #[test]
694 fn $name() {
695 let mut data = Vec::new();
696 let v: $t = $v;
697 v.to_mysql_text(&mut data).unwrap();
698 let mut pb = ParseBuf(&mut data[..]);
699 assert_eq!(
700 <$t>::from_value(
701 ValueDeserializer::<TextValue>::deserialize((), &mut pb)
702 .unwrap()
703 .0,
704 ),
705 v
706 );
707 }
708 };
709 }
710
711 rt!(u8_one, u8, 1);
712 rt!(i8_one, i8, 1);
713 rt!(u16_one, u16, 1);
714 rt!(i16_one, i16, 1);
715 rt!(u32_one, u32, 1);
716 rt!(i32_one, i32, 1);
717 rt!(u64_one, u64, 1);
718 rt!(i64_one, i64, 1);
719 rt!(f32_one, f32, 1.0);
720 rt!(f64_one, f64, 1.0);
721
722 rt!(u8_max, u8, u8::max_value());
723 rt!(i8_max, i8, i8::max_value());
724 rt!(u16_max, u16, u16::max_value());
725 rt!(i16_max, i16, i16::max_value());
726 rt!(u32_max, u32, u32::max_value());
727 rt!(i32_max, i32, i32::max_value());
728 rt!(u64_max, u64, u64::max_value());
729 rt!(i64_max, i64, i64::max_value());
730
731 rt!(opt_none, Option<u8>, None);
732 rt!(opt_some, Option<u8>, Some(1));
733
734 rt!(time, chrono::NaiveDate, chrono::Local::now().date_naive());
735 rt!(
736 datetime,
737 chrono::NaiveDateTime,
738 chrono::Utc
739 .with_ymd_and_hms(1989, 12, 7, 8, 0, 4)
740 .unwrap()
741 .naive_utc()
742 );
743 rt!(dur, time::Duration, time::Duration::from_secs(1893));
744 rt!(dur_micro, time::Duration, time::Duration::new(1893, 5000));
745 rt!(dur_zero, time::Duration, time::Duration::from_secs(0));
746 rt!(bytes, Vec<u8>, vec![0x42, 0x00, 0x1a]);
747 rt!(string, String, "foobar".to_owned());
748 }
749
750 mod roundtrip_bin {
751 use super::*;
752
753 use myc::{
754 io::ParseBuf,
755 proto::MyDeserialize,
756 value::{convert::FromValue, BinValue, ValueDeserializer},
757 };
758
759 macro_rules! rt {
760 ($name:ident, $t:ty, $v:expr, $ct:expr) => {
761 rt!($name, $t, $v, $ct, false);
762 };
763 ($name:ident, $t:ty, $v:expr, $ct:expr, $sig:expr) => {
764 #[test]
765 fn $name() {
766 let mut data = Vec::new();
767 let mut col = Column {
768 table: String::new(),
769 column: String::new(),
770 coltype: $ct,
771 colflags: ColumnFlags::empty(),
772 };
773
774 if !$sig {
775 col.colflags.insert(ColumnFlags::UNSIGNED_FLAG);
776 }
777
778 let v: $t = $v;
779 v.to_mysql_bin(&mut data, &col).unwrap();
780 let mut pb = ParseBuf(&mut data[..]);
781 assert_eq!(
782 <$t>::from_value(
783 ValueDeserializer::<BinValue>::deserialize(
784 (
785 $ct,
786 if $sig {
787 ColumnFlags::empty()
788 } else {
789 ColumnFlags::UNSIGNED_FLAG
790 }
791 ),
792 &mut pb
793 )
794 .unwrap()
795 .0,
796 ),
797 v
798 );
799 }
800 };
801 }
802
803 rt!(u8_one, u8, 1, ColumnType::MYSQL_TYPE_TINY, false);
804 rt!(i8_one, i8, 1, ColumnType::MYSQL_TYPE_TINY, true);
805 rt!(u8_one_short, u8, 1, ColumnType::MYSQL_TYPE_SHORT, false);
806 rt!(i8_one_short, i8, 1, ColumnType::MYSQL_TYPE_SHORT, true);
807 rt!(u8_one_long, u8, 1, ColumnType::MYSQL_TYPE_LONG, false);
808 rt!(i8_one_long, i8, 1, ColumnType::MYSQL_TYPE_LONG, true);
809 rt!(
810 u8_one_longlong,
811 u8,
812 1,
813 ColumnType::MYSQL_TYPE_LONGLONG,
814 false
815 );
816 rt!(
817 i8_one_longlong,
818 i8,
819 1,
820 ColumnType::MYSQL_TYPE_LONGLONG,
821 true
822 );
823 rt!(u16_one, u16, 1, ColumnType::MYSQL_TYPE_SHORT, false);
824 rt!(i16_one, i16, 1, ColumnType::MYSQL_TYPE_SHORT, true);
825 rt!(u16_one_long, u16, 1, ColumnType::MYSQL_TYPE_LONG, false);
826 rt!(i16_one_long, i16, 1, ColumnType::MYSQL_TYPE_LONG, true);
827 rt!(
828 u16_one_longlong,
829 u16,
830 1,
831 ColumnType::MYSQL_TYPE_LONGLONG,
832 false
833 );
834 rt!(
835 i16_one_longlong,
836 i16,
837 1,
838 ColumnType::MYSQL_TYPE_LONGLONG,
839 true
840 );
841 rt!(u32_one_long, u32, 1, ColumnType::MYSQL_TYPE_LONG, false);
842 rt!(i32_one_long, i32, 1, ColumnType::MYSQL_TYPE_LONG, true);
843 rt!(
844 u32_one_longlong,
845 u32,
846 1,
847 ColumnType::MYSQL_TYPE_LONGLONG,
848 false
849 );
850 rt!(
851 i32_one_longlong,
852 i32,
853 1,
854 ColumnType::MYSQL_TYPE_LONGLONG,
855 true
856 );
857 rt!(u64_one, u64, 1, ColumnType::MYSQL_TYPE_LONGLONG, false);
858 rt!(i64_one, i64, 1, ColumnType::MYSQL_TYPE_LONGLONG, true);
859
860 rt!(f32_one, f32, 1.0, ColumnType::MYSQL_TYPE_FLOAT, false);
861 rt!(f64_one, f64, 1.0, ColumnType::MYSQL_TYPE_DOUBLE, false);
862
863 rt!(
864 u8_max,
865 u8,
866 u8::max_value(),
867 ColumnType::MYSQL_TYPE_TINY,
868 false
869 );
870 rt!(
871 i8_max,
872 i8,
873 i8::max_value(),
874 ColumnType::MYSQL_TYPE_TINY,
875 true
876 );
877 rt!(
878 u16_max,
879 u16,
880 u16::max_value(),
881 ColumnType::MYSQL_TYPE_SHORT,
882 false
883 );
884 rt!(
885 i16_max,
886 i16,
887 i16::max_value(),
888 ColumnType::MYSQL_TYPE_SHORT,
889 true
890 );
891 rt!(
892 u32_max,
893 u32,
894 u32::max_value(),
895 ColumnType::MYSQL_TYPE_LONG,
896 false
897 );
898 rt!(
899 i32_max,
900 i32,
901 i32::max_value(),
902 ColumnType::MYSQL_TYPE_LONG,
903 true
904 );
905 rt!(
906 u64_max,
907 u64,
908 u64::max_value(),
909 ColumnType::MYSQL_TYPE_LONGLONG,
910 false
911 );
912 rt!(
913 i64_max,
914 i64,
915 i64::max_value(),
916 ColumnType::MYSQL_TYPE_LONGLONG,
917 true
918 );
919
920 rt!(opt_some, Option<u8>, Some(1), ColumnType::MYSQL_TYPE_TINY);
921
922 rt!(
923 time,
924 chrono::NaiveDate,
925 chrono::Local::now().date_naive(),
926 ColumnType::MYSQL_TYPE_DATE
927 );
928 rt!(
929 datetime,
930 chrono::NaiveDateTime,
931 chrono::Utc
932 .with_ymd_and_hms(1989, 12, 7, 8, 0, 4)
933 .unwrap()
934 .naive_utc(),
935 ColumnType::MYSQL_TYPE_DATETIME
936 );
937 rt!(
938 dur,
939 time::Duration,
940 time::Duration::from_secs(1893),
941 ColumnType::MYSQL_TYPE_TIME
942 );
943 rt!(
944 bytes,
945 Vec<u8>,
946 vec![0x42, 0x00, 0x1a],
947 ColumnType::MYSQL_TYPE_BLOB
948 );
949 rt!(
950 string,
951 String,
952 "foobar".to_owned(),
953 ColumnType::MYSQL_TYPE_STRING
954 );
955 }
956}