1use crate::{
2 MAX_INDEX_FIELDS,
3 db::store::{DataKey, EntityName, IndexName, StoreRegistry},
4 prelude::*,
5 traits::Storable,
6};
7use candid::CandidType;
8use canic_cdk::structures::{BTreeMap, DefaultMemoryImpl, memory::VirtualMemory, storable::Bound};
9use canic_memory::impl_storable_unbounded;
10use derive_more::{Deref, DerefMut, Display};
11use serde::{Deserialize, Serialize};
12use std::{borrow::Cow, collections::HashSet};
13
14#[derive(Deref, DerefMut)]
19pub struct IndexStoreRegistry(StoreRegistry<IndexStore>);
20
21impl IndexStoreRegistry {
22 #[must_use]
23 pub fn new() -> Self {
24 Self(StoreRegistry::new())
25 }
26}
27
28impl Default for IndexStoreRegistry {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub enum IndexInsertOutcome {
40 Inserted,
41 Skipped,
42}
43
44#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49pub enum IndexRemoveOutcome {
50 Removed,
51 Skipped,
52}
53
54#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub enum IndexInsertError {
60 UniqueViolation,
61}
62
63#[derive(Deref, DerefMut)]
68pub struct IndexStore(BTreeMap<IndexKey, IndexEntry, VirtualMemory<DefaultMemoryImpl>>);
69
70impl IndexStore {
71 #[must_use]
72 pub fn init(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
73 Self(BTreeMap::init(memory))
74 }
75
76 pub fn insert_index_entry<E: EntityKind>(
77 &mut self,
78 entity: &E,
79 index: &IndexModel,
80 ) -> Result<IndexInsertOutcome, IndexInsertError> {
81 let Some(index_key) = IndexKey::new(entity, index) else {
82 return Ok(IndexInsertOutcome::Skipped);
83 };
84 let key = entity.key();
85
86 if let Some(mut entry) = self.get(&index_key) {
87 if index.unique {
88 if entry.contains(&key) {
89 return Ok(IndexInsertOutcome::Skipped);
90 }
91 if !entry.is_empty() {
92 return Err(IndexInsertError::UniqueViolation);
93 }
94 self.insert(index_key, IndexEntry::new(key));
95 } else {
96 entry.insert_key(key);
97 self.insert(index_key, entry);
98 }
99 } else {
100 self.insert(index_key, IndexEntry::new(key));
101 }
102
103 Ok(IndexInsertOutcome::Inserted)
104 }
105
106 pub fn remove_index_entry<E: EntityKind>(
107 &mut self,
108 entity: &E,
109 index: &IndexModel,
110 ) -> IndexRemoveOutcome {
111 let Some(index_key) = IndexKey::new(entity, index) else {
112 return IndexRemoveOutcome::Skipped;
113 };
114
115 if let Some(mut entry) = self.get(&index_key) {
116 entry.remove_key(&entity.key());
117 if entry.is_empty() {
118 self.remove(&index_key);
119 } else {
120 self.insert(index_key, entry);
121 }
122 return IndexRemoveOutcome::Removed;
123 }
124
125 IndexRemoveOutcome::Skipped
126 }
127
128 pub fn resolve_data_values<E: EntityKind>(
129 &self,
130 index: &IndexModel,
131 prefix: &[Value],
132 ) -> Vec<DataKey> {
133 let mut out = Vec::new();
134
135 for (_, entry) in self.iter_with_prefix::<E>(index, prefix) {
136 out.extend(entry.keys.iter().map(|&k| DataKey::new::<E>(k)));
137 }
138
139 out
140 }
141
142 fn iter_with_prefix<E: EntityKind>(
143 &self,
144 index: &IndexModel,
145 prefix: &[Value],
146 ) -> Box<dyn Iterator<Item = (IndexKey, IndexEntry)> + '_> {
147 let index_id = IndexId::new::<E>(index);
148
149 let Some(fps) = prefix
150 .iter()
151 .map(Value::to_index_fingerprint)
152 .collect::<Option<Vec<_>>>()
153 else {
154 let empty = IndexKey::empty(index_id);
155 return Box::new(
156 self.range(empty.clone()..empty)
157 .map(|e| (e.key().clone(), e.value())),
158 );
159 };
160
161 let (start, end) = IndexKey::bounds_for_prefix(index_id, index.fields.len(), &fps);
162
163 Box::new(
164 self.range(start..=end)
165 .map(|e| (e.key().clone(), e.value())),
166 )
167 }
168
169 pub fn memory_bytes(&self) -> u64 {
171 self.iter()
172 .map(|entry| u64::from(IndexKey::STORED_SIZE) + entry.value().to_bytes().len() as u64)
173 .sum()
174 }
175}
176
177#[derive(Clone, Copy, Debug, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
182pub struct IndexId(pub IndexName);
183
184impl IndexId {
185 #[must_use]
186 pub fn new<E: EntityKind>(index: &IndexModel) -> Self {
187 let entity = EntityName::from_static(E::ENTITY_NAME);
188 Self(IndexName::from_parts(&entity, index.fields))
189 }
190
191 #[must_use]
192 pub const fn max_storable() -> Self {
193 Self(IndexName::max_storable())
194 }
195}
196
197#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
203pub struct IndexKey {
204 index_id: IndexId,
205 len: u8,
206 values: [[u8; 16]; MAX_INDEX_FIELDS],
207}
208
209impl IndexKey {
210 #[allow(clippy::cast_possible_truncation)]
211 pub const STORED_SIZE: u32 = IndexName::STORED_SIZE + 1 + (MAX_INDEX_FIELDS as u32 * 16);
212
213 pub fn new<E: EntityKind>(entity: &E, index: &IndexModel) -> Option<Self> {
214 if index.fields.len() > MAX_INDEX_FIELDS {
215 return None;
216 }
217
218 let mut values = [[0u8; 16]; MAX_INDEX_FIELDS];
219 let mut len = 0usize;
220
221 for field in index.fields {
222 let value = entity.get_value(field)?;
223 let fp = value.to_index_fingerprint()?;
224 values[len] = fp;
225 len += 1;
226 }
227
228 #[allow(clippy::cast_possible_truncation)]
229 Some(Self {
230 index_id: IndexId::new::<E>(index),
231 len: len as u8,
232 values,
233 })
234 }
235
236 #[must_use]
237 pub const fn empty(index_id: IndexId) -> Self {
238 Self {
239 index_id,
240 len: 0,
241 values: [[0u8; 16]; MAX_INDEX_FIELDS],
242 }
243 }
244
245 #[must_use]
246 #[allow(clippy::cast_possible_truncation)]
247 pub fn bounds_for_prefix(
248 index_id: IndexId,
249 index_len: usize,
250 prefix: &[[u8; 16]],
251 ) -> (Self, Self) {
252 let mut start = Self::empty(index_id);
253 let mut end = Self::empty(index_id);
254
255 for (i, fp) in prefix.iter().enumerate() {
256 start.values[i] = *fp;
257 end.values[i] = *fp;
258 }
259
260 start.len = index_len as u8;
261 end.len = start.len;
262
263 for value in end.values.iter_mut().take(index_len).skip(prefix.len()) {
264 *value = [0xFF; 16];
265 }
266
267 (start, end)
268 }
269}
270
271impl Storable for IndexKey {
272 fn to_bytes(&self) -> Cow<'_, [u8]> {
273 let mut buf = Vec::with_capacity(Self::STORED_SIZE as usize);
274
275 buf.extend_from_slice(&self.index_id.0.to_bytes());
277
278 buf.push(self.len);
280
281 for value in &self.values {
283 buf.extend_from_slice(value);
284 }
285
286 debug_assert_eq!(buf.len(), Self::STORED_SIZE as usize);
287 buf.into()
288 }
289
290 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
291 let bytes = bytes.as_ref();
292 assert_eq!(
293 bytes.len(),
294 Self::STORED_SIZE as usize,
295 "corrupted IndexKey: invalid size"
296 );
297 let mut offset = 0;
298
299 let index_name =
300 IndexName::from_bytes(&bytes[offset..offset + IndexName::STORED_SIZE as usize])
301 .expect("corrupted IndexKey: invalid IndexName");
302 offset += IndexName::STORED_SIZE as usize;
303
304 let len = bytes[offset];
305 offset += 1;
306
307 assert!(
308 len as usize <= MAX_INDEX_FIELDS,
309 "corrupted IndexKey: len exceeds MAX_INDEX_FIELDS"
310 );
311
312 let mut values = [[0u8; 16]; MAX_INDEX_FIELDS];
313 for value in &mut values {
314 value.copy_from_slice(&bytes[offset..offset + 16]);
315 offset += 16;
316 }
317
318 Self {
319 index_id: IndexId(index_name),
320 len,
321 values,
322 }
323 }
324
325 fn into_bytes(self) -> Vec<u8> {
326 self.to_bytes().into_owned()
327 }
328
329 const BOUND: Bound = Bound::Bounded {
330 max_size: Self::STORED_SIZE,
331 is_fixed_size: true,
332 };
333}
334
335#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
340pub struct IndexEntry {
341 keys: HashSet<Key>,
342}
343
344impl IndexEntry {
345 #[must_use]
346 pub fn new(key: Key) -> Self {
347 let mut keys = HashSet::with_capacity(1);
348 keys.insert(key);
349
350 Self { keys }
351 }
352
353 pub fn insert_key(&mut self, key: Key) {
354 self.keys.insert(key);
355 }
356
357 pub fn remove_key(&mut self, key: &Key) {
358 self.keys.remove(key);
359 }
360
361 #[must_use]
362 pub fn contains(&self, key: &Key) -> bool {
363 self.keys.contains(key)
364 }
365
366 #[must_use]
367 pub fn is_empty(&self) -> bool {
368 self.keys.is_empty()
369 }
370
371 #[must_use]
372 pub fn len(&self) -> usize {
373 self.keys.len()
374 }
375
376 #[must_use]
377 pub fn single_key(&self) -> Option<Key> {
378 if self.keys.len() == 1 {
379 self.keys.iter().copied().next()
380 } else {
381 None
382 }
383 }
384}
385
386impl_storable_unbounded!(IndexEntry);
387
388#[cfg(test)]
393mod tests {
394 use super::*;
395 use crate::traits::Storable;
396
397 #[test]
398 #[should_panic(expected = "corrupted IndexKey: invalid size")]
399 fn index_key_rejects_undersized_bytes() {
400 let buf = vec![0u8; IndexKey::STORED_SIZE as usize - 1];
401 let _ = IndexKey::from_bytes(buf.into());
402 }
403
404 #[test]
405 #[should_panic(expected = "corrupted IndexKey: invalid size")]
406 fn index_key_rejects_oversized_bytes() {
407 let buf = vec![0u8; IndexKey::STORED_SIZE as usize + 1];
408 let _ = IndexKey::from_bytes(buf.into());
409 }
410
411 #[test]
412 #[allow(clippy::cast_possible_truncation)]
413 #[should_panic(expected = "corrupted IndexKey: len exceeds MAX_INDEX_FIELDS")]
414 fn index_key_rejects_len_over_max() {
415 let key = IndexKey::empty(IndexId::max_storable());
416 let mut bytes = key.to_bytes().into_owned();
417 let len_offset = IndexName::STORED_SIZE as usize;
418 bytes[len_offset] = (MAX_INDEX_FIELDS as u8) + 1;
419 let _ = IndexKey::from_bytes(bytes.into());
420 }
421
422 #[test]
423 #[should_panic(expected = "corrupted IndexKey: invalid IndexName")]
424 fn index_key_rejects_invalid_index_name() {
425 let key = IndexKey::empty(IndexId::max_storable());
426 let mut bytes = key.to_bytes().into_owned();
427 bytes[0] = 0;
428 bytes[1] = 0;
429 let _ = IndexKey::from_bytes(bytes.into());
430 }
431
432 #[test]
433 #[expect(clippy::large_types_passed_by_value)]
434 fn index_key_ordering_matches_bytes() {
435 fn make_key(index_id: IndexId, len: u8, first: u8, second: u8) -> IndexKey {
436 let mut values = [[0u8; 16]; MAX_INDEX_FIELDS];
437 values[0] = [first; 16];
438 values[1] = [second; 16];
439 IndexKey {
440 index_id,
441 len,
442 values,
443 }
444 }
445
446 let entity = EntityName::from_static("entity");
447 let idx_a = IndexId(IndexName::from_parts(&entity, &["a"]));
448 let idx_b = IndexId(IndexName::from_parts(&entity, &["b"]));
449
450 let keys = vec![
451 make_key(idx_a, 1, 1, 0),
452 make_key(idx_a, 2, 1, 2),
453 make_key(idx_a, 1, 2, 0),
454 make_key(idx_b, 1, 0, 0),
455 ];
456
457 let mut sorted_by_ord = keys.clone();
458 sorted_by_ord.sort();
459
460 let mut sorted_by_bytes = keys;
461 sorted_by_bytes.sort_by(|a, b| a.to_bytes().cmp(&b.to_bytes()));
462
463 assert_eq!(
464 sorted_by_ord, sorted_by_bytes,
465 "IndexKey Ord and byte ordering diverged"
466 );
467 }
468}