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}