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::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::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}