signet_libmdbx/codec.rs
1//! Codec for deserializing database values into Rust types.
2
3use crate::{MdbxError, TransactionKind, error::ReadResult, tx::ops};
4use ffi::MDBX_txn;
5use std::{borrow::Cow, slice};
6
7/// A marker trait for types that can be deserialized from a database value
8/// without borrowing from the transaction.
9///
10/// Types implementing this trait can be used with iterators that need to
11/// return owned values. This is automatically implemented for any type that
12/// implements [`TableObject<'a>`] for all lifetimes `'a`.
13///
14/// # Built-in Implementations
15///
16/// - [`Vec<u8>`] - Always copies data
17/// - `[u8; N]` - Copies into fixed-size array - may pan
18/// - `()` - Ignores data entirely
19/// - [`ObjectLength`] - Returns only the length
20///
21pub trait TableObjectOwned: for<'de> TableObject<'de> {
22 /// Decodes the object from the given bytes, without borrowing them.
23 ///
24 /// [`ReadError`]: crate::ReadError
25 fn decode(data_val: &[u8]) -> ReadResult<Self> {
26 <Self as TableObject<'_>>::decode_borrow(Cow::Borrowed(data_val))
27 }
28}
29
30impl<T> TableObjectOwned for T where T: for<'de> TableObject<'de> {}
31
32/// Decodes values read from the database into Rust types.
33///
34/// Implement this trait to enable reading custom types directly from MDBX.
35/// The lifetime parameter `'a` allows types to borrow data from the
36/// transaction when appropriate (e.g., `Cow<'a, [u8]>`).
37///
38/// # Implementation Guide
39///
40/// For most types, only implement [`decode_borrow`](Self::decode_borrow). An
41/// internal function `decode_val` is provided to handle zero-copy borrowing.
42/// Implementing it is STRONGLY DISCOURAGED. Zero-copy borrowing can be
43/// achieved by implementing `decode_borrow`.
44///
45/// ## Zero-copy Deserialization
46///
47/// MDBX supports zero-copy deserialization for types that can borrow data
48/// directly from the database (like `Cow<'a, [u8]>`). Read-only transactions
49/// ALWAYS support borrowing, while read-write transactions require a check
50/// to see if the data is "dirty" (modified but not yet committed). If the page
51/// containing the data is dirty, a copy must be made before borrowing.
52///
53/// [`TableObject::decode_borrow`] is the main method to implement. It receives
54/// a `Cow<'a, [u8]>` which may be either borrowed or owned data, depending on
55/// the transaction type and data state. Implementations may choose to split
56/// the data further, copy it, or use it as-is.
57///
58/// ```
59/// # use std::borrow::Cow;
60/// use signet_libmdbx::{TableObject, ReadResult, MdbxError};
61///
62/// // A zero-copy wrapper around a u32 stored in little-endian format.
63/// struct MyZeroCopyU32<'a> (Cow<'a, [u8]>);
64///
65/// impl<'a> TableObject<'a> for MyZeroCopyU32<'a> {
66/// fn decode_borrow(data: Cow<'a, [u8]>) -> ReadResult<Self> {
67/// if data.len() < 4 {
68/// return Err(MdbxError::DecodeErrorLenDiff.into());
69/// }
70/// Ok(MyZeroCopyU32(data))
71/// }
72/// }
73///
74/// impl MyZeroCopyU32<'_> {
75/// /// Reads a u32 from the start of the data.
76/// pub fn read_u32(&self) -> u32 {
77/// let bytes = &self.0[..4];
78/// u32::from_le_bytes(bytes.try_into().unwrap())
79/// }
80/// }
81/// ```
82///
83/// ## Fixed-Size Types
84///
85/// ```
86/// # use std::borrow::Cow;
87/// # use signet_libmdbx::{TableObject, ReadResult, MdbxError};
88/// struct Hash([u8; 32]);
89///
90/// impl TableObject<'_> for Hash {
91/// fn decode_borrow(data: Cow<'_, [u8]>) -> ReadResult<Self> {
92/// let arr: [u8; 32] = data.as_ref().try_into()
93/// .map_err(|_| MdbxError::DecodeErrorLenDiff)?;
94/// Ok(Self(arr))
95/// }
96/// }
97/// ```
98///
99/// ## Variable-Size Types
100///
101/// ```
102/// # use std::borrow::Cow;
103/// # use signet_libmdbx::{TableObject, ReadResult, MdbxError};
104/// struct VarInt(u64);
105///
106/// impl TableObject<'_> for VarInt {
107/// fn decode_borrow(data: Cow<'_, [u8]>) -> ReadResult<Self> {
108/// // Example: decode LEB128 or similar
109/// let value = data.iter()
110/// .take(8)
111/// .enumerate()
112/// .fold(0u64, |acc, (i, &b)| acc | ((b as u64) << (i * 8)));
113/// Ok(Self(value))
114/// }
115/// }
116/// ```
117pub trait TableObject<'a>: Sized {
118 /// Creates the object from a `Cow` of bytes. This allows for efficient
119 /// handling of both owned and borrowed data.
120 fn decode_borrow(data: Cow<'a, [u8]>) -> ReadResult<Self>;
121
122 /// Decodes the value directly from the given MDBX_val pointer.
123 ///
124 /// **Do not implement this unless you need zero-copy borrowing and cannot
125 /// handle the [`Cow`] overhead**.
126 ///
127 /// This method is used internally to optimize deserialization for types
128 /// that borrow data directly from the database (like `Cow<'a, [u8]>`).
129 ///
130 /// # Safety
131 ///
132 /// The data pointed to by `data_val` is only valid for the lifetime of
133 /// the transaction. In read-write transactions, the data may be "dirty"
134 /// (modified but not yet committed), requiring a copy via `mdbx_is_dirty`
135 /// before borrowing.
136 ///
137 /// The caller must ensure that `tx` is a valid pointer to the current
138 /// transaction AND that `data_val` points to valid MDBX data AND that
139 /// they have exclusive access to the transaction if it is read-write.
140 #[doc(hidden)]
141 #[inline(always)]
142 unsafe fn decode_val<K: TransactionKind>(
143 tx: *const MDBX_txn,
144 data_val: ffi::MDBX_val,
145 ) -> ReadResult<Self> {
146 let cow = unsafe { Cow::<'a, [u8]>::decode_val::<K>(tx, data_val)? };
147 Self::decode_borrow(cow)
148 }
149}
150
151impl<'a> TableObject<'a> for Cow<'a, [u8]> {
152 fn decode_borrow(data: Cow<'a, [u8]>) -> ReadResult<Self> {
153 Ok(data)
154 }
155
156 #[doc(hidden)]
157 unsafe fn decode_val<K: TransactionKind>(
158 txn: *const MDBX_txn,
159 data_val: ffi::MDBX_val,
160 ) -> ReadResult<Self> {
161 // SAFETY: Caller ensures the tx is active, slice is valid for lifetime
162 // 'a.
163 let s = unsafe { slice::from_raw_parts(data_val.iov_base as *const u8, data_val.iov_len) };
164
165 // SAFETY: txn is valid from caller, data_val.iov_base points to db pages.
166 let is_dirty = (!K::IS_READ_ONLY) && unsafe { ops::is_dirty_raw(txn, data_val.iov_base) }?;
167
168 Ok(if is_dirty { Cow::Owned(s.to_vec()) } else { Cow::Borrowed(s) })
169 }
170}
171
172impl TableObject<'_> for Vec<u8> {
173 fn decode_borrow(data: Cow<'_, [u8]>) -> ReadResult<Self> {
174 Ok(data.into_owned())
175 }
176
177 unsafe fn decode_val<K: TransactionKind>(
178 _tx: *const MDBX_txn,
179 data_val: ffi::MDBX_val,
180 ) -> ReadResult<Self> {
181 // SAFETY: Caller ensures the tx is active, slice is valid for lifetime.
182 // We always copy for Vec<u8> since we need to own the data.
183 let s = unsafe { slice::from_raw_parts(data_val.iov_base as *const u8, data_val.iov_len) };
184 Ok(s.to_vec())
185 }
186}
187
188impl<'a> TableObject<'a> for () {
189 fn decode_borrow(_: Cow<'a, [u8]>) -> ReadResult<Self> {
190 Ok(())
191 }
192
193 unsafe fn decode_val<K: TransactionKind>(
194 _: *const MDBX_txn,
195 _: ffi::MDBX_val,
196 ) -> ReadResult<Self> {
197 Ok(())
198 }
199}
200
201/// If you don't need the data itself, just its length.
202#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
203#[repr(transparent)]
204pub struct ObjectLength(pub usize);
205
206impl TableObject<'_> for ObjectLength {
207 fn decode_borrow(data: Cow<'_, [u8]>) -> ReadResult<Self> {
208 Ok(Self(data.len()))
209 }
210
211 unsafe fn decode_val<K: TransactionKind>(
212 _tx: *const MDBX_txn,
213 data_val: ffi::MDBX_val,
214 ) -> ReadResult<Self> {
215 Ok(Self(data_val.iov_len))
216 }
217}
218
219impl core::ops::Deref for ObjectLength {
220 type Target = usize;
221
222 fn deref(&self) -> &Self::Target {
223 &self.0
224 }
225}
226
227impl<'a, const LEN: usize> TableObject<'a> for [u8; LEN] {
228 fn decode_borrow(data: Cow<'a, [u8]>) -> ReadResult<Self> {
229 if data.len() != LEN {
230 return Err(MdbxError::DecodeErrorLenDiff.into());
231 }
232 let mut a = [0; LEN];
233 a[..].copy_from_slice(&data);
234 Ok(a)
235 }
236
237 unsafe fn decode_val<K: TransactionKind>(
238 _tx: *const MDBX_txn,
239 data_val: ffi::MDBX_val,
240 ) -> ReadResult<Self> {
241 // SAFETY: Caller ensures the tx is active, slice is valid.
242 if data_val.iov_len != LEN {
243 return Err(MdbxError::DecodeErrorLenDiff.into());
244 }
245 let s = unsafe { slice::from_raw_parts(data_val.iov_base as *const u8, data_val.iov_len) };
246 let mut a = [0; LEN];
247 a[..].copy_from_slice(s);
248 Ok(a)
249 }
250}