opensrv_mysql/value/
encode.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::{self, Write};
16
17use byteorder::{LittleEndian, WriteBytesExt};
18
19use crate::myc;
20use crate::myc::constants::{ColumnFlags, ColumnType};
21use crate::myc::io::WriteMysqlExt;
22use crate::Column;
23
24/// Implementors of this trait can be sent as a single resultset value to a MySQL/MariaDB client.
25pub trait ToMysqlValue {
26    /// Encode value using the text-based protocol.
27    fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()>;
28
29    /// Encode value using the binary protocol.
30    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()>;
31
32    /// Is this value NULL?
33    fn is_null(&self) -> bool {
34        false
35    }
36}
37
38macro_rules! mysql_text_trivial {
39    () => {
40        fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
41            w.write_lenenc_str(format!("{}", self).as_bytes())
42                .map(|_| ())
43        }
44    };
45}
46
47use std::fmt;
48fn bad<V: fmt::Debug>(v: V, c: &Column) -> io::Error {
49    io::Error::new(
50        io::ErrorKind::InvalidData,
51        format!("tried to use {:?} as {:?}", v, c.coltype),
52    )
53}
54
55impl<T> ToMysqlValue for Option<T>
56where
57    T: ToMysqlValue,
58{
59    fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
60        if let Some(ref v) = *self {
61            v.to_mysql_text(w)
62        } else {
63            w.write_u8(0xFB)
64        }
65    }
66
67    fn to_mysql_bin<W: Write>(&self, w: &mut W, ct: &Column) -> io::Result<()> {
68        if let Some(ref v) = *self {
69            v.to_mysql_bin(w, ct)
70        } else {
71            // should be handled by NULL map
72            unreachable!();
73        }
74    }
75
76    fn is_null(&self) -> bool {
77        self.is_none()
78    }
79}
80
81// NOTE: these rules can all go away when TryFrom stabilizes
82//       see https://github.com/jonhoo/msql-srv/commit/13e5e753e5042a42cc45ad57c2b760561da2fb50
83// NOTE: yes, I know the = / => distinction is ugly
84macro_rules! like_try_into {
85    ($self:ident, $source:ty = $target:ty, $w:ident, $m:ident, $c:ident) => {{
86        let min = <$target>::min_value() as $source;
87        let max = <$target>::max_value() as $source;
88        if *$self <= max && *$self >= min {
89            $w.$m(*$self as $target)
90        } else {
91            Err(bad($self, $c))
92        }
93    }};
94    ($self:ident, $source:ty => $target:ty, $w:ident, $m:ident, $c:ident) => {{
95        let min = <$target>::min_value() as $source;
96        let max = <$target>::max_value() as $source;
97        if *$self <= max && *$self >= min {
98            $w.$m::<LittleEndian>(*$self as $target)
99        } else {
100            Err(bad($self, $c))
101        }
102    }};
103}
104
105macro_rules! forgiving_numeric {
106    ($t:ty) => {
107        impl ToMysqlValue for $t {
108            mysql_text_trivial!();
109            fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
110                let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
111                match c.coltype {
112                    ColumnType::MYSQL_TYPE_LONGLONG => {
113                        if signed {
114                            like_try_into!(self, $t => i64, w, write_i64, c)
115                        } else {
116                            like_try_into!(self, $t => u64, w, write_u64, c)
117                        }
118                    }
119                    ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
120                        if signed {
121                            like_try_into!(self, $t => i32, w, write_i32, c)
122                        } else {
123                            like_try_into!(self, $t => u32, w, write_u32, c)
124                        }
125                    }
126                    ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => {
127                        if signed {
128                            like_try_into!(self, $t => i16, w, write_i16, c)
129                        } else {
130                            like_try_into!(self, $t => u16, w, write_u16, c)
131                        }
132                    }
133                    ColumnType::MYSQL_TYPE_TINY => {
134                        if signed {
135                            like_try_into!(self, $t = i8, w, write_i8, c)
136                        } else {
137                            like_try_into!(self, $t = u8, w, write_u8, c)
138                        }
139                    }
140                    _ => Err(bad(self, c)),
141                }
142            }
143        }
144    };
145}
146
147forgiving_numeric!(usize);
148forgiving_numeric!(isize);
149
150impl ToMysqlValue for u8 {
151    mysql_text_trivial!();
152    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
153        let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
154        match c.coltype {
155            ColumnType::MYSQL_TYPE_LONGLONG => {
156                if signed {
157                    w.write_i64::<LittleEndian>(i64::from(*self))
158                } else {
159                    w.write_u64::<LittleEndian>(u64::from(*self))
160                }
161            }
162            ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
163                if signed {
164                    w.write_i32::<LittleEndian>(i32::from(*self))
165                } else {
166                    w.write_u32::<LittleEndian>(u32::from(*self))
167                }
168            }
169            ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => {
170                if signed {
171                    w.write_i16::<LittleEndian>(i16::from(*self))
172                } else {
173                    w.write_u16::<LittleEndian>(u16::from(*self))
174                }
175            }
176            ColumnType::MYSQL_TYPE_TINY => {
177                assert!(!signed);
178                w.write_u8(*self)
179            }
180            _ => Err(bad(self, c)),
181        }
182    }
183}
184
185impl ToMysqlValue for i8 {
186    mysql_text_trivial!();
187    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
188        let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
189        match c.coltype {
190            ColumnType::MYSQL_TYPE_LONGLONG => {
191                if signed {
192                    w.write_i64::<LittleEndian>(i64::from(*self))
193                } else {
194                    w.write_u64::<LittleEndian>(*self as u64)
195                }
196            }
197            ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
198                if signed {
199                    w.write_i32::<LittleEndian>(i32::from(*self))
200                } else {
201                    w.write_u32::<LittleEndian>(*self as u32)
202                }
203            }
204            ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => {
205                if signed {
206                    w.write_i16::<LittleEndian>(i16::from(*self))
207                } else {
208                    w.write_u16::<LittleEndian>(*self as u16)
209                }
210            }
211            ColumnType::MYSQL_TYPE_TINY => {
212                assert!(signed);
213                w.write_i8(*self)
214            }
215            _ => Err(bad(self, c)),
216        }
217    }
218}
219
220impl ToMysqlValue for u16 {
221    mysql_text_trivial!();
222    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
223        let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
224        match c.coltype {
225            ColumnType::MYSQL_TYPE_LONGLONG => {
226                if signed {
227                    w.write_i64::<LittleEndian>(i64::from(*self))
228                } else {
229                    w.write_u64::<LittleEndian>(u64::from(*self))
230                }
231            }
232            ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
233                if signed {
234                    w.write_i32::<LittleEndian>(i32::from(*self))
235                } else {
236                    w.write_u32::<LittleEndian>(u32::from(*self))
237                }
238            }
239            ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => {
240                assert!(!signed);
241                w.write_u16::<LittleEndian>(*self)
242            }
243            _ => Err(bad(self, c)),
244        }
245    }
246}
247
248impl ToMysqlValue for i16 {
249    mysql_text_trivial!();
250    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
251        let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
252        match c.coltype {
253            ColumnType::MYSQL_TYPE_LONGLONG => {
254                if signed {
255                    w.write_i64::<LittleEndian>(i64::from(*self))
256                } else {
257                    w.write_u64::<LittleEndian>(*self as u64)
258                }
259            }
260            ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
261                if signed {
262                    w.write_i32::<LittleEndian>(i32::from(*self))
263                } else {
264                    w.write_u32::<LittleEndian>(*self as u32)
265                }
266            }
267            ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => {
268                assert!(signed);
269                w.write_i16::<LittleEndian>(*self)
270            }
271            _ => Err(bad(self, c)),
272        }
273    }
274}
275
276impl ToMysqlValue for u32 {
277    mysql_text_trivial!();
278    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
279        let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
280        match c.coltype {
281            ColumnType::MYSQL_TYPE_LONGLONG => {
282                if signed {
283                    w.write_i64::<LittleEndian>(i64::from(*self))
284                } else {
285                    w.write_u64::<LittleEndian>(u64::from(*self))
286                }
287            }
288            ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
289                assert!(!signed);
290                w.write_u32::<LittleEndian>(*self)
291            }
292            _ => Err(bad(self, c)),
293        }
294    }
295}
296
297impl ToMysqlValue for i32 {
298    mysql_text_trivial!();
299    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
300        let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
301        match c.coltype {
302            ColumnType::MYSQL_TYPE_LONGLONG => {
303                if signed {
304                    w.write_i64::<LittleEndian>(i64::from(*self))
305                } else {
306                    w.write_u64::<LittleEndian>(*self as u64)
307                }
308            }
309            ColumnType::MYSQL_TYPE_LONG | ColumnType::MYSQL_TYPE_INT24 => {
310                assert!(signed);
311                w.write_i32::<LittleEndian>(*self)
312            }
313            _ => Err(bad(self, c)),
314        }
315    }
316}
317
318impl ToMysqlValue for u64 {
319    mysql_text_trivial!();
320    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
321        let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
322        match c.coltype {
323            ColumnType::MYSQL_TYPE_LONGLONG => {
324                assert!(!signed);
325                w.write_u64::<LittleEndian>(*self)
326            }
327            _ => Err(bad(self, c)),
328        }
329    }
330}
331
332impl ToMysqlValue for i64 {
333    mysql_text_trivial!();
334    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
335        let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
336        match c.coltype {
337            ColumnType::MYSQL_TYPE_LONGLONG => {
338                assert!(signed);
339                w.write_i64::<LittleEndian>(*self)
340            }
341            _ => Err(bad(self, c)),
342        }
343    }
344}
345
346impl ToMysqlValue for f32 {
347    mysql_text_trivial!();
348    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
349        match c.coltype {
350            ColumnType::MYSQL_TYPE_DOUBLE => w.write_f64::<LittleEndian>(f64::from(*self)),
351            ColumnType::MYSQL_TYPE_FLOAT => w.write_f32::<LittleEndian>(*self),
352            _ => Err(bad(self, c)),
353        }
354    }
355}
356
357impl ToMysqlValue for f64 {
358    mysql_text_trivial!();
359    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
360        match c.coltype {
361            ColumnType::MYSQL_TYPE_DOUBLE => w.write_f64::<LittleEndian>(*self),
362            _ => Err(bad(self, c)),
363        }
364    }
365}
366
367impl ToMysqlValue for String {
368    fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
369        self.as_bytes().to_mysql_text(w)
370    }
371    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
372        self.as_bytes().to_mysql_bin(w, c)
373    }
374}
375
376impl ToMysqlValue for str {
377    fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
378        self.as_bytes().to_mysql_text(w)
379    }
380    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
381        self.as_bytes().to_mysql_bin(w, c)
382    }
383}
384
385impl ToMysqlValue for [u8] {
386    fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
387        w.write_lenenc_str(self).map(|_| ())
388    }
389    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
390        match c.coltype {
391            ColumnType::MYSQL_TYPE_STRING
392            | ColumnType::MYSQL_TYPE_VAR_STRING
393            | ColumnType::MYSQL_TYPE_BLOB
394            | ColumnType::MYSQL_TYPE_TINY_BLOB
395            | ColumnType::MYSQL_TYPE_MEDIUM_BLOB
396            | ColumnType::MYSQL_TYPE_LONG_BLOB
397            | ColumnType::MYSQL_TYPE_SET
398            | ColumnType::MYSQL_TYPE_ENUM
399            | ColumnType::MYSQL_TYPE_DECIMAL
400            | ColumnType::MYSQL_TYPE_VARCHAR
401            | ColumnType::MYSQL_TYPE_BIT
402            | ColumnType::MYSQL_TYPE_NEWDECIMAL
403            | ColumnType::MYSQL_TYPE_GEOMETRY
404            | ColumnType::MYSQL_TYPE_JSON => w.write_lenenc_str(self).map(|_| ()),
405            _ => Err(bad(self, c)),
406        }
407    }
408}
409
410impl ToMysqlValue for Vec<u8> {
411    fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
412        (self[..]).to_mysql_text(w)
413    }
414    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
415        (self[..]).to_mysql_bin(w, c)
416    }
417}
418
419impl<'a, T> ToMysqlValue for &'a T
420where
421    T: ToMysqlValue + ?Sized,
422{
423    fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
424        (*self).to_mysql_text(w)
425    }
426    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
427        (*self).to_mysql_bin(w, c)
428    }
429}
430
431use chrono::{self, Datelike, NaiveDate, NaiveDateTime, Timelike};
432impl ToMysqlValue for NaiveDate {
433    fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
434        w.write_lenenc_str(
435            format!("{:04}-{:02}-{:02}", self.year(), self.month(), self.day()).as_bytes(),
436        )
437        .map(|_| ())
438    }
439    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
440        match c.coltype {
441            ColumnType::MYSQL_TYPE_DATE => {
442                w.write_u8(4u8)?;
443                w.write_u16::<LittleEndian>(self.year() as u16)?;
444                w.write_u8(self.month() as u8)?;
445                w.write_u8(self.day() as u8)
446            }
447            _ => Err(bad(self, c)),
448        }
449    }
450}
451
452impl ToMysqlValue for NaiveDateTime {
453    fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
454        let us = self.nanosecond() / 1_000;
455
456        if us != 0 {
457            w.write_lenenc_str(
458                format!(
459                    "{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:06}",
460                    self.year(),
461                    self.month(),
462                    self.day(),
463                    self.hour(),
464                    self.minute(),
465                    self.second(),
466                    us
467                )
468                .as_bytes(),
469            )
470            .map(|_| ())
471        } else {
472            w.write_lenenc_str(
473                format!(
474                    "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
475                    self.year(),
476                    self.month(),
477                    self.day(),
478                    self.hour(),
479                    self.minute(),
480                    self.second()
481                )
482                .as_bytes(),
483            )
484            .map(|_| ())
485        }
486    }
487    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
488        match c.coltype {
489            ColumnType::MYSQL_TYPE_DATETIME | ColumnType::MYSQL_TYPE_TIMESTAMP => {
490                let us = self.nanosecond() / 1_000;
491
492                if us != 0 {
493                    w.write_u8(11u8)?;
494                } else {
495                    w.write_u8(7u8)?;
496                }
497                w.write_u16::<LittleEndian>(self.year() as u16)?;
498                w.write_u8(self.month() as u8)?;
499                w.write_u8(self.day() as u8)?;
500                w.write_u8(self.hour() as u8)?;
501                w.write_u8(self.minute() as u8)?;
502                w.write_u8(self.second() as u8)?;
503
504                if us != 0 {
505                    w.write_u32::<LittleEndian>(us)?;
506                }
507                Ok(())
508            }
509            _ => Err(bad(self, c)),
510        }
511    }
512}
513
514use std::time::Duration;
515impl ToMysqlValue for Duration {
516    fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
517        let s = self.as_secs();
518        //let d = s / (24 * 3600);
519        // assert!(d <= 34);
520        //let h = (s % (24 * 3600)) / 3600;
521        let h = s / 3600;
522        let m = (s % 3600) / 60;
523        let s = s % 60;
524        let us = self.subsec_micros();
525        if us != 0 {
526            w.write_lenenc_str(format!("{:02}:{:02}:{:02}.{:06}", h, m, s, us).as_bytes())
527                .map(|_| ())
528        } else {
529            w.write_lenenc_str(format!("{:02}:{:02}:{:02}", h, m, s).as_bytes())
530                .map(|_| ())
531        }
532    }
533
534    #[allow(clippy::many_single_char_names)]
535    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
536        let s = self.as_secs();
537        let d = s / (24 * 3600);
538        assert!(d <= 34);
539        let h = (s % (24 * 3600)) / 3600;
540        let m = (s % 3600) / 60;
541        let s = s % 60;
542        let us = self.subsec_micros();
543
544        match c.coltype {
545            ColumnType::MYSQL_TYPE_TIME => {
546                if self.as_secs() == 0 && us == 0 {
547                    w.write_u8(0u8)?;
548                } else {
549                    if us != 0 {
550                        w.write_u8(12u8)?;
551                    } else {
552                        w.write_u8(8u8)?;
553                    }
554
555                    w.write_u8(0u8)?; // positive only (for now)
556                    w.write_u32::<LittleEndian>(d as u32)?;
557                    w.write_u8(h as u8)?;
558                    w.write_u8(m as u8)?;
559                    w.write_u8(s as u8)?;
560
561                    if us != 0 {
562                        w.write_u32::<LittleEndian>(us)?;
563                    }
564                }
565                Ok(())
566            }
567            _ => Err(bad(self, c)),
568        }
569    }
570}
571
572impl ToMysqlValue for myc::value::Value {
573    #[allow(clippy::many_single_char_names)]
574    fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
575        match *self {
576            myc::value::Value::NULL => None::<u8>.to_mysql_text(w),
577            myc::value::Value::Bytes(ref bytes) => bytes.to_mysql_text(w),
578            myc::value::Value::Int(n) => n.to_mysql_text(w),
579            myc::value::Value::UInt(n) => n.to_mysql_text(w),
580            myc::value::Value::Float(f) => f.to_mysql_text(w),
581            myc::value::Value::Double(f) => f.to_mysql_text(w),
582            myc::value::Value::Date(y, mo, d, h, mi, s, us) => {
583                NaiveDate::from_ymd_opt(i32::from(y), u32::from(mo), u32::from(d))
584                    .unwrap()
585                    .and_hms_micro_opt(u32::from(h), u32::from(mi), u32::from(s), us)
586                    .unwrap()
587                    .to_mysql_text(w)
588            }
589            myc::value::Value::Time(neg, d, h, m, s, us) => {
590                if neg {
591                    return Err(io::Error::new(
592                        io::ErrorKind::Other,
593                        "negative times not yet supported",
594                    ));
595                }
596                (chrono::Duration::days(i64::from(d))
597                    + chrono::Duration::hours(i64::from(h))
598                    + chrono::Duration::minutes(i64::from(m))
599                    + chrono::Duration::seconds(i64::from(s))
600                    + chrono::Duration::microseconds(i64::from(us)))
601                .to_std()
602                .expect("only positive times at the moment")
603                .to_mysql_text(w)
604            }
605        }
606    }
607
608    #[allow(clippy::many_single_char_names)]
609    fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
610        match *self {
611            myc::value::Value::NULL => unreachable!(),
612            myc::value::Value::Bytes(ref bytes) => bytes.to_mysql_bin(w, c),
613            myc::value::Value::Int(n) => {
614                // we *could* just delegate to i64 impl here, but then you couldn't use myc::value::Value
615                // and return, say, a short. also, myc uses i64 for *every* number type, *except*
616                // u64, so we even need to coerce across unsigned :( the good news is that our
617                // impls for numbers auto-upgrade to wider coltypes, so we can just downcast to the
618                // smallest containing type, and then call on that
619                let signed = !c.colflags.contains(ColumnFlags::UNSIGNED_FLAG);
620                if signed {
621                    if n >= i64::from(i8::min_value()) && n <= i64::from(i8::max_value()) {
622                        (n as i8).to_mysql_bin(w, c)
623                    } else if n >= i64::from(i16::min_value()) && n <= i64::from(i16::max_value()) {
624                        (n as i16).to_mysql_bin(w, c)
625                    } else if n >= i64::from(i32::min_value()) && n <= i64::from(i32::max_value()) {
626                        (n as i32).to_mysql_bin(w, c)
627                    } else {
628                        n.to_mysql_bin(w, c)
629                    }
630                } else if n < 0 {
631                    Err(bad(self, c))
632                } else if n <= i64::from(u8::max_value()) {
633                    (n as u8).to_mysql_bin(w, c)
634                } else if n <= i64::from(u16::max_value()) {
635                    (n as u16).to_mysql_bin(w, c)
636                } else if n <= i64::from(u32::max_value()) {
637                    (n as u32).to_mysql_bin(w, c)
638                } else {
639                    // must work since u64::max_value() > i64::max_value(), and n >= 0
640                    (n as u64).to_mysql_bin(w, c)
641                }
642            }
643            myc::value::Value::UInt(n) => {
644                // we are not as lenient with unsigned ints because the mysql crate isn't either
645                n.to_mysql_bin(w, c)
646            }
647            myc::value::Value::Float(f) => f.to_mysql_bin(w, c),
648            myc::value::Value::Double(f) => f.to_mysql_bin(w, c),
649            myc::value::Value::Date(y, mo, d, h, mi, s, us) => {
650                NaiveDate::from_ymd_opt(i32::from(y), u32::from(mo), u32::from(d))
651                    .unwrap()
652                    .and_hms_micro_opt(u32::from(h), u32::from(mi), u32::from(s), us)
653                    .unwrap()
654                    .to_mysql_bin(w, c)
655            }
656            myc::value::Value::Time(neg, d, h, m, s, us) => {
657                if neg {
658                    return Err(io::Error::new(
659                        io::ErrorKind::Other,
660                        "negative times not yet supported",
661                    ));
662                }
663                (chrono::Duration::days(i64::from(d))
664                    + chrono::Duration::hours(i64::from(h))
665                    + chrono::Duration::minutes(i64::from(m))
666                    + chrono::Duration::seconds(i64::from(s))
667                    + chrono::Duration::microseconds(i64::from(us)))
668                .to_std()
669                .expect("only positive times at the moment")
670                .to_mysql_bin(w, c)
671            }
672        }
673    }
674
675    fn is_null(&self) -> bool {
676        matches!(*self, myc::value::Value::NULL)
677    }
678}