sqlite_ll/
fixed_bytes.rs

1use core::fmt;
2use core::mem::MaybeUninit;
3use core::slice;
4
5/// A helper to read at most a fixed number of `N` bytes from a column. This
6/// allocates the storage for the bytes read on the stack.
7pub struct FixedBytes<const N: usize> {
8    /// Storage to read to.
9    data: [MaybeUninit<u8>; N],
10    /// Number of bytes initialized.
11    init: usize,
12}
13
14impl<const N: usize> FixedBytes<N> {
15    /// Construct a new empty `FixedBytes`.
16    pub(super) const fn new() -> Self {
17        Self {
18            // SAFETY: this is safe as per `MaybeUninit::uninit_array`, which isn't stable (yet).
19            data: unsafe { MaybeUninit::<[MaybeUninit<u8>; N]>::uninit().assume_init() },
20            init: 0,
21        }
22    }
23
24    /// Return a mutable pointer to the underlying bytes.
25    pub(super) fn as_mut_ptr(&mut self) -> *mut u8 {
26        self.data.as_mut_ptr().cast()
27    }
28
29    /// Set the number of initialized bytes.
30    ///
31    /// # Safety
32    ///
33    /// The caller must ensure that `len` does not exceed `N` and that at least
34    /// `len` bytes have been initialized.
35    pub(super) unsafe fn set_len(&mut self, len: usize) {
36        self.init = len;
37    }
38
39    /// Coerce into the underlying bytes if all of them have been initialized.
40    ///
41    /// # Examples
42    ///
43    /// ```
44    /// use sqlite_ll::{Connection, State, FixedBytes};
45    ///
46    /// let c = Connection::open_memory()?;
47    /// c.execute(r##"
48    /// CREATE TABLE users (id BLOB);
49    /// INSERT INTO users (id) VALUES (X'01020304'), (X'05060708');
50    /// "##)?;
51    ///
52    /// let mut stmt = c.prepare("SELECT id FROM users")?;
53    ///
54    /// while let State::Row = stmt.step()? {
55    ///     let bytes = stmt.read::<FixedBytes<4>>(0)?;
56    ///     assert!(matches!(bytes.into_bytes(), Some([1, 2, 3, 4] | [5, 6, 7, 8])));
57    /// }
58    /// # Ok::<_, sqlite_ll::Error>(())
59    /// ```
60    pub fn into_bytes(self) -> Option<[u8; N]> {
61        if self.init == N {
62            // SAFETY: All of the bytes in the sequence have been initialized
63            // and can be safety transmuted.
64            //
65            // Method of transmuting comes from the implementation of
66            // `MaybeUninit::array_assume_init` which is not yet stable.
67            unsafe { Some((&self.data as *const _ as *const [u8; N]).read()) }
68        } else {
69            None
70        }
71    }
72
73    /// Coerce into the slice of initialized memory which is present.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use sqlite_ll::{Connection, State, FixedBytes};
79    ///
80    /// let c = Connection::open_memory()?;
81    /// c.execute(r##"
82    /// CREATE TABLE users (id BLOB);
83    /// INSERT INTO users (id) VALUES (X'01020304'), (X'0506070809');
84    /// "##)?;
85    ///
86    /// let mut stmt = c.prepare("SELECT id FROM users")?;
87    ///
88    /// while let State::Row = stmt.step()? {
89    ///     let bytes = stmt.read::<FixedBytes<10>>(0)?;
90    ///     assert!(matches!(bytes.as_bytes(), &[1, 2, 3, 4] | &[5, 6, 7, 8, 9]));
91    /// }
92    /// # Ok::<_, sqlite_ll::Error>(())
93    /// ```
94    pub fn as_bytes(&self) -> &[u8] {
95        if self.init == 0 {
96            return &[];
97        }
98
99        // SAFETY: We've asserted that `initialized` accounts for the number of
100        // bytes that have been initialized.
101        unsafe { slice::from_raw_parts(self.data.as_ptr() as *const u8, self.init) }
102    }
103}
104
105impl<const N: usize> fmt::Debug for FixedBytes<N> {
106    #[inline]
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        self.as_bytes().fmt(f)
109    }
110}