Skip to main content

zero_mysql/
raw.rs

1//! Flexible decoding API for MySQL binary protocol values.
2//!
3//! This module provides traits for decoding MySQL values directly into target types
4//! without intermediate `Value` allocation.
5
6use crate::constant::{ColumnFlags, ColumnType};
7use crate::error::{Error, Result, eyre};
8use crate::protocol::BinaryRowPayload;
9use crate::protocol::command::{ColumnDefinition, ColumnDefinitionTail};
10use crate::protocol::primitive::*;
11use crate::value::{Time8, Time12, Timestamp4, Timestamp7, Timestamp11, Value};
12use simdutf8::basic::from_utf8;
13use zerocopy::FromBytes;
14
15/// MySQL binary charset number - indicates binary/non-text data
16const BINARY_CHARSET: u16 = 63;
17
18/// Trait for types that can be decoded from MySQL binary protocol values.
19///
20/// Each method corresponds to a MySQL wire format. Implementations should
21/// return `Err` for unsupported conversions.
22pub trait FromRawValue<'buf>: Sized {
23    fn from_null() -> Result<Self> {
24        Err(Error::BadUsageError(format!(
25            "Cannot decode MySQL type NULL to {}",
26            std::any::type_name::<Self>()
27        )))
28    }
29
30    fn from_i8(_v: i8) -> Result<Self> {
31        Err(Error::BadUsageError(format!(
32            "Cannot decode MySQL type TINYINT (i8) to {}",
33            std::any::type_name::<Self>()
34        )))
35    }
36
37    fn from_i16(_v: i16) -> Result<Self> {
38        Err(Error::BadUsageError(format!(
39            "Cannot decode MySQL type SMALLINT (i16) to {}",
40            std::any::type_name::<Self>()
41        )))
42    }
43
44    fn from_i32(_v: i32) -> Result<Self> {
45        Err(Error::BadUsageError(format!(
46            "Cannot decode MySQL type INT (i32) to {}",
47            std::any::type_name::<Self>()
48        )))
49    }
50
51    fn from_i64(_v: i64) -> Result<Self> {
52        Err(Error::BadUsageError(format!(
53            "Cannot decode MySQL type BIGINT (i64) to {}",
54            std::any::type_name::<Self>()
55        )))
56    }
57
58    fn from_u8(_v: u8) -> Result<Self> {
59        Err(Error::BadUsageError(format!(
60            "Cannot decode MySQL type TINYINT UNSIGNED (u8) to {}",
61            std::any::type_name::<Self>()
62        )))
63    }
64
65    fn from_u16(_v: u16) -> Result<Self> {
66        Err(Error::BadUsageError(format!(
67            "Cannot decode MySQL type SMALLINT UNSIGNED (u16) to {}",
68            std::any::type_name::<Self>()
69        )))
70    }
71
72    fn from_u32(_v: u32) -> Result<Self> {
73        Err(Error::BadUsageError(format!(
74            "Cannot decode MySQL type INT UNSIGNED (u32) to {}",
75            std::any::type_name::<Self>()
76        )))
77    }
78
79    fn from_u64(_v: u64) -> Result<Self> {
80        Err(Error::BadUsageError(format!(
81            "Cannot decode MySQL type BIGINT UNSIGNED (u64) to {}",
82            std::any::type_name::<Self>()
83        )))
84    }
85
86    fn from_float(_v: f32) -> Result<Self> {
87        Err(Error::BadUsageError(format!(
88            "Cannot decode MySQL type FLOAT (f32) to {}",
89            std::any::type_name::<Self>()
90        )))
91    }
92
93    fn from_double(_v: f64) -> Result<Self> {
94        Err(Error::BadUsageError(format!(
95            "Cannot decode MySQL type DOUBLE (f64) to {}",
96            std::any::type_name::<Self>()
97        )))
98    }
99
100    fn from_bytes(_v: &'buf [u8]) -> Result<Self> {
101        Err(Error::BadUsageError(format!(
102            "Cannot decode MySQL type BYTES to {}",
103            std::any::type_name::<Self>()
104        )))
105    }
106
107    fn from_str(_v: &'buf [u8]) -> Result<Self> {
108        Err(Error::BadUsageError(format!(
109            "Cannot decode MySQL type STRING to {}",
110            std::any::type_name::<Self>()
111        )))
112    }
113
114    fn from_decimal(_v: &'buf [u8]) -> Result<Self> {
115        Err(Error::BadUsageError(format!(
116            "Cannot decode MySQL type DECIMAL to {}",
117            std::any::type_name::<Self>()
118        )))
119    }
120
121    fn from_date0() -> Result<Self> {
122        Err(Error::BadUsageError(format!(
123            "Cannot decode MySQL type DATE to {}",
124            std::any::type_name::<Self>()
125        )))
126    }
127
128    fn from_date4(_v: &'buf Timestamp4) -> Result<Self> {
129        Err(Error::BadUsageError(format!(
130            "Cannot decode MySQL type DATE to {}",
131            std::any::type_name::<Self>()
132        )))
133    }
134
135    fn from_datetime0() -> Result<Self> {
136        Err(Error::BadUsageError(format!(
137            "Cannot decode MySQL type DATETIME to {}",
138            std::any::type_name::<Self>()
139        )))
140    }
141
142    fn from_datetime4(_v: &'buf Timestamp4) -> Result<Self> {
143        Err(Error::BadUsageError(format!(
144            "Cannot decode MySQL type DATETIME to {}",
145            std::any::type_name::<Self>()
146        )))
147    }
148
149    fn from_datetime7(_v: &'buf Timestamp7) -> Result<Self> {
150        Err(Error::BadUsageError(format!(
151            "Cannot decode MySQL type DATETIME to {}",
152            std::any::type_name::<Self>()
153        )))
154    }
155
156    fn from_datetime11(_v: &'buf Timestamp11) -> Result<Self> {
157        Err(Error::BadUsageError(format!(
158            "Cannot decode MySQL type DATETIME to {}",
159            std::any::type_name::<Self>()
160        )))
161    }
162
163    fn from_time0() -> Result<Self> {
164        Err(Error::BadUsageError(format!(
165            "Cannot decode MySQL type TIME to {}",
166            std::any::type_name::<Self>()
167        )))
168    }
169
170    fn from_time8(_v: &'buf Time8) -> Result<Self> {
171        Err(Error::BadUsageError(format!(
172            "Cannot decode MySQL type TIME to {}",
173            std::any::type_name::<Self>()
174        )))
175    }
176
177    fn from_time12(_v: &'buf Time12) -> Result<Self> {
178        Err(Error::BadUsageError(format!(
179            "Cannot decode MySQL type TIME to {}",
180            std::any::type_name::<Self>()
181        )))
182    }
183}
184
185/// Parse a single value from binary data into target type `T`.
186///
187/// Returns the parsed value and remaining bytes.
188pub fn parse_value<'buf, T: FromRawValue<'buf>>(
189    col: &ColumnDefinitionTail,
190    is_null: bool,
191    data: &'buf [u8],
192) -> Result<(T, &'buf [u8])> {
193    if is_null {
194        return Ok((T::from_null()?, data));
195    }
196    let is_unsigned = col.flags()?.contains(ColumnFlags::UNSIGNED_FLAG);
197    let is_binary_charset = col.charset() == BINARY_CHARSET;
198
199    match col.column_type()? {
200        ColumnType::MYSQL_TYPE_NULL => Ok((T::from_null()?, data)),
201
202        // Integer types
203        ColumnType::MYSQL_TYPE_TINY => {
204            let (val, rest) = read_int_1(data)?;
205            let out = if is_unsigned {
206                T::from_u8(val)?
207            } else {
208                T::from_i8(val as i8)?
209            };
210            Ok((out, rest))
211        }
212
213        ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => {
214            let (val, rest) = read_int_2(data)?;
215            let out = if is_unsigned {
216                T::from_u16(val)?
217            } else {
218                T::from_i16(val as i16)?
219            };
220            Ok((out, rest))
221        }
222
223        ColumnType::MYSQL_TYPE_INT24 | ColumnType::MYSQL_TYPE_LONG => {
224            let (val, rest) = read_int_4(data)?;
225            let out = if is_unsigned {
226                T::from_u32(val)?
227            } else {
228                T::from_i32(val as i32)?
229            };
230            Ok((out, rest))
231        }
232
233        ColumnType::MYSQL_TYPE_LONGLONG => {
234            let (val, rest) = read_int_8(data)?;
235            let out = if is_unsigned {
236                T::from_u64(val)?
237            } else {
238                T::from_i64(val as i64)?
239            };
240            Ok((out, rest))
241        }
242
243        // Floating point types
244        ColumnType::MYSQL_TYPE_FLOAT => {
245            let (val, rest) = read_int_4(data)?;
246            Ok((T::from_float(f32::from_bits(val))?, rest))
247        }
248
249        ColumnType::MYSQL_TYPE_DOUBLE => {
250            let (val, rest) = read_int_8(data)?;
251            Ok((T::from_double(f64::from_bits(val))?, rest))
252        }
253
254        // DATE types
255        ColumnType::MYSQL_TYPE_DATE | ColumnType::MYSQL_TYPE_NEWDATE => {
256            let (len, mut rest) = read_int_1(data)?;
257            match len {
258                0 => Ok((T::from_date0()?, rest)),
259                4 => {
260                    let ts = Timestamp4::ref_from_bytes(&rest[..4])?;
261                    rest = &rest[4..];
262                    Ok((T::from_date4(ts)?, rest))
263                }
264                _ => Err(Error::LibraryBug(eyre!("invalid date length: {}", len))),
265            }
266        }
267
268        // DATETIME/TIMESTAMP types
269        ColumnType::MYSQL_TYPE_DATETIME
270        | ColumnType::MYSQL_TYPE_TIMESTAMP
271        | ColumnType::MYSQL_TYPE_TIMESTAMP2
272        | ColumnType::MYSQL_TYPE_DATETIME2 => {
273            let (len, rest) = read_int_1(data)?;
274            match len {
275                0 => Ok((T::from_datetime0()?, rest)),
276                4 => {
277                    let (chunk, tail) = rest
278                        .split_first_chunk::<4>()
279                        .ok_or_else(|| Error::LibraryBug(eyre!("truncated datetime4")))?;
280                    let ts = Timestamp4::ref_from_bytes(chunk)?;
281                    Ok((T::from_datetime4(ts)?, tail))
282                }
283                7 => {
284                    let (chunk, tail) = rest
285                        .split_first_chunk::<7>()
286                        .ok_or_else(|| Error::LibraryBug(eyre!("truncated datetime7")))?;
287                    let ts = Timestamp7::ref_from_bytes(chunk)?;
288                    Ok((T::from_datetime7(ts)?, tail))
289                }
290                11 => {
291                    let (chunk, tail) = rest
292                        .split_first_chunk::<11>()
293                        .ok_or_else(|| Error::LibraryBug(eyre!("truncated datetime11")))?;
294                    let ts = Timestamp11::ref_from_bytes(chunk)?;
295                    Ok((T::from_datetime11(ts)?, tail))
296                }
297                _ => Err(Error::LibraryBug(eyre!("invalid datetime length: {}", len))),
298            }
299        }
300
301        // TIME types
302        ColumnType::MYSQL_TYPE_TIME | ColumnType::MYSQL_TYPE_TIME2 => {
303            let (len, rest) = read_int_1(data)?;
304            match len {
305                0 => Ok((T::from_time0()?, rest)),
306                8 => {
307                    let (chunk, tail) = rest
308                        .split_first_chunk::<8>()
309                        .ok_or_else(|| Error::LibraryBug(eyre!("truncated time8")))?;
310                    let time = Time8::ref_from_bytes(chunk)?;
311                    Ok((T::from_time8(time)?, tail))
312                }
313                12 => {
314                    let (chunk, tail) = rest
315                        .split_first_chunk::<12>()
316                        .ok_or_else(|| Error::LibraryBug(eyre!("truncated time12")))?;
317                    let time = Time12::ref_from_bytes(chunk)?;
318                    Ok((T::from_time12(time)?, tail))
319                }
320                _ => Err(Error::LibraryBug(eyre!("invalid time length: {}", len))),
321            }
322        }
323
324        // DECIMAL types
325        ColumnType::MYSQL_TYPE_DECIMAL | ColumnType::MYSQL_TYPE_NEWDECIMAL => {
326            let (bytes, rest) = read_string_lenenc(data)?;
327            Ok((T::from_decimal(bytes)?, rest))
328        }
329
330        // String and BLOB types
331        ColumnType::MYSQL_TYPE_VARCHAR
332        | ColumnType::MYSQL_TYPE_VAR_STRING
333        | ColumnType::MYSQL_TYPE_STRING
334        | ColumnType::MYSQL_TYPE_BLOB
335        | ColumnType::MYSQL_TYPE_TINY_BLOB
336        | ColumnType::MYSQL_TYPE_MEDIUM_BLOB
337        | ColumnType::MYSQL_TYPE_LONG_BLOB
338        | ColumnType::MYSQL_TYPE_GEOMETRY
339        | ColumnType::MYSQL_TYPE_JSON
340        | ColumnType::MYSQL_TYPE_ENUM
341        | ColumnType::MYSQL_TYPE_SET
342        | ColumnType::MYSQL_TYPE_BIT
343        | ColumnType::MYSQL_TYPE_TYPED_ARRAY => {
344            let (bytes, rest) = read_string_lenenc(data)?;
345            let out = if is_binary_charset {
346                T::from_bytes(bytes)?
347            } else {
348                T::from_str(bytes)?
349            };
350            Ok((out, rest))
351        }
352    }
353}
354
355/// Skip a single value in binary data without parsing it.
356///
357/// Returns the remaining bytes after the skipped value.
358pub fn skip_value<'buf>(
359    col: &ColumnDefinitionTail,
360    is_null: bool,
361    data: &'buf [u8],
362) -> Result<((), &'buf [u8])> {
363    if is_null {
364        return Ok(((), data));
365    }
366
367    match col.column_type()? {
368        ColumnType::MYSQL_TYPE_NULL => Ok(((), data)),
369
370        // Fixed-size integer types
371        ColumnType::MYSQL_TYPE_TINY => Ok(((), &data[1..])),
372        ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => Ok(((), &data[2..])),
373        ColumnType::MYSQL_TYPE_INT24
374        | ColumnType::MYSQL_TYPE_LONG
375        | ColumnType::MYSQL_TYPE_FLOAT => Ok(((), &data[4..])),
376        ColumnType::MYSQL_TYPE_LONGLONG | ColumnType::MYSQL_TYPE_DOUBLE => Ok(((), &data[8..])),
377
378        // Variable-length date/time types
379        ColumnType::MYSQL_TYPE_DATE
380        | ColumnType::MYSQL_TYPE_NEWDATE
381        | ColumnType::MYSQL_TYPE_DATETIME
382        | ColumnType::MYSQL_TYPE_TIMESTAMP
383        | ColumnType::MYSQL_TYPE_TIMESTAMP2
384        | ColumnType::MYSQL_TYPE_DATETIME2
385        | ColumnType::MYSQL_TYPE_TIME
386        | ColumnType::MYSQL_TYPE_TIME2 => {
387            let (len, rest) = read_int_1(data)?;
388            Ok(((), &rest[len as usize..]))
389        }
390
391        // Length-encoded string types
392        ColumnType::MYSQL_TYPE_DECIMAL
393        | ColumnType::MYSQL_TYPE_NEWDECIMAL
394        | ColumnType::MYSQL_TYPE_VARCHAR
395        | ColumnType::MYSQL_TYPE_VAR_STRING
396        | ColumnType::MYSQL_TYPE_STRING
397        | ColumnType::MYSQL_TYPE_BLOB
398        | ColumnType::MYSQL_TYPE_TINY_BLOB
399        | ColumnType::MYSQL_TYPE_MEDIUM_BLOB
400        | ColumnType::MYSQL_TYPE_LONG_BLOB
401        | ColumnType::MYSQL_TYPE_GEOMETRY
402        | ColumnType::MYSQL_TYPE_JSON
403        | ColumnType::MYSQL_TYPE_ENUM
404        | ColumnType::MYSQL_TYPE_SET
405        | ColumnType::MYSQL_TYPE_BIT
406        | ColumnType::MYSQL_TYPE_TYPED_ARRAY => {
407            let (_, rest) = read_string_lenenc(data)?;
408            Ok(((), rest))
409        }
410    }
411}
412
413/// Trait for types that can be decoded from a MySQL row.
414pub trait FromRow<'buf>: Sized {
415    fn from_row(cols: &[ColumnDefinition<'_>], row: BinaryRowPayload<'buf>) -> Result<Self>;
416}
417
418// ============================================================================
419// FromRawValue implementations for Value<'a>
420// ============================================================================
421
422impl<'buf, 'value> FromRawValue<'buf> for Value<'value>
423where
424    'buf: 'value,
425{
426    fn from_null() -> Result<Self> {
427        Ok(Value::Null)
428    }
429
430    fn from_i8(v: i8) -> Result<Self> {
431        Ok(Value::SignedInt(v as i64))
432    }
433
434    fn from_i16(v: i16) -> Result<Self> {
435        Ok(Value::SignedInt(v as i64))
436    }
437
438    fn from_i32(v: i32) -> Result<Self> {
439        Ok(Value::SignedInt(v as i64))
440    }
441
442    fn from_i64(v: i64) -> Result<Self> {
443        Ok(Value::SignedInt(v))
444    }
445
446    fn from_u8(v: u8) -> Result<Self> {
447        Ok(Value::UnsignedInt(v as u64))
448    }
449
450    fn from_u16(v: u16) -> Result<Self> {
451        Ok(Value::UnsignedInt(v as u64))
452    }
453
454    fn from_u32(v: u32) -> Result<Self> {
455        Ok(Value::UnsignedInt(v as u64))
456    }
457
458    fn from_u64(v: u64) -> Result<Self> {
459        Ok(Value::UnsignedInt(v))
460    }
461
462    fn from_float(v: f32) -> Result<Self> {
463        Ok(Value::Float(v))
464    }
465
466    fn from_double(v: f64) -> Result<Self> {
467        Ok(Value::Double(v))
468    }
469
470    fn from_bytes(v: &'buf [u8]) -> Result<Self> {
471        Ok(Value::Byte(v))
472    }
473
474    fn from_str(v: &'buf [u8]) -> Result<Self> {
475        Ok(Value::Byte(v))
476    }
477
478    fn from_decimal(v: &'buf [u8]) -> Result<Self> {
479        Ok(Value::Byte(v))
480    }
481
482    fn from_date0() -> Result<Self> {
483        Ok(Value::Date0)
484    }
485
486    fn from_date4(v: &'buf Timestamp4) -> Result<Self> {
487        Ok(Value::Date4(v))
488    }
489
490    fn from_datetime0() -> Result<Self> {
491        Ok(Value::Datetime0)
492    }
493
494    fn from_datetime4(v: &'buf Timestamp4) -> Result<Self> {
495        Ok(Value::Datetime4(v))
496    }
497
498    fn from_datetime7(v: &'buf Timestamp7) -> Result<Self> {
499        Ok(Value::Datetime7(v))
500    }
501
502    fn from_datetime11(v: &'buf Timestamp11) -> Result<Self> {
503        Ok(Value::Datetime11(v))
504    }
505
506    fn from_time0() -> Result<Self> {
507        Ok(Value::Time0)
508    }
509
510    fn from_time8(v: &'buf Time8) -> Result<Self> {
511        Ok(Value::Time8(v))
512    }
513
514    fn from_time12(v: &'buf Time12) -> Result<Self> {
515        Ok(Value::Time12(v))
516    }
517}
518
519// ============================================================================
520// FromRawValue implementations for primitive types
521// ============================================================================
522
523impl FromRawValue<'_> for i8 {
524    fn from_i8(v: i8) -> Result<Self> {
525        Ok(v)
526    }
527}
528
529impl FromRawValue<'_> for i16 {
530    fn from_i8(v: i8) -> Result<Self> {
531        Ok(v as i16)
532    }
533
534    fn from_i16(v: i16) -> Result<Self> {
535        Ok(v)
536    }
537}
538
539impl FromRawValue<'_> for i32 {
540    fn from_i8(v: i8) -> Result<Self> {
541        Ok(v as i32)
542    }
543
544    fn from_i16(v: i16) -> Result<Self> {
545        Ok(v as i32)
546    }
547
548    fn from_i32(v: i32) -> Result<Self> {
549        Ok(v)
550    }
551}
552
553impl FromRawValue<'_> for i64 {
554    fn from_i8(v: i8) -> Result<Self> {
555        Ok(v as i64)
556    }
557
558    fn from_i16(v: i16) -> Result<Self> {
559        Ok(v as i64)
560    }
561
562    fn from_i32(v: i32) -> Result<Self> {
563        Ok(v as i64)
564    }
565
566    fn from_i64(v: i64) -> Result<Self> {
567        Ok(v)
568    }
569}
570
571impl FromRawValue<'_> for bool {
572    fn from_i8(v: i8) -> Result<Self> {
573        Ok(v != 0)
574    }
575
576    fn from_u8(v: u8) -> Result<Self> {
577        Ok(v != 0)
578    }
579}
580
581impl FromRawValue<'_> for u8 {
582    fn from_u8(v: u8) -> Result<Self> {
583        Ok(v)
584    }
585}
586
587impl FromRawValue<'_> for u16 {
588    fn from_u8(v: u8) -> Result<Self> {
589        Ok(v as u16)
590    }
591
592    fn from_u16(v: u16) -> Result<Self> {
593        Ok(v)
594    }
595}
596
597impl FromRawValue<'_> for u32 {
598    fn from_u8(v: u8) -> Result<Self> {
599        Ok(v as u32)
600    }
601
602    fn from_u16(v: u16) -> Result<Self> {
603        Ok(v as u32)
604    }
605
606    fn from_u32(v: u32) -> Result<Self> {
607        Ok(v)
608    }
609}
610
611impl FromRawValue<'_> for u64 {
612    fn from_u8(v: u8) -> Result<Self> {
613        Ok(v as u64)
614    }
615
616    fn from_u16(v: u16) -> Result<Self> {
617        Ok(v as u64)
618    }
619
620    fn from_u32(v: u32) -> Result<Self> {
621        Ok(v as u64)
622    }
623
624    fn from_u64(v: u64) -> Result<Self> {
625        Ok(v)
626    }
627}
628
629impl FromRawValue<'_> for f32 {
630    fn from_float(v: f32) -> Result<Self> {
631        Ok(v)
632    }
633}
634
635impl FromRawValue<'_> for f64 {
636    fn from_double(v: f64) -> Result<Self> {
637        Ok(v)
638    }
639
640    fn from_float(v: f32) -> Result<Self> {
641        Ok(v as f64)
642    }
643}
644
645impl<'a> FromRawValue<'a> for &'a [u8] {
646    fn from_bytes(v: &'a [u8]) -> Result<Self> {
647        Ok(v)
648    }
649}
650
651impl FromRawValue<'_> for Vec<u8> {
652    fn from_bytes(v: &[u8]) -> Result<Self> {
653        Ok(v.to_vec())
654    }
655}
656
657impl<'a> FromRawValue<'a> for &'a str {
658    fn from_str(v: &'a [u8]) -> Result<Self> {
659        from_utf8(v).map_err(|e| {
660            Error::BadUsageError(format!("Cannot decode MySQL type STRING to &str: {}", e))
661        })
662    }
663}
664
665impl FromRawValue<'_> for String {
666    fn from_str(v: &[u8]) -> Result<Self> {
667        from_utf8(v).map(|s| s.to_owned()).map_err(|e| {
668            Error::BadUsageError(format!("Cannot decode MySQL type STRING to String: {}", e))
669        })
670    }
671}
672
673impl<'a, T: FromRawValue<'a>> FromRawValue<'a> for Option<T> {
674    fn from_null() -> Result<Self> {
675        Ok(None)
676    }
677
678    fn from_i8(v: i8) -> Result<Self> {
679        T::from_i8(v).map(Some)
680    }
681
682    fn from_i16(v: i16) -> Result<Self> {
683        T::from_i16(v).map(Some)
684    }
685
686    fn from_i32(v: i32) -> Result<Self> {
687        T::from_i32(v).map(Some)
688    }
689
690    fn from_i64(v: i64) -> Result<Self> {
691        T::from_i64(v).map(Some)
692    }
693
694    fn from_u8(v: u8) -> Result<Self> {
695        T::from_u8(v).map(Some)
696    }
697
698    fn from_u16(v: u16) -> Result<Self> {
699        T::from_u16(v).map(Some)
700    }
701
702    fn from_u32(v: u32) -> Result<Self> {
703        T::from_u32(v).map(Some)
704    }
705
706    fn from_u64(v: u64) -> Result<Self> {
707        T::from_u64(v).map(Some)
708    }
709
710    fn from_float(v: f32) -> Result<Self> {
711        T::from_float(v).map(Some)
712    }
713
714    fn from_double(v: f64) -> Result<Self> {
715        T::from_double(v).map(Some)
716    }
717
718    fn from_bytes(v: &'a [u8]) -> Result<Self> {
719        T::from_bytes(v).map(Some)
720    }
721
722    fn from_str(v: &'a [u8]) -> Result<Self> {
723        T::from_str(v).map(Some)
724    }
725
726    fn from_decimal(v: &'a [u8]) -> Result<Self> {
727        T::from_decimal(v).map(Some)
728    }
729
730    fn from_date0() -> Result<Self> {
731        T::from_date0().map(Some)
732    }
733
734    fn from_date4(v: &'a Timestamp4) -> Result<Self> {
735        T::from_date4(v).map(Some)
736    }
737
738    fn from_datetime0() -> Result<Self> {
739        T::from_datetime0().map(Some)
740    }
741
742    fn from_datetime4(v: &'a Timestamp4) -> Result<Self> {
743        T::from_datetime4(v).map(Some)
744    }
745
746    fn from_datetime7(v: &'a Timestamp7) -> Result<Self> {
747        T::from_datetime7(v).map(Some)
748    }
749
750    fn from_datetime11(v: &'a Timestamp11) -> Result<Self> {
751        T::from_datetime11(v).map(Some)
752    }
753
754    fn from_time0() -> Result<Self> {
755        T::from_time0().map(Some)
756    }
757
758    fn from_time8(v: &'a Time8) -> Result<Self> {
759        T::from_time8(v).map(Some)
760    }
761
762    fn from_time12(v: &'a Time12) -> Result<Self> {
763        T::from_time12(v).map(Some)
764    }
765}
766
767// ============================================================================
768// FromRow implementations for tuples
769// ============================================================================
770
771macro_rules! impl_from_row_tuple {
772    ($($idx:tt: $T:ident),+) => {
773        impl<'buf, 'value, $($T: FromRawValue<'buf>),+> FromRow<'buf> for ($($T,)+) {
774            #[expect(non_snake_case)]
775            fn from_row(cols: &[ColumnDefinition<'_>], row: BinaryRowPayload<'buf>) -> Result<Self> {
776                let mut data = row.values();
777                let null_bitmap = row.null_bitmap();
778                $(
779                    let col = cols.get($idx)
780                        .ok_or_else(|| Error::LibraryBug(eyre!(
781                            "from_row: column index {} out of bounds (got {} columns)",
782                            $idx, cols.len()
783                        )))?;
784                    let ($T, rest) = parse_value::<$T>(&col.tail, null_bitmap.is_null($idx), data)?;
785                    data = rest;
786                )+
787                let _ = data; // suppress unused warning for last element
788                Ok(($($T,)+))
789            }
790        }
791    };
792}
793
794impl_from_row_tuple!(0: A);
795impl_from_row_tuple!(0: A, 1: B);
796impl_from_row_tuple!(0: A, 1: B, 2: C);
797impl_from_row_tuple!(0: A, 1: B, 2: C, 3: D);
798impl_from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E);
799impl_from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F);
800impl_from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G);
801impl_from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H);
802impl_from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I);
803impl_from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J);
804impl_from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K);
805impl_from_row_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L);
806
807// ============================================================================
808// UUID support
809// ============================================================================
810
811#[cfg(feature = "with-uuid")]
812impl FromRawValue<'_> for uuid::Uuid {
813    fn from_str(v: &[u8]) -> Result<Self> {
814        let s = from_utf8(v).map_err(|e| {
815            Error::BadUsageError(format!("Cannot decode MySQL STRING to UUID: {}", e))
816        })?;
817        uuid::Uuid::parse_str(s)
818            .map_err(|e| Error::BadUsageError(format!("Cannot parse UUID from '{}': {}", s, e)))
819    }
820
821    fn from_bytes(v: &[u8]) -> Result<Self> {
822        uuid::Uuid::from_slice(v)
823            .map_err(|e| Error::BadUsageError(format!("Cannot decode MySQL BINARY to UUID: {}", e)))
824    }
825}
826
827// ============================================================================
828// chrono support
829// ============================================================================
830
831#[cfg(feature = "with-chrono")]
832const MIDNIGHT: chrono::NaiveTime = chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap();
833
834#[cfg(feature = "with-chrono")]
835impl FromRawValue<'_> for chrono::NaiveDate {
836    fn from_date0() -> Result<Self> {
837        Err(Error::BadUsageError(
838            "Cannot decode zero DATE to chrono::NaiveDate".to_string(),
839        ))
840    }
841
842    fn from_date4(v: &Timestamp4) -> Result<Self> {
843        chrono::NaiveDate::from_ymd_opt(v.year() as i32, v.month as u32, v.day as u32).ok_or_else(
844            || Error::BadUsageError(format!("Invalid date: {}-{}-{}", v.year(), v.month, v.day)),
845        )
846    }
847}
848
849#[cfg(feature = "with-chrono")]
850impl FromRawValue<'_> for chrono::NaiveTime {
851    fn from_time0() -> Result<Self> {
852        Ok(MIDNIGHT)
853    }
854
855    fn from_time8(v: &Time8) -> Result<Self> {
856        if v.is_negative() || v.days() > 0 {
857            return Err(Error::BadUsageError(
858                "Cannot decode TIME with days or negative to chrono::NaiveTime".to_string(),
859            ));
860        }
861        chrono::NaiveTime::from_hms_opt(v.hour as u32, v.minute as u32, v.second as u32).ok_or_else(
862            || {
863                Error::BadUsageError(format!(
864                    "Invalid time: {}:{}:{}",
865                    v.hour, v.minute, v.second
866                ))
867            },
868        )
869    }
870
871    fn from_time12(v: &Time12) -> Result<Self> {
872        if v.is_negative() || v.days() > 0 {
873            return Err(Error::BadUsageError(
874                "Cannot decode TIME with days or negative to chrono::NaiveTime".to_string(),
875            ));
876        }
877        chrono::NaiveTime::from_hms_micro_opt(
878            v.hour as u32,
879            v.minute as u32,
880            v.second as u32,
881            v.microsecond(),
882        )
883        .ok_or_else(|| {
884            Error::BadUsageError(format!(
885                "Invalid time: {}:{}:{}.{}",
886                v.hour,
887                v.minute,
888                v.second,
889                v.microsecond()
890            ))
891        })
892    }
893}
894
895#[cfg(feature = "with-chrono")]
896impl FromRawValue<'_> for chrono::NaiveDateTime {
897    fn from_datetime0() -> Result<Self> {
898        Err(Error::BadUsageError(
899            "Cannot decode zero DATETIME to chrono::NaiveDateTime".to_string(),
900        ))
901    }
902
903    fn from_datetime4(v: &Timestamp4) -> Result<Self> {
904        let date = chrono::NaiveDate::from_ymd_opt(v.year() as i32, v.month as u32, v.day as u32)
905            .ok_or_else(|| {
906            Error::BadUsageError(format!("Invalid date: {}-{}-{}", v.year(), v.month, v.day))
907        })?;
908        Ok(date.and_time(MIDNIGHT))
909    }
910
911    fn from_datetime7(v: &Timestamp7) -> Result<Self> {
912        let date = chrono::NaiveDate::from_ymd_opt(v.year() as i32, v.month as u32, v.day as u32)
913            .ok_or_else(|| {
914            Error::BadUsageError(format!("Invalid date: {}-{}-{}", v.year(), v.month, v.day))
915        })?;
916        date.and_hms_opt(v.hour as u32, v.minute as u32, v.second as u32)
917            .ok_or_else(|| {
918                Error::BadUsageError(format!(
919                    "Invalid time: {}:{}:{}",
920                    v.hour, v.minute, v.second
921                ))
922            })
923    }
924
925    fn from_datetime11(v: &Timestamp11) -> Result<Self> {
926        let date = chrono::NaiveDate::from_ymd_opt(v.year() as i32, v.month as u32, v.day as u32)
927            .ok_or_else(|| {
928            Error::BadUsageError(format!("Invalid date: {}-{}-{}", v.year(), v.month, v.day))
929        })?;
930        date.and_hms_micro_opt(
931            v.hour as u32,
932            v.minute as u32,
933            v.second as u32,
934            v.microsecond(),
935        )
936        .ok_or_else(|| {
937            Error::BadUsageError(format!(
938                "Invalid datetime: {}-{}-{} {}:{}:{}.{}",
939                v.year(),
940                v.month,
941                v.day,
942                v.hour,
943                v.minute,
944                v.second,
945                v.microsecond()
946            ))
947        })
948    }
949}
950
951// ============================================================================
952// time crate support
953// ============================================================================
954
955#[cfg(feature = "with-time")]
956impl FromRawValue<'_> for time::Date {
957    fn from_date0() -> Result<Self> {
958        Err(Error::BadUsageError(
959            "Cannot decode zero DATE to time::Date".to_string(),
960        ))
961    }
962
963    fn from_date4(v: &Timestamp4) -> Result<Self> {
964        let month = time::Month::try_from(v.month)
965            .map_err(|e| Error::BadUsageError(format!("Invalid month {}: {}", v.month, e)))?;
966        time::Date::from_calendar_date(v.year() as i32, month, v.day).map_err(|e| {
967            Error::BadUsageError(format!(
968                "Invalid date {}-{}-{}: {}",
969                v.year(),
970                v.month,
971                v.day,
972                e
973            ))
974        })
975    }
976}
977
978#[cfg(feature = "with-time")]
979impl FromRawValue<'_> for time::Time {
980    fn from_time0() -> Result<Self> {
981        Ok(time::Time::MIDNIGHT)
982    }
983
984    fn from_time8(v: &Time8) -> Result<Self> {
985        if v.is_negative() || v.days() > 0 {
986            return Err(Error::BadUsageError(
987                "Cannot decode TIME with days or negative to time::Time".to_string(),
988            ));
989        }
990        time::Time::from_hms(v.hour, v.minute, v.second).map_err(|e| {
991            Error::BadUsageError(format!(
992                "Invalid time {}:{}:{}: {}",
993                v.hour, v.minute, v.second, e
994            ))
995        })
996    }
997
998    fn from_time12(v: &Time12) -> Result<Self> {
999        if v.is_negative() || v.days() > 0 {
1000            return Err(Error::BadUsageError(
1001                "Cannot decode TIME with days or negative to time::Time".to_string(),
1002            ));
1003        }
1004        time::Time::from_hms_micro(v.hour, v.minute, v.second, v.microsecond()).map_err(|e| {
1005            Error::BadUsageError(format!(
1006                "Invalid time {}:{}:{}.{}: {}",
1007                v.hour,
1008                v.minute,
1009                v.second,
1010                v.microsecond(),
1011                e
1012            ))
1013        })
1014    }
1015}
1016
1017#[cfg(feature = "with-time")]
1018impl FromRawValue<'_> for time::PrimitiveDateTime {
1019    fn from_datetime0() -> Result<Self> {
1020        Err(Error::BadUsageError(
1021            "Cannot decode zero DATETIME to time::PrimitiveDateTime".to_string(),
1022        ))
1023    }
1024
1025    fn from_datetime4(v: &Timestamp4) -> Result<Self> {
1026        let month = time::Month::try_from(v.month)
1027            .map_err(|e| Error::BadUsageError(format!("Invalid month {}: {}", v.month, e)))?;
1028        let date = time::Date::from_calendar_date(v.year() as i32, month, v.day).map_err(|e| {
1029            Error::BadUsageError(format!(
1030                "Invalid date {}-{}-{}: {}",
1031                v.year(),
1032                v.month,
1033                v.day,
1034                e
1035            ))
1036        })?;
1037        Ok(time::PrimitiveDateTime::new(date, time::Time::MIDNIGHT))
1038    }
1039
1040    fn from_datetime7(v: &Timestamp7) -> Result<Self> {
1041        let month = time::Month::try_from(v.month)
1042            .map_err(|e| Error::BadUsageError(format!("Invalid month {}: {}", v.month, e)))?;
1043        let date = time::Date::from_calendar_date(v.year() as i32, month, v.day).map_err(|e| {
1044            Error::BadUsageError(format!(
1045                "Invalid date {}-{}-{}: {}",
1046                v.year(),
1047                v.month,
1048                v.day,
1049                e
1050            ))
1051        })?;
1052        let time = time::Time::from_hms(v.hour, v.minute, v.second).map_err(|e| {
1053            Error::BadUsageError(format!(
1054                "Invalid time {}:{}:{}: {}",
1055                v.hour, v.minute, v.second, e
1056            ))
1057        })?;
1058        Ok(time::PrimitiveDateTime::new(date, time))
1059    }
1060
1061    fn from_datetime11(v: &Timestamp11) -> Result<Self> {
1062        let month = time::Month::try_from(v.month)
1063            .map_err(|e| Error::BadUsageError(format!("Invalid month {}: {}", v.month, e)))?;
1064        let date = time::Date::from_calendar_date(v.year() as i32, month, v.day).map_err(|e| {
1065            Error::BadUsageError(format!(
1066                "Invalid date {}-{}-{}: {}",
1067                v.year(),
1068                v.month,
1069                v.day,
1070                e
1071            ))
1072        })?;
1073        let time = time::Time::from_hms_micro(v.hour, v.minute, v.second, v.microsecond())
1074            .map_err(|e| {
1075                Error::BadUsageError(format!(
1076                    "Invalid time {}:{}:{}.{}: {}",
1077                    v.hour,
1078                    v.minute,
1079                    v.second,
1080                    v.microsecond(),
1081                    e
1082                ))
1083            })?;
1084        Ok(time::PrimitiveDateTime::new(date, time))
1085    }
1086}
1087
1088// ============================================================================
1089// rust_decimal support
1090// ============================================================================
1091
1092#[cfg(feature = "with-rust-decimal")]
1093impl FromRawValue<'_> for rust_decimal::Decimal {
1094    fn from_decimal(v: &[u8]) -> Result<Self> {
1095        let s = from_utf8(v).map_err(|e| {
1096            Error::BadUsageError(format!("Cannot decode MySQL DECIMAL to string: {}", e))
1097        })?;
1098        s.parse()
1099            .map_err(|e| Error::BadUsageError(format!("Cannot parse Decimal from '{}': {}", s, e)))
1100    }
1101
1102    fn from_str(v: &[u8]) -> Result<Self> {
1103        Self::from_decimal(v)
1104    }
1105}