icydb_core/db/store/
data.rs

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