Skip to main content

drizzle_mysql/
driver.rs

1//! Row decoding shared by the `MySQL` drivers.
2//!
3//! A driver exposes its rows through [`MySQLRowAccess`]; [`MySQLRow`] wraps
4//! one so generated models and `FromDrizzleRow` types can decode it. There
5//! is no connection, pool or runtime code here; the drivers live in the
6//! `drizzle` crate. With the `mysql-common` feature, `mysql_common::Row`
7//! implements [`MySQLRowAccess`] and `MySQLValue` converts to
8//! `mysql_common::Value`.
9
10#[cfg(feature = "query")]
11use crate::traits::MySQLJsonStorage;
12use crate::{
13    prelude::*,
14    traits::DrizzleMySQLColumn,
15    values::{MySQLValue, OwnedMySQLValue},
16};
17use drizzle_core::{FromDrizzleRow, error::DrizzleError};
18
19#[cfg(not(feature = "std"))]
20use alloc::format;
21
22/// A driver row whose cells can be read as [`MySQLValue`]s.
23///
24/// Implemented for `[OwnedMySQLValue]`, `Vec<OwnedMySQLValue>`,
25/// `[MySQLValue]`, and (with `mysql-common`) `mysql_common::Row`.
26/// Implementations must return `Ok(None)` for a missing or already consumed
27/// cell, and must not panic on a bad offset or conversion.
28pub trait MySQLRowAccess {
29    /// Returns the cell at `offset` (0-based), or `None` if there is none.
30    ///
31    /// # Errors
32    ///
33    /// Returns an error when the driver cannot read the row.
34    fn value_at(&self, offset: usize) -> Result<Option<MySQLValue<'_>>, DrizzleError>;
35
36    /// Returns whether the cell at `offset` is a `BIT` column, whose bytes
37    /// are a packed big-endian number.
38    ///
39    /// The default is `false`. Drivers override it from their column
40    /// metadata, so integer and `bool` decoders read `BIT` bytes as a number
41    /// while byte decoders keep them as bytes.
42    fn is_bit_at(&self, _offset: usize) -> bool {
43        false
44    }
45}
46
47/// A borrowed row that [`FromDrizzleRow`] types decode from.
48///
49/// # Examples
50///
51/// ```
52/// use drizzle_core::FromDrizzleRow;
53/// use drizzle_mysql::MySQLRow;
54/// use drizzle_mysql::values::OwnedMySQLValue;
55///
56/// let cells = vec![
57///     OwnedMySQLValue::Int(7),
58///     OwnedMySQLValue::Bytes(b"Alice".to_vec()),
59///     OwnedMySQLValue::Null,
60/// ];
61/// let row = MySQLRow::new(&cells);
62///
63/// assert_eq!(i64::from_row_at(&row, 0)?, 7);
64/// assert_eq!(String::from_row_at(&row, 1)?, "Alice");
65/// assert_eq!(Option::<String>::from_row_at(&row, 2)?, None);
66/// assert!(row.is_null_at(3).is_err());
67/// # Ok::<(), drizzle_core::error::DrizzleError>(())
68/// ```
69#[derive(Debug, Clone, Copy)]
70pub struct MySQLRow<'row, R: ?Sized> {
71    inner: &'row R,
72}
73
74impl<'row, R: MySQLRowAccess + ?Sized> MySQLRow<'row, R> {
75    /// Wraps a driver row without copying it.
76    #[must_use]
77    pub const fn new(inner: &'row R) -> Self {
78        Self { inner }
79    }
80
81    fn value_at(&self, offset: usize) -> Result<MySQLValue<'_>, DrizzleError> {
82        self.inner.value_at(offset)?.ok_or_else(|| {
83            DrizzleError::ConversionError(
84                format!("MySQL row has no available column at offset {offset}").into(),
85            )
86        })
87    }
88
89    fn decode_at<T: DecodeMySQLValue>(&self, offset: usize) -> Result<T, DrizzleError> {
90        let value = self.value_at(offset)?;
91        if self.inner.is_bit_at(offset) {
92            T::decode_bit(value)
93        } else {
94            T::decode(value)
95        }
96    }
97
98    /// Returns whether the cell at `offset` is SQL `NULL`.
99    ///
100    /// # Errors
101    ///
102    /// Returns an error when the row has no cell at `offset` or the driver
103    /// cannot read the row.
104    pub fn is_null_at(&self, offset: usize) -> Result<bool, DrizzleError> {
105        Ok(self.value_at(offset)?.is_null())
106    }
107
108    /// Decodes a custom column through its type-owned codec.
109    ///
110    /// # Errors
111    ///
112    /// Returns an error when the row has no cell at `offset`, the driver
113    /// cannot read the row, or the codec rejects the stored value.
114    #[doc(hidden)]
115    pub fn decode_column<T: DrizzleMySQLColumn>(&self, offset: usize) -> Result<T, DrizzleError> {
116        T::decode(self.value_at(offset)?)
117    }
118}
119
120impl MySQLRowAccess for [OwnedMySQLValue] {
121    fn value_at(&self, offset: usize) -> Result<Option<MySQLValue<'_>>, DrizzleError> {
122        Ok(self.get(offset).map(MySQLValue::from))
123    }
124}
125
126impl MySQLRowAccess for Vec<OwnedMySQLValue> {
127    fn value_at(&self, offset: usize) -> Result<Option<MySQLValue<'_>>, DrizzleError> {
128        self.as_slice().value_at(offset)
129    }
130}
131
132impl MySQLRowAccess for [MySQLValue<'_>] {
133    fn value_at(&self, offset: usize) -> Result<Option<MySQLValue<'_>>, DrizzleError> {
134        Ok(self.get(offset).map(MySQLValue::as_ref))
135    }
136}
137
138#[doc(hidden)]
139pub trait DecodeMySQLValue: Sized {
140    const EXPECTED: &'static str;
141
142    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError>;
143
144    fn decode_bit(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
145        Self::decode(value)
146    }
147}
148
149impl<T: DrizzleMySQLColumn> DecodeMySQLValue for T {
150    const EXPECTED: &'static str = T::SQL_TYPE;
151
152    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
153        T::decode(value)
154    }
155}
156
157/// Decodes a binary value emitted by the MySQL relational JSON projection.
158///
159/// This is a macro/runtime seam, not an application-level codec API. The SQL
160/// renderer tags binary values and carries their bytes as hexadecimal text so
161/// MySQL's JSON constructors never reinterpret the binary character set.
162#[cfg(feature = "query")]
163#[doc(hidden)]
164pub fn decode_blob<T: DecodeMySQLValue>(projected: &serde_json::Value) -> Result<T, DrizzleError> {
165    T::decode(MySQLValue::from(projected_binary(projected)?))
166}
167
168#[cfg(feature = "query")]
169fn projected_binary(projected: &serde_json::Value) -> Result<Vec<u8>, DrizzleError> {
170    let object = projected.as_object().ok_or_else(|| {
171        DrizzleError::ConversionError("projected MySQL binary value is not an object".into())
172    })?;
173    if object
174        .get("$drizzle_storage")
175        .and_then(serde_json::Value::as_str)
176        != Some("blob")
177    {
178        return Err(DrizzleError::ConversionError(
179            "projected MySQL binary value has an invalid storage tag".into(),
180        ));
181    }
182    let hex = object
183        .get("$drizzle_value")
184        .and_then(serde_json::Value::as_str)
185        .ok_or_else(|| {
186            DrizzleError::ConversionError(
187                "projected MySQL binary value is missing its hexadecimal payload".into(),
188            )
189        })?;
190    if hex.len() % 2 != 0 {
191        return Err(DrizzleError::ConversionError(
192            "projected MySQL binary value has an odd-length hexadecimal payload".into(),
193        ));
194    }
195
196    let mut bytes = Vec::with_capacity(hex.len() / 2);
197    for pair in hex.as_bytes().chunks(2) {
198        let high = decode_hex_nibble(pair[0])?;
199        let low = decode_hex_nibble(pair[1])?;
200        bytes.push((high << 4) | low);
201    }
202    Ok(bytes)
203}
204
205/// Decodes a text scalar emitted by MySQL's JSON projection using the same
206/// codec as a value returned by the binary protocol.
207#[cfg(feature = "query")]
208#[doc(hidden)]
209pub fn decode_text<T: DecodeMySQLValue>(projected: &serde_json::Value) -> Result<T, DrizzleError> {
210    let text = projected.as_str().ok_or_else(|| {
211        DrizzleError::ConversionError("projected MySQL text value is not a string".into())
212    })?;
213    T::decode(MySQLValue::from(text))
214}
215
216/// Decodes a custom column emitted by MySQL's relational JSON projection.
217#[cfg(feature = "query")]
218#[doc(hidden)]
219pub fn decode_projected<T: DrizzleMySQLColumn>(
220    projected: &serde_json::Value,
221) -> Result<T, DrizzleError> {
222    T::decode_json(projected)
223}
224
225#[cfg(feature = "query")]
226#[doc(hidden)]
227pub fn projected_value(
228    projected: &serde_json::Value,
229    storage: MySQLJsonStorage,
230) -> Result<OwnedMySQLValue, DrizzleError> {
231    match storage {
232        MySQLJsonStorage::Signed => projected
233            .as_i64()
234            .map(OwnedMySQLValue::Int)
235            .ok_or_else(|| projected_error("signed integer")),
236        MySQLJsonStorage::Unsigned => projected
237            .as_u64()
238            .map(OwnedMySQLValue::UInt)
239            .ok_or_else(|| projected_error("unsigned integer")),
240        MySQLJsonStorage::Double => projected
241            .as_f64()
242            .map(OwnedMySQLValue::Double)
243            .ok_or_else(|| projected_error("number")),
244        MySQLJsonStorage::Boolean => projected
245            .as_bool()
246            .map(i64::from)
247            .or_else(|| projected.as_i64().filter(|value| matches!(value, 0 | 1)))
248            .map(OwnedMySQLValue::Int)
249            .ok_or_else(|| projected_error("boolean")),
250        MySQLJsonStorage::Text => projected
251            .as_str()
252            .map(|value| OwnedMySQLValue::Bytes(value.as_bytes().to_vec()))
253            .ok_or_else(|| projected_error("string")),
254        MySQLJsonStorage::SignedText => projected_text(projected)?
255            .parse()
256            .map(OwnedMySQLValue::Int)
257            .map_err(|_| projected_error("signed integer string")),
258        MySQLJsonStorage::UnsignedText => projected_text(projected)?
259            .parse()
260            .map(OwnedMySQLValue::UInt)
261            .map_err(|_| projected_error("unsigned integer string")),
262        MySQLJsonStorage::FloatText => projected_text(projected)?
263            .parse()
264            .map(OwnedMySQLValue::Float)
265            .map_err(|_| projected_error("float string")),
266        MySQLJsonStorage::Binary => projected_binary(projected).map(OwnedMySQLValue::Bytes),
267        MySQLJsonStorage::Json => serde_json::to_vec(projected)
268            .map(OwnedMySQLValue::Bytes)
269            .map_err(Into::into),
270        MySQLJsonStorage::Date => projected_date(projected_text(projected)?),
271        MySQLJsonStorage::Time => projected_time(projected_text(projected)?),
272        MySQLJsonStorage::DateTime => projected_datetime(projected_text(projected)?),
273    }
274}
275
276#[cfg(feature = "query")]
277fn projected_text(projected: &serde_json::Value) -> Result<&str, DrizzleError> {
278    projected.as_str().ok_or_else(|| projected_error("string"))
279}
280
281#[cfg(feature = "query")]
282fn projected_date(value: &str) -> Result<OwnedMySQLValue, DrizzleError> {
283    let mut parts = value.split('-');
284    let year = parse_projected_part(parts.next(), "date")?;
285    let month = parse_projected_part(parts.next(), "date")?;
286    let day = parse_projected_part(parts.next(), "date")?;
287    if parts.next().is_some() {
288        return Err(projected_error("date string"));
289    }
290    Ok(OwnedMySQLValue::Date {
291        year,
292        month,
293        day,
294        hour: 0,
295        minute: 0,
296        second: 0,
297        microseconds: 0,
298    })
299}
300
301#[cfg(feature = "query")]
302fn projected_datetime(value: &str) -> Result<OwnedMySQLValue, DrizzleError> {
303    let (date, time) = value
304        .split_once(' ')
305        .ok_or_else(|| projected_error("datetime string"))?;
306    let OwnedMySQLValue::Date {
307        year, month, day, ..
308    } = projected_date(date)?
309    else {
310        unreachable!();
311    };
312    let (hour, minute, second, microseconds) = projected_clock(time, "datetime")?;
313    let hour = u8::try_from(hour).map_err(|_| projected_error("datetime string"))?;
314    Ok(OwnedMySQLValue::Date {
315        year,
316        month,
317        day,
318        hour,
319        minute,
320        second,
321        microseconds,
322    })
323}
324
325#[cfg(feature = "query")]
326fn projected_time(value: &str) -> Result<OwnedMySQLValue, DrizzleError> {
327    let (negative, value) = value
328        .strip_prefix('-')
329        .map_or((false, value), |value| (true, value));
330    let (hours, minutes, seconds, microseconds) = projected_clock(value, "time")?;
331    Ok(OwnedMySQLValue::Time {
332        negative,
333        days: hours / 24,
334        hours: u8::try_from(hours % 24).expect("hour remainder is below 24"),
335        minutes,
336        seconds,
337        microseconds,
338    })
339}
340
341#[cfg(feature = "query")]
342fn projected_clock(value: &str, kind: &str) -> Result<(u32, u8, u8, u32), DrizzleError> {
343    let mut parts = value.split(':');
344    let hour = parse_projected_part(parts.next(), kind)?;
345    let minute = parse_projected_part(parts.next(), kind)?;
346    let second = parts.next().ok_or_else(|| projected_error(kind))?;
347    if parts.next().is_some() {
348        return Err(projected_error(kind));
349    }
350    let (second, fraction) = second.split_once('.').unwrap_or((second, ""));
351    let second = second.parse().map_err(|_| projected_error(kind))?;
352    if minute > 59 || second > 59 {
353        return Err(projected_error(kind));
354    }
355    if fraction.len() > 6 || !fraction.bytes().all(|byte| byte.is_ascii_digit()) {
356        return Err(projected_error(kind));
357    }
358    let microseconds = if fraction.is_empty() {
359        0
360    } else {
361        let value: u32 = fraction.parse().map_err(|_| projected_error(kind))?;
362        value * 10_u32.pow(u32::try_from(6 - fraction.len()).expect("bounded fraction"))
363    };
364    Ok((hour, minute, second, microseconds))
365}
366
367#[cfg(feature = "query")]
368fn parse_projected_part<T: core::str::FromStr>(
369    value: Option<&str>,
370    kind: &str,
371) -> Result<T, DrizzleError> {
372    value
373        .ok_or_else(|| projected_error(kind))?
374        .parse()
375        .map_err(|_| projected_error(kind))
376}
377
378#[cfg(feature = "query")]
379fn projected_error(expected: &str) -> DrizzleError {
380    DrizzleError::ConversionError(format!("projected MySQL value is not a JSON {expected}").into())
381}
382
383#[cfg(feature = "query")]
384fn decode_hex_nibble(value: u8) -> Result<u8, DrizzleError> {
385    match value {
386        b'0'..=b'9' => Ok(value - b'0'),
387        b'a'..=b'f' => Ok(value - b'a' + 10),
388        b'A'..=b'F' => Ok(value - b'A' + 10),
389        _ => Err(DrizzleError::ConversionError(
390            "projected MySQL binary value contains non-hexadecimal text".into(),
391        )),
392    }
393}
394
395fn conversion_error(expected: &str, value: &MySQLValue<'_>) -> DrizzleError {
396    DrizzleError::ConversionError(
397        format!(
398            "expected {expected}, got MySQL {}",
399            match value {
400                MySQLValue::Null => "NULL",
401                MySQLValue::Bytes(_) => "bytes",
402                MySQLValue::Int(_) => "signed integer",
403                MySQLValue::UInt(_) => "unsigned integer",
404                MySQLValue::Float(_) => "float",
405                MySQLValue::Double(_) => "double",
406                MySQLValue::Date { .. } => "date/datetime",
407                MySQLValue::Time { .. } => "time",
408            }
409        )
410        .into(),
411    )
412}
413
414fn utf8(value: &[u8]) -> Result<&str, DrizzleError> {
415    core::str::from_utf8(value)
416        .map_err(|error| DrizzleError::ConversionError(error.to_string().into()))
417}
418
419macro_rules! impl_integer_decode {
420    ($($ty:ty),+ $(,)?) => {$(
421        impl DecodeMySQLValue for $ty {
422            const EXPECTED: &'static str = stringify!($ty);
423
424            fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
425                match value {
426                    MySQLValue::Int(value) => Self::try_from(value).map_err(Into::into),
427                    MySQLValue::UInt(value) => Self::try_from(value).map_err(Into::into),
428                    MySQLValue::Bytes(value) => utf8(value.as_ref())?
429                        .parse::<Self>()
430                        .map_err(Into::into),
431                    other => Err(conversion_error(Self::EXPECTED, &other)),
432                }
433            }
434
435            fn decode_bit(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
436                match value {
437                    MySQLValue::Bytes(value) => Self::try_from(mysql_bit_to_u64(value.as_ref())?)
438                        .map_err(Into::into),
439                    other => Self::decode(other),
440                }
441            }
442        }
443    )+};
444}
445
446impl_integer_decode!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
447
448impl DecodeMySQLValue for f32 {
449    const EXPECTED: &'static str = "f32";
450
451    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
452        match value {
453            MySQLValue::Float(value) => Ok(value),
454            MySQLValue::Bytes(value) => utf8(value.as_ref())?.parse().map_err(Into::into),
455            other => Err(conversion_error(Self::EXPECTED, &other)),
456        }
457    }
458}
459
460impl DecodeMySQLValue for f64 {
461    const EXPECTED: &'static str = "f64";
462
463    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
464        match value {
465            MySQLValue::Double(value) => Ok(value),
466            MySQLValue::Float(value) => Ok(value.into()),
467            MySQLValue::Bytes(value) => utf8(value.as_ref())?.parse().map_err(Into::into),
468            other => Err(conversion_error(Self::EXPECTED, &other)),
469        }
470    }
471}
472
473impl DecodeMySQLValue for bool {
474    const EXPECTED: &'static str = "bool";
475
476    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
477        match value {
478            MySQLValue::Int(value) => Ok(value != 0),
479            MySQLValue::UInt(value) => Ok(value != 0),
480            MySQLValue::Bytes(value) if value.as_ref() == b"0" => Ok(false),
481            MySQLValue::Bytes(value) if value.as_ref() == b"1" => Ok(true),
482            other => Err(conversion_error(Self::EXPECTED, &other)),
483        }
484    }
485
486    fn decode_bit(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
487        match value {
488            MySQLValue::Bytes(value) => Ok(mysql_bit_to_u64(value.as_ref())? != 0),
489            other => Self::decode(other),
490        }
491    }
492}
493
494impl DecodeMySQLValue for String {
495    const EXPECTED: &'static str = "UTF-8 text";
496
497    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
498        match value {
499            MySQLValue::Bytes(value) => Ok(String::from(utf8(value.as_ref())?)),
500            other => Err(conversion_error(Self::EXPECTED, &other)),
501        }
502    }
503}
504
505impl DecodeMySQLValue for char {
506    const EXPECTED: &'static str = "one Unicode scalar";
507
508    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
509        let text = String::decode(value)?;
510        let mut chars = text.chars();
511        match (chars.next(), chars.next()) {
512            (Some(value), None) => Ok(value),
513            _ => Err(DrizzleError::ConversionError(
514                "expected exactly one Unicode scalar".into(),
515            )),
516        }
517    }
518}
519
520impl DecodeMySQLValue for Vec<u8> {
521    const EXPECTED: &'static str = "bytes";
522
523    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
524        match value {
525            MySQLValue::Bytes(value) => Ok(value.into_owned()),
526            other => Err(conversion_error(Self::EXPECTED, &other)),
527        }
528    }
529}
530
531impl<const N: usize> DecodeMySQLValue for [u8; N] {
532    const EXPECTED: &'static str = "fixed-size byte array";
533
534    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
535        let value = Vec::<u8>::decode(value)?;
536        value.try_into().map_err(|value: Vec<u8>| {
537            DrizzleError::ConversionError(
538                format!("expected {N} bytes, got {} bytes", value.len(),).into(),
539            )
540        })
541    }
542}
543
544impl<const N: usize> DecodeMySQLValue for [char; N] {
545    const EXPECTED: &'static str = "fixed-size character array";
546
547    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
548        let value = String::decode(value)?;
549        let chars: Vec<char> = value.chars().collect();
550        chars.try_into().map_err(|chars: Vec<char>| {
551            DrizzleError::ConversionError(
552                format!("expected {N} characters, got {} characters", chars.len(),).into(),
553            )
554        })
555    }
556}
557
558macro_rules! impl_row_decode {
559    ($($ty:ty),+ $(,)?) => {$(
560        impl<'row, R: MySQLRowAccess + ?Sized> FromDrizzleRow<MySQLRow<'row, R>> for $ty {
561            const COLUMN_COUNT: usize = 1;
562
563            fn from_row_at(
564                row: &MySQLRow<'row, R>,
565                offset: usize,
566            ) -> Result<Self, DrizzleError> {
567                row.decode_at::<$ty>(offset)
568            }
569        }
570    )+};
571}
572
573impl_row_decode!(
574    i8,
575    i16,
576    i32,
577    i64,
578    isize,
579    u8,
580    u16,
581    u32,
582    u64,
583    usize,
584    f32,
585    f64,
586    bool,
587    char,
588    String,
589    Vec<u8>,
590);
591
592impl<'row, R: MySQLRowAccess + ?Sized, const N: usize> FromDrizzleRow<MySQLRow<'row, R>>
593    for [u8; N]
594{
595    const COLUMN_COUNT: usize = 1;
596
597    fn from_row_at(row: &MySQLRow<'row, R>, offset: usize) -> Result<Self, DrizzleError> {
598        row.decode_at::<Self>(offset)
599    }
600}
601
602impl<'row, R: MySQLRowAccess + ?Sized, const N: usize> FromDrizzleRow<MySQLRow<'row, R>>
603    for [char; N]
604{
605    const COLUMN_COUNT: usize = 1;
606
607    fn from_row_at(row: &MySQLRow<'row, R>, offset: usize) -> Result<Self, DrizzleError> {
608        row.decode_at::<Self>(offset)
609    }
610}
611
612impl<'row, R, T> FromDrizzleRow<MySQLRow<'row, R>> for Option<T>
613where
614    R: MySQLRowAccess + ?Sized,
615    T: FromDrizzleRow<MySQLRow<'row, R>>,
616{
617    const COLUMN_COUNT: usize = T::COLUMN_COUNT;
618
619    /// A multi-column `T` (a select model in a LEFT JOIN) is absent only
620    /// when every one of its columns is NULL.
621    fn from_row_at(row: &MySQLRow<'row, R>, offset: usize) -> Result<Self, DrizzleError> {
622        for column in offset..offset + T::COLUMN_COUNT.max(1) {
623            if !row.is_null_at(column)? {
624                return T::from_row_at(row, offset).map(Some);
625            }
626        }
627        Ok(None)
628    }
629}
630
631#[cfg(feature = "compact-str")]
632impl DecodeMySQLValue for compact_str::CompactString {
633    const EXPECTED: &'static str = "UTF-8 text";
634
635    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
636        String::decode(value).map(Into::into)
637    }
638}
639
640#[cfg(feature = "compact-str")]
641impl_row_decode!(compact_str::CompactString);
642
643#[cfg(feature = "arrayvec")]
644impl<const N: usize> DecodeMySQLValue for arrayvec::ArrayString<N> {
645    const EXPECTED: &'static str = "bounded UTF-8 text";
646
647    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
648        let value = String::decode(value)?;
649        Self::try_from(value.as_str())
650            .map_err(|error| DrizzleError::ConversionError(error.to_string().into()))
651    }
652}
653
654#[cfg(feature = "arrayvec")]
655impl<'row, R: MySQLRowAccess + ?Sized, const N: usize> FromDrizzleRow<MySQLRow<'row, R>>
656    for arrayvec::ArrayString<N>
657{
658    const COLUMN_COUNT: usize = 1;
659
660    fn from_row_at(row: &MySQLRow<'row, R>, offset: usize) -> Result<Self, DrizzleError> {
661        row.decode_at::<Self>(offset)
662    }
663}
664
665#[cfg(feature = "arrayvec")]
666impl<const N: usize> DecodeMySQLValue for arrayvec::ArrayVec<u8, N> {
667    const EXPECTED: &'static str = "bounded bytes";
668
669    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
670        let value = Vec::<u8>::decode(value)?;
671        Self::try_from(value.as_slice())
672            .map_err(|error| DrizzleError::ConversionError(error.to_string().into()))
673    }
674}
675
676#[cfg(feature = "arrayvec")]
677impl<'row, R: MySQLRowAccess + ?Sized, const N: usize> FromDrizzleRow<MySQLRow<'row, R>>
678    for arrayvec::ArrayVec<u8, N>
679{
680    const COLUMN_COUNT: usize = 1;
681
682    fn from_row_at(row: &MySQLRow<'row, R>, offset: usize) -> Result<Self, DrizzleError> {
683        row.decode_at::<Self>(offset)
684    }
685}
686
687#[cfg(feature = "smallvec")]
688impl<const N: usize> DecodeMySQLValue for smallvec::SmallVec<[u8; N]> {
689    const EXPECTED: &'static str = "bytes";
690
691    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
692        Ok(Vec::<u8>::decode(value)?.into())
693    }
694}
695
696#[cfg(feature = "smallvec")]
697impl<'row, R: MySQLRowAccess + ?Sized, const N: usize> FromDrizzleRow<MySQLRow<'row, R>>
698    for smallvec::SmallVec<[u8; N]>
699{
700    const COLUMN_COUNT: usize = 1;
701
702    fn from_row_at(row: &MySQLRow<'row, R>, offset: usize) -> Result<Self, DrizzleError> {
703        row.decode_at::<Self>(offset)
704    }
705}
706
707#[cfg(feature = "bytes")]
708impl DecodeMySQLValue for bytes::Bytes {
709    const EXPECTED: &'static str = "bytes";
710
711    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
712        Vec::<u8>::decode(value).map(Into::into)
713    }
714}
715
716#[cfg(feature = "bytes")]
717impl DecodeMySQLValue for bytes::BytesMut {
718    const EXPECTED: &'static str = "bytes";
719
720    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
721        Vec::<u8>::decode(value).map(|value| Self::from(value.as_slice()))
722    }
723}
724
725#[cfg(feature = "bytes")]
726impl_row_decode!(bytes::Bytes, bytes::BytesMut);
727
728#[cfg(feature = "serde")]
729impl DecodeMySQLValue for serde_json::Value {
730    const EXPECTED: &'static str = "JSON text";
731
732    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
733        match value {
734            MySQLValue::Bytes(value) => serde_json::from_slice(value.as_ref()).map_err(Into::into),
735            other => Err(conversion_error(Self::EXPECTED, &other)),
736        }
737    }
738}
739
740#[cfg(feature = "serde")]
741impl_row_decode!(serde_json::Value);
742
743#[cfg(feature = "uuid")]
744impl DecodeMySQLValue for uuid::Uuid {
745    const EXPECTED: &'static str = "16-byte or textual UUID";
746
747    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
748        match value {
749            MySQLValue::Bytes(value) if value.len() == 16 => {
750                Self::from_slice(value.as_ref()).map_err(Into::into)
751            }
752            MySQLValue::Bytes(value) => Self::parse_str(utf8(value.as_ref())?).map_err(Into::into),
753            other => Err(conversion_error(Self::EXPECTED, &other)),
754        }
755    }
756}
757
758#[cfg(feature = "uuid")]
759impl_row_decode!(uuid::Uuid);
760
761#[cfg(feature = "rust-decimal")]
762impl DecodeMySQLValue for rust_decimal::Decimal {
763    const EXPECTED: &'static str = "decimal text within rust_decimal range";
764
765    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
766        match value {
767            MySQLValue::Bytes(value) => {
768                utf8(value.as_ref())?
769                    .parse()
770                    .map_err(|error: rust_decimal::Error| {
771                        DrizzleError::ConversionError(error.to_string().into())
772                    })
773            }
774            other => Err(conversion_error(Self::EXPECTED, &other)),
775        }
776    }
777}
778
779#[cfg(feature = "rust-decimal")]
780impl_row_decode!(rust_decimal::Decimal);
781
782#[cfg(feature = "chrono")]
783fn chrono_date(year: u16, month: u8, day: u8) -> Result<chrono::NaiveDate, DrizzleError> {
784    chrono::NaiveDate::from_ymd_opt(i32::from(year), u32::from(month), u32::from(day)).ok_or_else(
785        || {
786            DrizzleError::ConversionError(
787                format!("invalid MySQL date {year:04}-{month:02}-{day:02}").into(),
788            )
789        },
790    )
791}
792
793#[cfg(feature = "chrono")]
794fn chrono_time(
795    negative: bool,
796    days: u32,
797    hours: u8,
798    minutes: u8,
799    seconds: u8,
800    microseconds: u32,
801) -> Result<chrono::NaiveTime, DrizzleError> {
802    if negative || days != 0 {
803        return Err(DrizzleError::ConversionError(
804            "MySQL duration outside a single non-negative day cannot decode as chrono::NaiveTime"
805                .into(),
806        ));
807    }
808    chrono::NaiveTime::from_hms_micro_opt(
809        u32::from(hours),
810        u32::from(minutes),
811        u32::from(seconds),
812        microseconds,
813    )
814    .ok_or_else(|| DrizzleError::ConversionError("invalid MySQL time".into()))
815}
816
817#[cfg(feature = "chrono")]
818impl DecodeMySQLValue for chrono::NaiveDate {
819    const EXPECTED: &'static str = "MySQL DATE";
820
821    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
822        match value {
823            MySQLValue::Date {
824                year, month, day, ..
825            } => chrono_date(year, month, day),
826            MySQLValue::Bytes(value) => {
827                utf8(value.as_ref())?
828                    .parse()
829                    .map_err(|error: chrono::ParseError| {
830                        DrizzleError::ConversionError(error.to_string().into())
831                    })
832            }
833            other => Err(conversion_error(Self::EXPECTED, &other)),
834        }
835    }
836}
837
838#[cfg(feature = "chrono")]
839impl DecodeMySQLValue for chrono::NaiveTime {
840    const EXPECTED: &'static str = "MySQL TIME within one day";
841
842    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
843        match value {
844            MySQLValue::Time {
845                negative,
846                days,
847                hours,
848                minutes,
849                seconds,
850                microseconds,
851            } => chrono_time(negative, days, hours, minutes, seconds, microseconds),
852            MySQLValue::Bytes(value) => {
853                utf8(value.as_ref())?
854                    .parse()
855                    .map_err(|error: chrono::ParseError| {
856                        DrizzleError::ConversionError(error.to_string().into())
857                    })
858            }
859            other => Err(conversion_error(Self::EXPECTED, &other)),
860        }
861    }
862}
863
864#[cfg(feature = "chrono")]
865impl DecodeMySQLValue for chrono::NaiveDateTime {
866    const EXPECTED: &'static str = "MySQL DATETIME/TIMESTAMP";
867
868    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
869        match value {
870            MySQLValue::Date {
871                year,
872                month,
873                day,
874                hour,
875                minute,
876                second,
877                microseconds,
878            } => chrono_date(year, month, day)?
879                .and_hms_micro_opt(
880                    u32::from(hour),
881                    u32::from(minute),
882                    u32::from(second),
883                    microseconds,
884                )
885                .ok_or_else(|| DrizzleError::ConversionError("invalid MySQL datetime".into())),
886            MySQLValue::Bytes(value) => {
887                let value = utf8(value.as_ref())?;
888                chrono::NaiveDateTime::parse_from_str(value, "%Y-%m-%d %H:%M:%S%.f")
889                    .or_else(|_| value.parse())
890                    .map_err(|error: chrono::ParseError| {
891                        DrizzleError::ConversionError(error.to_string().into())
892                    })
893            }
894            other => Err(conversion_error(Self::EXPECTED, &other)),
895        }
896    }
897}
898
899fn mysql_bit_to_u64(value: &[u8]) -> Result<u64, DrizzleError> {
900    if value.len() > core::mem::size_of::<u64>() {
901        return Err(DrizzleError::ConversionError(
902            format!("MySQL BIT value exceeds 64 bits: {} bytes", value.len()).into(),
903        ));
904    }
905    Ok(value
906        .iter()
907        .fold(0_u64, |result, byte| (result << 8) | u64::from(*byte)))
908}
909
910#[cfg(feature = "chrono")]
911impl DecodeMySQLValue for chrono::DateTime<chrono::Utc> {
912    const EXPECTED: &'static str = "UTC MySQL TIMESTAMP";
913
914    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
915        chrono::NaiveDateTime::decode(value)
916            .map(|value| Self::from_naive_utc_and_offset(value, chrono::Utc))
917    }
918}
919
920#[cfg(feature = "chrono")]
921impl DecodeMySQLValue for chrono::DateTime<chrono::FixedOffset> {
922    const EXPECTED: &'static str = "UTC MySQL TIMESTAMP";
923
924    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
925        let offset = chrono::FixedOffset::east_opt(0)
926            .ok_or_else(|| DrizzleError::ConversionError("invalid UTC offset".into()))?;
927        chrono::NaiveDateTime::decode(value)
928            .map(|value| Self::from_naive_utc_and_offset(value, offset))
929    }
930}
931
932#[cfg(feature = "chrono")]
933impl_row_decode!(
934    chrono::NaiveDate,
935    chrono::NaiveTime,
936    chrono::NaiveDateTime,
937    chrono::DateTime<chrono::Utc>,
938    chrono::DateTime<chrono::FixedOffset>,
939);
940
941#[cfg(feature = "time")]
942fn time_date(year: u16, month: u8, day: u8) -> Result<time::Date, DrizzleError> {
943    let month = time::Month::try_from(month)
944        .map_err(|error| DrizzleError::ConversionError(error.to_string().into()))?;
945    time::Date::from_calendar_date(i32::from(year), month, day)
946        .map_err(|error| DrizzleError::ConversionError(error.to_string().into()))
947}
948
949#[cfg(feature = "time")]
950fn time_time(
951    negative: bool,
952    days: u32,
953    hours: u8,
954    minutes: u8,
955    seconds: u8,
956    microseconds: u32,
957) -> Result<time::Time, DrizzleError> {
958    if negative || days != 0 {
959        return Err(DrizzleError::ConversionError(
960            "MySQL duration outside a single non-negative day cannot decode as time::Time".into(),
961        ));
962    }
963    time::Time::from_hms_micro(hours, minutes, seconds, microseconds)
964        .map_err(|error| DrizzleError::ConversionError(error.to_string().into()))
965}
966
967#[cfg(feature = "time")]
968impl DecodeMySQLValue for time::Date {
969    const EXPECTED: &'static str = "MySQL DATE";
970
971    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
972        match value {
973            MySQLValue::Date {
974                year, month, day, ..
975            } => time_date(year, month, day),
976            MySQLValue::Bytes(value) => time::Date::parse(
977                utf8(value.as_ref())?,
978                &time::format_description::well_known::Iso8601::DATE,
979            )
980            .map_err(|error| DrizzleError::ConversionError(error.to_string().into())),
981            other => Err(conversion_error(Self::EXPECTED, &other)),
982        }
983    }
984}
985
986#[cfg(feature = "time")]
987impl DecodeMySQLValue for time::Time {
988    const EXPECTED: &'static str = "MySQL TIME within one day";
989
990    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
991        match value {
992            MySQLValue::Time {
993                negative,
994                days,
995                hours,
996                minutes,
997                seconds,
998                microseconds,
999            } => time_time(negative, days, hours, minutes, seconds, microseconds),
1000            MySQLValue::Bytes(value) => {
1001                let text = utf8(value.as_ref())?;
1002                let description =
1003                    time::macros::format_description!("[hour]:[minute]:[second].[subsecond]");
1004                time::Time::parse(text, description)
1005                    .or_else(|_| {
1006                        time::Time::parse(
1007                            text,
1008                            time::macros::format_description!("[hour]:[minute]:[second]"),
1009                        )
1010                    })
1011                    .map_err(|error| DrizzleError::ConversionError(error.to_string().into()))
1012            }
1013            other => Err(conversion_error(Self::EXPECTED, &other)),
1014        }
1015    }
1016}
1017
1018#[cfg(feature = "time")]
1019impl DecodeMySQLValue for time::PrimitiveDateTime {
1020    const EXPECTED: &'static str = "MySQL DATETIME/TIMESTAMP";
1021
1022    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
1023        match value {
1024            MySQLValue::Date {
1025                year,
1026                month,
1027                day,
1028                hour,
1029                minute,
1030                second,
1031                microseconds,
1032            } => Ok(Self::new(
1033                time_date(year, month, day)?,
1034                time::Time::from_hms_micro(hour, minute, second, microseconds)
1035                    .map_err(|error| DrizzleError::ConversionError(error.to_string().into()))?,
1036            )),
1037            MySQLValue::Bytes(value) => {
1038                let text = utf8(value.as_ref())?;
1039                Self::parse(
1040                    text,
1041                    time::macros::format_description!(
1042                        "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"
1043                    ),
1044                )
1045                .or_else(|_| {
1046                    Self::parse(
1047                        text,
1048                        time::macros::format_description!(
1049                            "[year]-[month]-[day] [hour]:[minute]:[second]"
1050                        ),
1051                    )
1052                })
1053                .map_err(|error| DrizzleError::ConversionError(error.to_string().into()))
1054            }
1055            other => Err(conversion_error(Self::EXPECTED, &other)),
1056        }
1057    }
1058}
1059
1060#[cfg(feature = "time")]
1061impl DecodeMySQLValue for time::OffsetDateTime {
1062    const EXPECTED: &'static str = "UTC MySQL TIMESTAMP";
1063
1064    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
1065        time::PrimitiveDateTime::decode(value).map(|value| value.assume_utc())
1066    }
1067}
1068
1069#[cfg(feature = "time")]
1070impl_row_decode!(
1071    time::Date,
1072    time::Time,
1073    time::PrimitiveDateTime,
1074    time::OffsetDateTime,
1075);
1076
1077#[cfg(feature = "jiff")]
1078fn jiff_error(error: jiff::Error) -> DrizzleError {
1079    DrizzleError::ConversionError(error.to_string().into())
1080}
1081
1082#[cfg(feature = "jiff")]
1083fn jiff_out_of_range() -> DrizzleError {
1084    DrizzleError::ConversionError("MySQL date or time part is out of range for jiff".into())
1085}
1086
1087#[cfg(feature = "jiff")]
1088fn jiff_date(year: u16, month: u8, day: u8) -> Result<jiff::civil::Date, DrizzleError> {
1089    jiff::civil::Date::new(
1090        i16::try_from(year).map_err(|_| jiff_out_of_range())?,
1091        i8::try_from(month).map_err(|_| jiff_out_of_range())?,
1092        i8::try_from(day).map_err(|_| jiff_out_of_range())?,
1093    )
1094    .map_err(jiff_error)
1095}
1096
1097#[cfg(feature = "jiff")]
1098fn jiff_time(
1099    hours: u8,
1100    minutes: u8,
1101    seconds: u8,
1102    microseconds: u32,
1103) -> Result<jiff::civil::Time, DrizzleError> {
1104    let nanoseconds = i32::try_from(microseconds)
1105        .ok()
1106        .and_then(|micros| micros.checked_mul(1_000))
1107        .ok_or_else(jiff_out_of_range)?;
1108    jiff::civil::Time::new(
1109        i8::try_from(hours).map_err(|_| jiff_out_of_range())?,
1110        i8::try_from(minutes).map_err(|_| jiff_out_of_range())?,
1111        i8::try_from(seconds).map_err(|_| jiff_out_of_range())?,
1112        nanoseconds,
1113    )
1114    .map_err(jiff_error)
1115}
1116
1117#[cfg(feature = "jiff")]
1118impl DecodeMySQLValue for jiff::civil::Date {
1119    const EXPECTED: &'static str = "MySQL DATE";
1120
1121    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
1122        match value {
1123            MySQLValue::Date {
1124                year, month, day, ..
1125            } => jiff_date(year, month, day),
1126            MySQLValue::Bytes(value) => utf8(value.as_ref())?.parse().map_err(jiff_error),
1127            other => Err(conversion_error(Self::EXPECTED, &other)),
1128        }
1129    }
1130}
1131
1132#[cfg(feature = "jiff")]
1133impl DecodeMySQLValue for jiff::civil::Time {
1134    const EXPECTED: &'static str = "MySQL TIME within one day";
1135
1136    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
1137        match value {
1138            MySQLValue::Time {
1139                negative,
1140                days,
1141                hours,
1142                minutes,
1143                seconds,
1144                microseconds,
1145            } => {
1146                if negative || days != 0 {
1147                    return Err(DrizzleError::ConversionError(
1148                        "MySQL duration outside a single non-negative day cannot decode as \
1149                         jiff::civil::Time"
1150                            .into(),
1151                    ));
1152                }
1153                jiff_time(hours, minutes, seconds, microseconds)
1154            }
1155            MySQLValue::Bytes(value) => utf8(value.as_ref())?.parse().map_err(jiff_error),
1156            other => Err(conversion_error(Self::EXPECTED, &other)),
1157        }
1158    }
1159}
1160
1161#[cfg(feature = "jiff")]
1162impl DecodeMySQLValue for jiff::civil::DateTime {
1163    const EXPECTED: &'static str = "MySQL DATETIME/TIMESTAMP";
1164
1165    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
1166        match value {
1167            MySQLValue::Date {
1168                year,
1169                month,
1170                day,
1171                hour,
1172                minute,
1173                second,
1174                microseconds,
1175            } => Ok(jiff_date(year, month, day)?.to_datetime(jiff_time(
1176                hour,
1177                minute,
1178                second,
1179                microseconds,
1180            )?)),
1181            MySQLValue::Bytes(value) => utf8(value.as_ref())?.parse().map_err(jiff_error),
1182            other => Err(conversion_error(Self::EXPECTED, &other)),
1183        }
1184    }
1185}
1186
1187#[cfg(feature = "jiff")]
1188impl DecodeMySQLValue for jiff::Timestamp {
1189    const EXPECTED: &'static str = "UTC MySQL TIMESTAMP";
1190
1191    fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
1192        let utc = jiff::civil::DateTime::decode(value)?;
1193        jiff::tz::Offset::UTC.to_timestamp(utc).map_err(jiff_error)
1194    }
1195}
1196
1197#[cfg(feature = "jiff")]
1198impl_row_decode!(
1199    jiff::civil::Date,
1200    jiff::civil::Time,
1201    jiff::civil::DateTime,
1202    jiff::Timestamp,
1203);
1204
1205#[cfg(feature = "mysql-common")]
1206impl MySQLRowAccess for mysql_common::Row {
1207    fn value_at(&self, offset: usize) -> Result<Option<MySQLValue<'_>>, DrizzleError> {
1208        let Some(value) = self.as_ref(offset) else {
1209            return Ok(None);
1210        };
1211        Ok(Some(match value {
1212            mysql_common::Value::NULL => MySQLValue::Null,
1213            mysql_common::Value::Bytes(value) => MySQLValue::from(value.as_slice()),
1214            mysql_common::Value::Int(value) => MySQLValue::Int(*value),
1215            mysql_common::Value::UInt(value) => MySQLValue::UInt(*value),
1216            mysql_common::Value::Float(value) => MySQLValue::Float(*value),
1217            mysql_common::Value::Double(value) => MySQLValue::Double(*value),
1218            mysql_common::Value::Date(year, month, day, hour, minute, second, micros) => {
1219                MySQLValue::Date {
1220                    year: *year,
1221                    month: *month,
1222                    day: *day,
1223                    hour: *hour,
1224                    minute: *minute,
1225                    second: *second,
1226                    microseconds: *micros,
1227                }
1228            }
1229            mysql_common::Value::Time(negative, days, hours, minutes, seconds, micros) => {
1230                MySQLValue::Time {
1231                    negative: *negative,
1232                    days: *days,
1233                    hours: *hours,
1234                    minutes: *minutes,
1235                    seconds: *seconds,
1236                    microseconds: *micros,
1237                }
1238            }
1239        }))
1240    }
1241
1242    fn is_bit_at(&self, offset: usize) -> bool {
1243        self.columns_ref().get(offset).is_some_and(|column| {
1244            column.column_type() == mysql_common::constants::ColumnType::MYSQL_TYPE_BIT
1245        })
1246    }
1247}
1248
1249#[cfg(feature = "mysql-common")]
1250impl From<MySQLValue<'_>> for mysql_common::Value {
1251    fn from(value: MySQLValue<'_>) -> Self {
1252        match value {
1253            MySQLValue::Null => Self::NULL,
1254            MySQLValue::Bytes(value) => Self::Bytes(value.into_owned()),
1255            MySQLValue::Int(value) => Self::Int(value),
1256            MySQLValue::UInt(value) => Self::UInt(value),
1257            MySQLValue::Float(value) => Self::Float(value),
1258            MySQLValue::Double(value) => Self::Double(value),
1259            MySQLValue::Date {
1260                year,
1261                month,
1262                day,
1263                hour,
1264                minute,
1265                second,
1266                microseconds,
1267            } => Self::Date(year, month, day, hour, minute, second, microseconds),
1268            MySQLValue::Time {
1269                negative,
1270                days,
1271                hours,
1272                minutes,
1273                seconds,
1274                microseconds,
1275            } => Self::Time(negative, days, hours, minutes, seconds, microseconds),
1276        }
1277    }
1278}
1279
1280#[cfg(test)]
1281mod tests {
1282    use super::*;
1283
1284    fn decode<T: for<'row> FromDrizzleRow<MySQLRow<'row, [OwnedMySQLValue]>>>(
1285        values: &[OwnedMySQLValue],
1286    ) -> Result<T, DrizzleError> {
1287        T::from_row(&MySQLRow::new(values))
1288    }
1289
1290    #[test]
1291    fn signed_unsigned_null_and_offsets_decode_without_panics() {
1292        let values = [
1293            OwnedMySQLValue::Int(-1),
1294            OwnedMySQLValue::UInt(u64::MAX),
1295            OwnedMySQLValue::Null,
1296        ];
1297        assert_eq!(
1298            decode::<(i64, u64, Option<i32>)>(&values).unwrap(),
1299            (-1, u64::MAX, None)
1300        );
1301        assert!(decode::<(u64,)>(&values).is_err());
1302        assert!(decode::<(i64, u64, i32)>(&values).is_err());
1303        assert!(decode::<(i64, u64, Option<i32>, i32)>(&values).is_err());
1304    }
1305
1306    #[test]
1307    fn text_protocol_numbers_are_checked_for_overflow() {
1308        let valid = [OwnedMySQLValue::Bytes(b"255".to_vec())];
1309        assert_eq!(decode::<u8>(&valid).unwrap(), 255);
1310        let overflow = [OwnedMySQLValue::Bytes(b"256".to_vec())];
1311        assert!(decode::<u8>(&overflow).is_err());
1312        let negative = [OwnedMySQLValue::Bytes(b"-1".to_vec())];
1313        assert!(decode::<u64>(&negative).is_err());
1314    }
1315
1316    #[test]
1317    fn invalid_utf8_is_a_conversion_error() {
1318        let values = [OwnedMySQLValue::Bytes(vec![0xff])];
1319        assert!(decode::<String>(&values).is_err());
1320    }
1321
1322    #[cfg(feature = "query")]
1323    #[test]
1324    fn projected_binary_values_follow_the_checked_wire_codec() {
1325        let tagged = serde_json::json!({
1326            "$drizzle_storage": "blob",
1327            "$drizzle_value": "0001FEFF"
1328        });
1329        assert_eq!(decode_blob::<Vec<u8>>(&tagged).unwrap(), [0, 1, 254, 255]);
1330        assert!(
1331            decode_blob::<Vec<u8>>(&serde_json::json!({
1332                "$drizzle_storage": "text",
1333                "$drizzle_value": "00"
1334            }))
1335            .is_err()
1336        );
1337        assert!(
1338            decode_blob::<Vec<u8>>(&serde_json::json!({
1339                "$drizzle_storage": "blob",
1340                "$drizzle_value": "0"
1341            }))
1342            .is_err()
1343        );
1344    }
1345
1346    #[cfg(feature = "query")]
1347    #[test]
1348    fn projected_text_values_recover_wire_shapes() {
1349        assert_eq!(
1350            projected_value(
1351                &serde_json::json!("-9223372036854775801"),
1352                MySQLJsonStorage::SignedText
1353            )
1354            .unwrap(),
1355            OwnedMySQLValue::Int(-9_223_372_036_854_775_801)
1356        );
1357        assert_eq!(
1358            projected_value(&serde_json::json!("1.25"), MySQLJsonStorage::FloatText).unwrap(),
1359            OwnedMySQLValue::Float(1.25)
1360        );
1361        assert_eq!(
1362            projected_value(
1363                &serde_json::json!("2026-08-27 12:34:56.1234"),
1364                MySQLJsonStorage::DateTime,
1365            )
1366            .unwrap(),
1367            OwnedMySQLValue::Date {
1368                year: 2026,
1369                month: 8,
1370                day: 27,
1371                hour: 12,
1372                minute: 34,
1373                second: 56,
1374                microseconds: 123_400,
1375            }
1376        );
1377        assert_eq!(
1378            projected_value(&serde_json::json!("-49:02:03.4"), MySQLJsonStorage::Time).unwrap(),
1379            OwnedMySQLValue::Time {
1380                negative: true,
1381                days: 2,
1382                hours: 1,
1383                minutes: 2,
1384                seconds: 3,
1385                microseconds: 400_000,
1386            }
1387        );
1388    }
1389
1390    #[cfg(all(
1391        feature = "arrayvec",
1392        feature = "bytes",
1393        feature = "compact-str",
1394        feature = "smallvec"
1395    ))]
1396    #[test]
1397    fn feature_forwarded_text_and_binary_types_decode_through_the_same_row_contract() {
1398        let text = [OwnedMySQLValue::Bytes(b"drizzle".to_vec())];
1399        assert_eq!(
1400            decode::<compact_str::CompactString>(&text).unwrap(),
1401            "drizzle"
1402        );
1403        assert_eq!(
1404            decode::<arrayvec::ArrayString<8>>(&text).unwrap().as_str(),
1405            "drizzle"
1406        );
1407        assert!(decode::<arrayvec::ArrayString<3>>(&text).is_err());
1408
1409        let binary = [OwnedMySQLValue::Bytes(vec![1, 2, 3])];
1410        assert_eq!(
1411            decode::<arrayvec::ArrayVec<u8, 3>>(&binary)
1412                .unwrap()
1413                .as_slice(),
1414            &[1, 2, 3]
1415        );
1416        assert!(decode::<arrayvec::ArrayVec<u8, 2>>(&binary).is_err());
1417        assert_eq!(
1418            decode::<smallvec::SmallVec<[u8; 2]>>(&binary)
1419                .unwrap()
1420                .as_slice(),
1421            &[1, 2, 3]
1422        );
1423        assert_eq!(
1424            decode::<bytes::Bytes>(&binary).unwrap().as_ref(),
1425            &[1, 2, 3]
1426        );
1427    }
1428
1429    #[cfg(feature = "uuid")]
1430    #[test]
1431    fn uuid_accepts_binary_and_text_forms_but_rejects_malformed_bytes() {
1432        let expected = uuid::Uuid::from_u128(0x12345678_90ab_cdef_1234_567890abcdef);
1433        assert_eq!(
1434            decode::<uuid::Uuid>(&[OwnedMySQLValue::Bytes(expected.as_bytes().to_vec())]).unwrap(),
1435            expected
1436        );
1437        assert_eq!(
1438            decode::<uuid::Uuid>(&[OwnedMySQLValue::Bytes(expected.to_string().into_bytes())])
1439                .unwrap(),
1440            expected
1441        );
1442        assert!(decode::<uuid::Uuid>(&[OwnedMySQLValue::Bytes(vec![1, 2, 3])]).is_err());
1443    }
1444
1445    #[cfg(feature = "serde")]
1446    #[test]
1447    fn json_decode_reports_invalid_payloads() {
1448        let value =
1449            decode::<serde_json::Value>(&[OwnedMySQLValue::Bytes(br#"{"ok":true}"#.to_vec())])
1450                .unwrap();
1451        assert_eq!(value["ok"], true);
1452        assert!(decode::<serde_json::Value>(&[OwnedMySQLValue::Bytes(b"{".to_vec())]).is_err());
1453    }
1454
1455    #[cfg(feature = "chrono")]
1456    #[test]
1457    fn chrono_round_trips_binary_dates_and_rejects_zero_dates() {
1458        let expected = chrono::NaiveDate::from_ymd_opt(2026, 8, 26)
1459            .unwrap()
1460            .and_hms_micro_opt(12, 34, 56, 789)
1461            .unwrap();
1462        assert_eq!(
1463            decode::<chrono::NaiveDateTime>(&[OwnedMySQLValue::from(expected)]).unwrap(),
1464            expected
1465        );
1466        assert_eq!(
1467            decode::<chrono::NaiveDateTime>(&[OwnedMySQLValue::Bytes(
1468                b"2026-08-26 12:34:56.000789".to_vec(),
1469            )])
1470            .unwrap(),
1471            expected
1472        );
1473        assert!(
1474            decode::<chrono::NaiveDate>(&[OwnedMySQLValue::Date {
1475                year: 0,
1476                month: 0,
1477                day: 0,
1478                hour: 0,
1479                minute: 0,
1480                second: 0,
1481                microseconds: 0,
1482            }])
1483            .is_err()
1484        );
1485    }
1486
1487    #[cfg(feature = "time")]
1488    #[test]
1489    fn time_round_trips_binary_dates_and_rejects_duration_as_clock_time() {
1490        let expected = time::Date::from_calendar_date(2026, time::Month::August, 26)
1491            .unwrap()
1492            .with_hms_micro(12, 34, 56, 789)
1493            .unwrap();
1494        assert_eq!(
1495            decode::<time::PrimitiveDateTime>(&[OwnedMySQLValue::from(expected)]).unwrap(),
1496            expected
1497        );
1498        assert!(
1499            decode::<time::Time>(&[OwnedMySQLValue::Time {
1500                negative: false,
1501                days: 1,
1502                hours: 0,
1503                minutes: 0,
1504                seconds: 0,
1505                microseconds: 0,
1506            }])
1507            .is_err()
1508        );
1509    }
1510
1511    #[cfg(feature = "jiff")]
1512    #[test]
1513    fn jiff_round_trips_binary_dates_and_rejects_duration_as_clock_time() {
1514        let expected = jiff::civil::date(2026, 8, 26).at(12, 34, 56, 789_000);
1515        assert_eq!(
1516            decode::<jiff::civil::DateTime>(&[OwnedMySQLValue::from(expected)]).unwrap(),
1517            expected
1518        );
1519        let instant: jiff::Timestamp = "2026-08-26T12:34:56.000789Z".parse().unwrap();
1520        assert_eq!(
1521            decode::<jiff::Timestamp>(&[OwnedMySQLValue::from(instant)]).unwrap(),
1522            instant
1523        );
1524        assert!(
1525            decode::<jiff::civil::Time>(&[OwnedMySQLValue::Time {
1526                negative: false,
1527                days: 1,
1528                hours: 0,
1529                minutes: 0,
1530                seconds: 0,
1531                microseconds: 0,
1532            }])
1533            .is_err()
1534        );
1535    }
1536
1537    #[cfg(feature = "rust-decimal")]
1538    #[test]
1539    fn decimal_boundaries_fail_instead_of_truncating() {
1540        let max = [OwnedMySQLValue::Bytes(
1541            b"79228162514264337593543950335".to_vec(),
1542        )];
1543        assert_eq!(
1544            decode::<rust_decimal::Decimal>(&max).unwrap(),
1545            rust_decimal::Decimal::MAX
1546        );
1547        let overflow = [OwnedMySQLValue::Bytes(
1548            b"79228162514264337593543950336".to_vec(),
1549        )];
1550        assert!(decode::<rust_decimal::Decimal>(&overflow).is_err());
1551    }
1552
1553    #[cfg(feature = "mysql-common")]
1554    #[test]
1555    fn mysql_bit_bytes_decode_as_big_endian_unsigned_values() {
1556        assert_eq!(mysql_bit_to_u64(&[]).unwrap(), 0);
1557        assert_eq!(mysql_bit_to_u64(&[0xff]).unwrap(), 255);
1558        assert_eq!(mysql_bit_to_u64(&[0x01, 0x00]).unwrap(), 256);
1559        assert_eq!(mysql_bit_to_u64(&[0xff; 8]).unwrap(), u64::MAX);
1560        assert!(mysql_bit_to_u64(&[0; 9]).is_err());
1561    }
1562
1563    #[test]
1564    fn bit_metadata_preserves_binary_targets_and_enables_numeric_targets() {
1565        struct PackedBitRow(OwnedMySQLValue);
1566
1567        impl MySQLRowAccess for PackedBitRow {
1568            fn value_at(&self, offset: usize) -> Result<Option<MySQLValue<'_>>, DrizzleError> {
1569                Ok((offset == 0).then(|| MySQLValue::from(&self.0)))
1570            }
1571
1572            fn is_bit_at(&self, offset: usize) -> bool {
1573                offset == 0
1574            }
1575        }
1576
1577        let row = PackedBitRow(OwnedMySQLValue::Bytes(vec![0xa5]));
1578        let row = MySQLRow::new(&row);
1579        assert_eq!(u64::from_row(&row).unwrap(), 0xa5);
1580        assert!(bool::from_row(&row).unwrap());
1581        assert_eq!(Vec::<u8>::from_row(&row).unwrap(), [0xa5]);
1582    }
1583}