msql_srv/value/
encode.rs

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
7/// Implementors of this trait can be sent as a single resultset value to a MySQL/MariaDB client.
8pub trait ToMysqlValue {
9    /// Encode value using the text-based protocol.
10    fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()>;
11
12    /// Encode value using the binary protocol.
13    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()>;
14
15    /// Is this value NULL?
16    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            // should be handled by NULL map
55            unreachable!();
56        }
57    }
58
59    fn is_null(&self) -> bool {
60        self.is_none()
61    }
62}
63
64// NOTE: these rules can all go away when TryFrom stabilizes
65//       see https://github.com/jonhoo/msql-srv/commit/13e5e753e5042a42cc45ad57c2b760561da2fb50
66// NOTE: yes, I know the = / => distinction is ugly
67macro_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 d = s / (24 * 3600);
502        // assert!(d <= 34);
503        //let h = (s % (24 * 3600)) / 3600;
504        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)?; // positive only (for now)
539                    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                // we *could* just delegate to i64 impl here, but then you couldn't use myc::value::Value
602                // and return, say, a short. also, myc uses i64 for *every* number type, *except*
603                // u64, so we even need to coerce across unsigned :( the good news is that our
604                // impls for numbers auto-upgrade to wider coltypes, so we can just downcast to the
605                // smallest containing type, and then call on that
606                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                    // must work since u64::max_value() > i64::max_value(), and n >= 0
627                    (n as u64).to_mysql_bin(w, c)
628                }
629            }
630            myc::value::Value::UInt(n) => {
631                // we are not as lenient with unsigned ints because the mysql crate isn't either
632                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}