Skip to main content

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}