1use 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
24pub trait ToMysqlValue {
26 fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()>;
28
29 fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()>;
31
32 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 unreachable!();
73 }
74 }
75
76 fn is_null(&self) -> bool {
77 self.is_none()
78 }
79}
80
81macro_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 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)?; 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 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 (n as u64).to_mysql_bin(w, c)
641 }
642 }
643 myc::value::Value::UInt(n) => {
644 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}