sqlx_mysql/protocol/statement/
row.rs

1use bytes::{Buf, Bytes};
2
3use crate::error::Error;
4use crate::io::MySqlBufExt;
5use crate::io::{BufExt, ProtocolDecode};
6use crate::protocol::text::ColumnType;
7use crate::protocol::Row;
8use crate::MySqlColumn;
9
10// https://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html#packet-ProtocolBinary::ResultsetRow
11// https://dev.mysql.com/doc/internals/en/binary-protocol-value.html
12
13#[derive(Debug)]
14pub(crate) struct BinaryRow(pub(crate) Row);
15
16impl<'de> ProtocolDecode<'de, &'de [MySqlColumn]> for BinaryRow {
17    fn decode_with(mut buf: Bytes, columns: &'de [MySqlColumn]) -> Result<Self, Error> {
18        let header = buf.get_u8();
19        if header != 0 {
20            return Err(err_protocol!(
21                "exepcted 0x00 (ROW) but found 0x{:02x}",
22                header
23            ));
24        }
25
26        let storage = buf.clone();
27        let offset = buf.len();
28
29        let null_bitmap_len = (columns.len() + 9) / 8;
30        let null_bitmap = buf.get_bytes(null_bitmap_len);
31
32        let mut values = Vec::with_capacity(columns.len());
33
34        for (column_idx, column) in columns.iter().enumerate() {
35            // NOTE: the column index starts at the 3rd bit
36            let column_null_idx = column_idx + 2;
37
38            let byte_idx = column_null_idx / 8;
39            let bit_idx = column_null_idx % 8;
40
41            let is_null = null_bitmap[byte_idx] & (1u8 << bit_idx) != 0;
42
43            if is_null {
44                values.push(None);
45                continue;
46            }
47
48            // NOTE: MySQL will never generate NULL types for non-NULL values
49            let type_info = &column.type_info;
50
51            // Unlike Postgres, MySQL does not length-prefix every value in a binary row.
52            // Values are *either* fixed-length or length-prefixed,
53            // so we need to inspect the type code to be sure.
54            let size: usize = match type_info.r#type {
55                // All fixed-length types.
56                ColumnType::LongLong => 8,
57                ColumnType::Long | ColumnType::Int24 => 4,
58                ColumnType::Short | ColumnType::Year => 2,
59                ColumnType::Tiny => 1,
60                ColumnType::Float => 4,
61                ColumnType::Double => 8,
62
63                // Blobs and strings are prefixed with their length,
64                // which is itself a length-encoded integer.
65                ColumnType::String
66                | ColumnType::VarChar
67                | ColumnType::VarString
68                | ColumnType::Enum
69                | ColumnType::Set
70                | ColumnType::LongBlob
71                | ColumnType::MediumBlob
72                | ColumnType::Blob
73                | ColumnType::TinyBlob
74                | ColumnType::Geometry
75                | ColumnType::Bit
76                | ColumnType::Decimal
77                | ColumnType::Json
78                | ColumnType::NewDecimal => {
79                    let size = buf.get_uint_lenenc();
80                    usize::try_from(size)
81                        .map_err(|_| err_protocol!("BLOB length out of range: {size}"))?
82                }
83
84                // Like strings and blobs, these values are variable-length.
85                // Unlike strings and blobs, however, they exclusively use one byte for length.
86                ColumnType::Time
87                | ColumnType::Timestamp
88                | ColumnType::Date
89                | ColumnType::Datetime => {
90                    // Leave the length byte on the front of the value because decoding uses it.
91                    buf[0] as usize + 1
92                }
93
94                // NOTE: MySQL will never generate NULL types for non-NULL values
95                ColumnType::Null => unreachable!(),
96            };
97
98            let offset = offset - buf.len();
99
100            values.push(Some(offset..(offset + size)));
101
102            buf.advance(size);
103        }
104
105        Ok(BinaryRow(Row { values, storage }))
106    }
107}