icydb_core/db/store/
data.rs1use 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#[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 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 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#[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}