icydb_core/db/store/
data.rs

1use crate::{
2    db::store::{EntityName, StoreRegistry},
3    prelude::*,
4    traits::Storable,
5};
6use canic_cdk::structures::{BTreeMap, DefaultMemoryImpl, memory::VirtualMemory, storable::Bound};
7use derive_more::{Deref, DerefMut};
8use std::{
9    borrow::Cow,
10    fmt::{self, Display},
11};
12
13///
14/// DataStoreRegistry
15///
16
17#[derive(Deref, DerefMut)]
18pub struct DataStoreRegistry(StoreRegistry<DataStore>);
19
20impl DataStoreRegistry {
21    #[must_use]
22    #[allow(clippy::new_without_default)]
23    /// Create an empty data store registry.
24    pub fn new() -> Self {
25        Self(StoreRegistry::new())
26    }
27}
28
29///
30/// DataStore
31///
32
33#[derive(Deref, DerefMut)]
34pub struct DataStore(BTreeMap<DataKey, Vec<u8>, VirtualMemory<DefaultMemoryImpl>>);
35
36impl DataStore {
37    #[must_use]
38    /// Initialize a data store with the provided backing memory.
39    pub fn init(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
40        Self(BTreeMap::init(memory))
41    }
42
43    /// Sum of bytes used by all stored rows.
44    pub fn memory_bytes(&self) -> u64 {
45        self.iter()
46            .map(|entry| u64::from(DataKey::STORED_SIZE) + entry.value().len() as u64)
47            .sum()
48    }
49}
50
51///
52/// DataRow
53///
54
55pub type DataRow = (DataKey, Vec<u8>);
56
57///
58/// DataKey
59///
60
61#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
62pub struct DataKey {
63    entity: EntityName,
64    key: Key,
65}
66
67impl DataKey {
68    #[allow(clippy::cast_possible_truncation)]
69    pub const STORED_SIZE: u32 = EntityName::STORED_SIZE + Key::STORED_SIZE as u32;
70
71    #[must_use]
72    /// Build a data key for the given entity type and primary key.
73    pub fn new<E: EntityKind>(key: impl Into<Key>) -> Self {
74        Self {
75            entity: EntityName::from_static(E::ENTITY_NAME),
76            key: key.into(),
77        }
78    }
79
80    #[must_use]
81    pub const fn lower_bound<E: EntityKind>() -> Self {
82        Self {
83            entity: EntityName::from_static(E::ENTITY_NAME),
84            key: Key::lower_bound(),
85        }
86    }
87
88    #[must_use]
89    pub const fn upper_bound<E: EntityKind>() -> Self {
90        Self {
91            entity: EntityName::from_static(E::ENTITY_NAME),
92            key: Key::upper_bound(),
93        }
94    }
95
96    /// Return the primary key component of this data key.
97    #[must_use]
98    pub const fn key(&self) -> Key {
99        self.key
100    }
101
102    /// Entity name (stable, compile-time constant per entity type).
103    #[must_use]
104    pub const fn entity_name(&self) -> &EntityName {
105        &self.entity
106    }
107
108    /// Compute the on-disk size used by a single data entry from its value length.
109    /// Includes the bounded `DataKey` size and the value bytes.
110    #[must_use]
111    pub const fn entry_size_bytes(value_len: u64) -> u64 {
112        Self::STORED_SIZE as u64 + value_len
113    }
114
115    #[must_use]
116    /// Max sentinel key for sizing.
117    pub fn max_storable() -> Self {
118        Self {
119            entity: EntityName::max_storable(),
120            key: Key::max_storable(),
121        }
122    }
123}
124
125impl Display for DataKey {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        write!(f, "#{} ({})", self.entity, self.key)
128    }
129}
130
131impl From<DataKey> for Key {
132    fn from(key: DataKey) -> Self {
133        key.key()
134    }
135}
136
137/// Binary layout (fixed-size):
138/// [u8 entity_len]
139/// [MAX_ENTITY_NAME_LEN bytes entity_name]
140/// [Key bytes...]
141impl Storable for DataKey {
142    fn to_bytes(&self) -> Cow<'_, [u8]> {
143        let mut buf = Vec::with_capacity(Self::STORED_SIZE as usize);
144
145        // ── EntityName (fixed-size) ─────────────────
146        buf.push(self.entity.len);
147        buf.extend_from_slice(&self.entity.bytes);
148
149        // ── Key (fixed-size) ───────────────────────
150        let key_bytes = self.key.to_bytes();
151        debug_assert_eq!(
152            key_bytes.len(),
153            Key::STORED_SIZE,
154            "Key serialization must be exactly fixed-size"
155        );
156        buf.extend_from_slice(&key_bytes);
157
158        debug_assert_eq!(
159            buf.len(),
160            Self::STORED_SIZE as usize,
161            "DataKey serialized size mismatch"
162        );
163
164        Cow::Owned(buf)
165    }
166
167    fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
168        let bytes = bytes.as_ref();
169        assert_eq!(
170            bytes.len(),
171            Self::STORED_SIZE as usize,
172            "corrupted DataKey: invalid size"
173        );
174
175        let mut offset = 0;
176
177        // ── EntityName ─────────────────────────────
178        let entity = EntityName::from_bytes(&bytes[offset..offset + EntityName::STORED_SIZE_USIZE])
179            .expect("corrupted DataKey: invalid EntityName bytes");
180        offset += EntityName::STORED_SIZE_USIZE;
181
182        // ── Key ────────────────────────────────────
183        let key = Key::from_bytes(bytes[offset..offset + Key::STORED_SIZE].into());
184
185        Self { entity, key }
186    }
187
188    fn into_bytes(self) -> Vec<u8> {
189        self.to_bytes().into_owned()
190    }
191
192    const BOUND: Bound = Bound::Bounded {
193        max_size: Self::STORED_SIZE,
194        is_fixed_size: true,
195    };
196}
197
198///
199/// TESTS
200///
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use crate::traits::Storable;
206
207    #[test]
208    fn data_key_is_exactly_fixed_size() {
209        let data_key = DataKey::max_storable();
210        let size = data_key.to_bytes().len();
211
212        assert_eq!(
213            size,
214            DataKey::STORED_SIZE as usize,
215            "DataKey must serialize to exactly STORED_SIZE bytes"
216        );
217    }
218
219    #[test]
220    fn data_key_ordering_matches_bytes() {
221        let keys = vec![
222            DataKey {
223                entity: EntityName::from_static("a"),
224                key: Key::Int(0),
225            },
226            DataKey {
227                entity: EntityName::from_static("aa"),
228                key: Key::Int(0),
229            },
230            DataKey {
231                entity: EntityName::from_static("b"),
232                key: Key::Int(0),
233            },
234            DataKey {
235                entity: EntityName::from_static("a"),
236                key: Key::Uint(1),
237            },
238        ];
239
240        let mut sorted_by_ord = keys.clone();
241        sorted_by_ord.sort();
242
243        let mut sorted_by_bytes = keys;
244        sorted_by_bytes.sort_by(|a, b| a.to_bytes().cmp(&b.to_bytes()));
245
246        assert_eq!(
247            sorted_by_ord, sorted_by_bytes,
248            "DataKey Ord and byte ordering diverged"
249        );
250    }
251
252    #[test]
253    #[should_panic(expected = "corrupted DataKey: invalid size")]
254    fn data_key_rejects_undersized_bytes() {
255        let buf = vec![0u8; DataKey::STORED_SIZE as usize - 1];
256        let _ = DataKey::from_bytes(buf.into());
257    }
258
259    #[test]
260    #[should_panic(expected = "corrupted DataKey: invalid size")]
261    fn data_key_rejects_oversized_bytes() {
262        let buf = vec![0u8; DataKey::STORED_SIZE as usize + 1];
263        let _ = DataKey::from_bytes(buf.into());
264    }
265
266    #[test]
267    #[should_panic(expected = "corrupted DataKey: invalid EntityName bytes")]
268    fn data_key_rejects_invalid_entity_len() {
269        let mut buf = DataKey::max_storable().to_bytes().into_owned();
270        buf[0] = 0;
271        let _ = DataKey::from_bytes(buf.into());
272    }
273
274    #[test]
275    #[should_panic(expected = "corrupted DataKey: invalid EntityName bytes")]
276    fn data_key_rejects_non_ascii_entity_bytes() {
277        let data_key = DataKey {
278            entity: EntityName::from_static("a"),
279            key: Key::Int(1),
280        };
281        let mut buf = data_key.to_bytes().into_owned();
282        buf[1] = 0xFF;
283        let _ = DataKey::from_bytes(buf.into());
284    }
285
286    #[test]
287    #[should_panic(expected = "corrupted DataKey: invalid EntityName bytes")]
288    fn data_key_rejects_entity_padding() {
289        let data_key = DataKey {
290            entity: EntityName::from_static("user"),
291            key: Key::Int(1),
292        };
293        let mut buf = data_key.to_bytes().into_owned();
294        let padding_offset = 1 + data_key.entity.len();
295        buf[padding_offset] = b'x';
296        let _ = DataKey::from_bytes(buf.into());
297    }
298}