Skip to main content

zero_mysql/
ref_row.rs

1//! Zero-copy row decoding for fixed-size types.
2//!
3//! This module provides traits and types for zero-copy decoding of database rows
4//! where all fields have fixed wire sizes. This is useful for high-performance
5//! scenarios where avoiding allocations is critical.
6//!
7//! # Requirements
8//!
9//! - All struct fields must implement `FixedWireSize`
10//! - All columns must be `NOT NULL` (no `Option<T>` support)
11//! - Struct must use `#[repr(C, packed)]` for predictable layout
12//! - Fields must use endian-aware types (e.g., `I64LE` instead of `i64`)
13//!
14//! # Example
15//!
16//! ```ignore
17//! use zerocopy::little_endian::{I64 as I64LE, I32 as I32LE};
18//! use zero_mysql::ref_row::RefFromRow;
19//!
20//! #[derive(RefFromRow)]
21//! #[repr(C, packed)]
22//! struct UserStats {
23//!     user_id: I64LE,
24//!     login_count: I32LE,
25//! }
26//! ```
27
28use crate::error::Result;
29use crate::protocol::BinaryRowPayload;
30use crate::protocol::command::ColumnDefinition;
31
32/// Marker trait for types with a fixed wire size in MySQL binary protocol.
33///
34/// This trait is only implemented for types that have a guaranteed fixed size
35/// on the wire. Native integer types like `i64` are NOT implemented because
36/// MySQL uses little-endian encoding, which differs from native byte order on
37/// big-endian platforms.
38///
39/// Use zerocopy's little-endian types instead:
40/// - `zerocopy::little_endian::I16` instead of `i16`
41/// - `zerocopy::little_endian::I32` instead of `i32`
42/// - `zerocopy::little_endian::I64` instead of `i64`
43/// - etc.
44pub trait FixedWireSize {
45    /// The fixed size in bytes on the wire.
46    const WIRE_SIZE: usize;
47}
48
49// Single-byte types are endian-agnostic
50impl FixedWireSize for i8 {
51    const WIRE_SIZE: usize = 1;
52}
53impl FixedWireSize for u8 {
54    const WIRE_SIZE: usize = 1;
55}
56
57// Little-endian integer types (MySQL wire format)
58impl FixedWireSize for zerocopy::little_endian::I16 {
59    const WIRE_SIZE: usize = 2;
60}
61impl FixedWireSize for zerocopy::little_endian::U16 {
62    const WIRE_SIZE: usize = 2;
63}
64impl FixedWireSize for zerocopy::little_endian::I32 {
65    const WIRE_SIZE: usize = 4;
66}
67impl FixedWireSize for zerocopy::little_endian::U32 {
68    const WIRE_SIZE: usize = 4;
69}
70impl FixedWireSize for zerocopy::little_endian::I64 {
71    const WIRE_SIZE: usize = 8;
72}
73impl FixedWireSize for zerocopy::little_endian::U64 {
74    const WIRE_SIZE: usize = 8;
75}
76
77// Re-export little-endian types for convenience
78pub use zerocopy::little_endian::{
79    I16 as I16LE, I32 as I32LE, I64 as I64LE, U16 as U16LE, U32 as U32LE, U64 as U64LE,
80};
81
82/// Trait for zero-copy decoding of a row into a fixed-size struct.
83///
84/// Unlike `FromRow`, this trait returns a reference directly into the buffer
85/// without any copying or allocation. This requires:
86///
87/// 1. All fields have fixed wire sizes (implement `FixedWireSize`)
88/// 2. No NULL values (columns must be `NOT NULL`)
89/// 3. Struct has `#[repr(C, packed)]` layout
90///
91/// The derive macro generates zerocopy trait implementations automatically.
92///
93/// # Compile-fail tests
94///
95/// Missing `#[repr(C, packed)]`:
96/// ```compile_fail
97/// use zero_mysql::ref_row::I32LE;
98/// use zero_mysql_derive::RefFromRow;
99///
100/// #[derive(RefFromRow)]
101/// struct Invalid {
102///     value: I32LE,
103/// }
104/// ```
105///
106/// Native integer types (must use little-endian wrappers):
107/// ```compile_fail
108/// use zero_mysql_derive::RefFromRow;
109///
110/// #[derive(RefFromRow)]
111/// #[repr(C, packed)]
112/// struct Invalid {
113///     value: i64,
114/// }
115/// ```
116///
117/// `String` fields are not allowed:
118/// ```compile_fail
119/// use zero_mysql_derive::RefFromRow;
120///
121/// #[derive(RefFromRow)]
122/// #[repr(C, packed)]
123/// struct Invalid {
124///     name: String,
125/// }
126/// ```
127///
128/// `Vec` fields are not allowed:
129/// ```compile_fail
130/// use zero_mysql_derive::RefFromRow;
131///
132/// #[derive(RefFromRow)]
133/// #[repr(C, packed)]
134/// struct Invalid {
135///     data: Vec<u8>,
136/// }
137/// ```
138pub trait RefFromRow<'buf>: Sized {
139    /// Decode a row as a zero-copy reference.
140    ///
141    /// # Errors
142    ///
143    /// Returns an error if:
144    /// - The row data size doesn't match the struct size
145    /// - Any column is NULL (RefFromRow doesn't support NULL)
146    fn ref_from_row(
147        cols: &[ColumnDefinition<'_>],
148        row: BinaryRowPayload<'buf>,
149    ) -> Result<&'buf Self>;
150}