icydb_core/db/store/
data.rs1use 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#[derive(Deref, DerefMut)]
18pub struct DataStoreRegistry(StoreRegistry<DataStore>);
19
20impl DataStoreRegistry {
21 #[must_use]
22 #[allow(clippy::new_without_default)]
23 pub fn new() -> Self {
25 Self(StoreRegistry::new())
26 }
27}
28
29#[derive(Deref, DerefMut)]
34pub struct DataStore(BTreeMap<DataKey, Vec<u8>, VirtualMemory<DefaultMemoryImpl>>);
35
36impl DataStore {
37 #[must_use]
38 pub fn init(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
40 Self(BTreeMap::init(memory))
41 }
42
43 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
51pub type DataRow = (DataKey, Vec<u8>);
56
57#[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 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 #[must_use]
98 pub const fn key(&self) -> Key {
99 self.key
100 }
101
102 #[must_use]
104 pub const fn entity_name(&self) -> &EntityName {
105 &self.entity
106 }
107
108 #[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 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
137impl Storable for DataKey {
142 fn to_bytes(&self) -> Cow<'_, [u8]> {
143 let mut buf = Vec::with_capacity(Self::STORED_SIZE as usize);
144
145 buf.push(self.entity.len);
147 buf.extend_from_slice(&self.entity.bytes);
148
149 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 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 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#[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}