Skip to main content

wasm_dbms_api/memory/
encode.rs

1use std::borrow::Cow;
2
3use crate::memory::{MSize, MemoryResult};
4use crate::prelude::PageOffset;
5
6/// Default alignment in bytes for [`DataSize::Dynamic`] data types.
7pub const DEFAULT_ALIGNMENT: MSize = 32;
8
9/// This trait defines the encoding and decoding behaviour for data types used in the DBMS.
10pub trait Encode: Clone {
11    /// The size characteristic of the data type.
12    ///
13    /// The [`DataSize`] can either be a fixed size in bytes or dynamic.
14    const SIZE: DataSize;
15
16    /// The alignment requirement in bytes for the data type.
17    ///
18    /// If [`Self::SIZE`] is [`DataSize::Fixed`], the alignment must be equal to the size,
19    /// otherwise it can be any value.
20    ///
21    /// This value  should never be less than 8 for [`DataSize::Dynamic`] types to ensure proper memory alignment.
22    ///
23    /// We should set a default value (probably 32) for dynamic types to avoid misalignment issues, but letting an expert user to
24    /// override it if necessary.
25    const ALIGNMENT: PageOffset;
26
27    /// Encodes the data type into a vector of bytes.
28    fn encode(&'_ self) -> Cow<'_, [u8]>;
29
30    /// Decodes the data type from a slice of bytes.
31    fn decode(data: Cow<[u8]>) -> MemoryResult<Self>
32    where
33        Self: Sized;
34
35    /// Returns the size in bytes of the encoded data type.
36    ///
37    /// Implementations must never panic or wrap: when the encoded size does
38    /// not fit an [`MSize`], they must return [`MSize::MAX`]. No record of
39    /// that size fits a page together with its header, so storage rejects it
40    /// with [`MemoryError::DataTooLarge`](crate::memory::MemoryError::DataTooLarge)
41    /// before calling [`Self::encode`]. The output of [`Self::encode`] is
42    /// unspecified for such values.
43    fn size(&self) -> MSize;
44}
45
46/// Returns the encoded size of a `payload_len`-byte payload preceded by a
47/// `header`-byte header, saturating at [`MSize::MAX`] when it does not fit.
48pub(crate) fn saturating_size(header: MSize, payload_len: usize) -> MSize {
49    MSize::try_from(payload_len).map_or(MSize::MAX, |len| header.saturating_add(len))
50}
51
52/// Encodes `len` as the 2-byte little-endian length prefix used by dynamic
53/// payloads, saturating at [`u16::MAX`] when it does not fit.
54///
55/// A saturated prefix only occurs for values whose [`Encode::size`] is
56/// [`MSize::MAX`], which storage rejects before encoding.
57pub(crate) fn length_prefix(len: usize) -> [u8; 2] {
58    u16::try_from(len).unwrap_or(u16::MAX).to_le_bytes()
59}
60
61/// Represents the size of data types used in the DBMS.
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum DataSize {
64    /// A fixed size in bytes.
65    Fixed(MSize),
66    /// A variable size.
67    Dynamic,
68}
69
70impl DataSize {
71    /// Returns the size in bytes if the data size is fixed.
72    pub fn get_fixed_size(&self) -> Option<MSize> {
73        match self {
74            DataSize::Fixed(size) => Some(*size),
75            DataSize::Dynamic => None,
76        }
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_saturating_size_is_exact_when_it_fits() {
86        assert_eq!(saturating_size(2, 0), 2);
87        assert_eq!(saturating_size(2, 65_533), MSize::MAX);
88        assert_eq!(saturating_size(0, 65_535), MSize::MAX);
89    }
90
91    #[test]
92    fn test_saturating_size_saturates_when_it_does_not_fit() {
93        assert_eq!(saturating_size(2, 65_534), MSize::MAX);
94        assert_eq!(saturating_size(0, 65_536), MSize::MAX);
95        assert_eq!(saturating_size(2, usize::MAX), MSize::MAX);
96    }
97
98    #[test]
99    fn test_length_prefix_saturates_at_u16_max() {
100        assert_eq!(length_prefix(0), [0, 0]);
101        assert_eq!(length_prefix(65_535), [0xFF, 0xFF]);
102        assert_eq!(length_prefix(65_536), [0xFF, 0xFF]);
103    }
104
105    #[test]
106    fn test_should_get_data_size_fixed() {
107        let size = DataSize::Fixed(10);
108        assert_eq!(size.get_fixed_size(), Some(10));
109
110        let variable_size = DataSize::Dynamic;
111        assert_eq!(variable_size.get_fixed_size(), None);
112    }
113}