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}