icydb_core/db/store/
data.rs

1use crate::{Key, db::store::StoreRegistry, traits::EntityKind};
2use candid::CandidType;
3use canic_cdk::structures::{BTreeMap, DefaultMemoryImpl, memory::VirtualMemory};
4use canic_memory::impl_storable_bounded;
5use derive_more::{Deref, DerefMut};
6use serde::{Deserialize, Serialize};
7use std::fmt::{self, Display};
8
9///
10/// DataStoreRegistry
11///
12
13#[derive(Deref, DerefMut)]
14pub struct DataStoreRegistry(StoreRegistry<DataStore>);
15
16impl DataStoreRegistry {
17    #[must_use]
18    #[allow(clippy::new_without_default)]
19    /// Create an empty data store registry.
20    pub fn new() -> Self {
21        Self(StoreRegistry::new())
22    }
23}
24
25///
26/// DataStore
27///
28
29#[derive(Deref, DerefMut)]
30pub struct DataStore(BTreeMap<DataKey, Vec<u8>, VirtualMemory<DefaultMemoryImpl>>);
31
32impl DataStore {
33    #[must_use]
34    /// Initialize a data store with the provided backing memory.
35    pub fn init(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
36        Self(BTreeMap::init(memory))
37    }
38
39    /// Sum of bytes used by all stored rows.
40    pub fn memory_bytes(&self) -> u64 {
41        self.iter()
42            .map(|entry| u64::from(DataKey::STORABLE_MAX_SIZE) + entry.value().len() as u64)
43            .sum()
44    }
45}
46
47///
48/// DataRow
49///
50
51pub type DataRow = (DataKey, Vec<u8>);
52
53///
54/// DataKey
55///
56
57#[derive(
58    CandidType, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize,
59)]
60pub struct DataKey {
61    entity_id: u64,
62    key: Key,
63}
64
65impl DataKey {
66    pub const STORABLE_MAX_SIZE: u32 = 160;
67
68    #[must_use]
69    /// Build a data key for the given entity type and primary key.
70    pub fn new<E: EntityKind>(key: impl Into<Key>) -> Self {
71        Self {
72            entity_id: E::ENTITY_ID,
73            key: key.into(),
74        }
75    }
76
77    #[must_use]
78    pub const fn lower_bound<E: EntityKind>() -> Self {
79        Self {
80            entity_id: E::ENTITY_ID,
81            key: Key::lower_bound(),
82        }
83    }
84
85    #[must_use]
86    pub const fn upper_bound<E: EntityKind>() -> Self {
87        Self {
88            entity_id: E::ENTITY_ID,
89            key: Key::upper_bound(),
90        }
91    }
92
93    /// Return the primary key component of this data key.
94    #[must_use]
95    pub const fn key(&self) -> Key {
96        self.key
97    }
98
99    /// Entity identifier (stable, compile-time constant per entity type).
100    #[must_use]
101    pub const fn entity_id(&self) -> u64 {
102        self.entity_id
103    }
104
105    /// Compute the on-disk size used by a single data entry from its value length.
106    /// Includes the bounded `DataKey` size and the value bytes.
107    #[must_use]
108    pub const fn entry_size_bytes(value_len: u64) -> u64 {
109        Self::STORABLE_MAX_SIZE as u64 + value_len
110    }
111
112    #[must_use]
113    /// Max sentinel key for sizing.
114    pub fn max_storable() -> Self {
115        Self {
116            entity_id: u64::MAX,
117            key: Key::max_storable(),
118        }
119    }
120}
121
122impl Display for DataKey {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        write!(f, "#{} ({})", self.entity_id, self.key)
125    }
126}
127
128impl From<DataKey> for Key {
129    fn from(key: DataKey) -> Self {
130        key.key()
131    }
132}
133
134impl_storable_bounded!(DataKey, Self::STORABLE_MAX_SIZE, false);
135
136///
137/// TESTS
138///
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use crate::traits::Storable;
144
145    #[test]
146    fn data_key_max_size_is_bounded() {
147        let data_key = DataKey::max_storable();
148        let size = Storable::to_bytes(&data_key).len();
149
150        assert!(
151            size <= DataKey::STORABLE_MAX_SIZE as usize,
152            "serialized DataKey too large: got {size} bytes (limit {})",
153            DataKey::STORABLE_MAX_SIZE
154        );
155    }
156}