icydb_core/db/store/
data.rs

1use crate::{
2    db::store::{EntityName, MAX_ENTITY_NAME_LEN, 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 len = bytes[offset];
179        assert!(
180            len > 0 && len as usize <= MAX_ENTITY_NAME_LEN,
181            "corrupted DataKey: invalid entity name length"
182        );
183        offset += 1;
184
185        let mut entity_bytes = [0u8; MAX_ENTITY_NAME_LEN];
186        entity_bytes.copy_from_slice(&bytes[offset..offset + MAX_ENTITY_NAME_LEN]);
187        offset += MAX_ENTITY_NAME_LEN;
188
189        let entity = EntityName {
190            len,
191            bytes: entity_bytes,
192        };
193
194        // ── Key ────────────────────────────────────
195        let key = Key::from_bytes(bytes[offset..offset + Key::STORED_SIZE].into());
196
197        Self { entity, key }
198    }
199
200    fn into_bytes(self) -> Vec<u8> {
201        self.to_bytes().into_owned()
202    }
203
204    const BOUND: Bound = Bound::Bounded {
205        max_size: Self::STORED_SIZE,
206        is_fixed_size: true,
207    };
208}
209
210///
211/// TESTS
212///
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217    use crate::traits::Storable;
218
219    #[test]
220    fn data_key_is_exactly_fixed_size() {
221        let data_key = DataKey::max_storable();
222        let size = data_key.to_bytes().len();
223
224        assert_eq!(
225            size,
226            DataKey::STORED_SIZE as usize,
227            "DataKey must serialize to exactly STORED_SIZE bytes"
228        );
229    }
230
231    #[test]
232    fn data_key_ordering_matches_bytes() {
233        let keys = vec![
234            DataKey {
235                entity: EntityName::from_static("a"),
236                key: Key::Int(0),
237            },
238            DataKey {
239                entity: EntityName::from_static("aa"),
240                key: Key::Int(0),
241            },
242            DataKey {
243                entity: EntityName::from_static("b"),
244                key: Key::Int(0),
245            },
246            DataKey {
247                entity: EntityName::from_static("a"),
248                key: Key::Uint(1),
249            },
250        ];
251
252        let mut sorted_by_ord = keys.clone();
253        sorted_by_ord.sort();
254
255        let mut sorted_by_bytes = keys;
256        sorted_by_bytes.sort_by(|a, b| a.to_bytes().cmp(&b.to_bytes()));
257
258        assert_eq!(
259            sorted_by_ord, sorted_by_bytes,
260            "DataKey Ord and byte ordering diverged"
261        );
262    }
263
264    #[test]
265    #[should_panic(expected = "corrupted DataKey: invalid size")]
266    fn data_key_rejects_undersized_bytes() {
267        let buf = vec![0u8; DataKey::STORED_SIZE as usize - 1];
268        let _ = DataKey::from_bytes(buf.into());
269    }
270
271    #[test]
272    #[should_panic(expected = "corrupted DataKey: invalid size")]
273    fn data_key_rejects_oversized_bytes() {
274        let buf = vec![0u8; DataKey::STORED_SIZE as usize + 1];
275        let _ = DataKey::from_bytes(buf.into());
276    }
277
278    #[test]
279    #[should_panic(expected = "corrupted DataKey: invalid entity name length")]
280    fn data_key_rejects_invalid_entity_len() {
281        let mut buf = DataKey::max_storable().to_bytes().into_owned();
282        buf[0] = 0;
283        let _ = DataKey::from_bytes(buf.into());
284    }
285}