icydb_core/db/store/
index.rs

1use crate::{
2    MAX_INDEX_FIELDS,
3    db::{
4        executor::ExecutorError,
5        store::{DataKey, StoreRegistry},
6    },
7    error::InternalError,
8    obs::sink::{self, MetricsEvent},
9    prelude::*,
10};
11use candid::CandidType;
12use canic_cdk::structures::{BTreeMap, DefaultMemoryImpl, memory::VirtualMemory};
13use canic_memory::{impl_storable_bounded, impl_storable_unbounded};
14use canic_utils::hash::hash_u64;
15use derive_more::{Deref, DerefMut, Display};
16use serde::{Deserialize, Serialize};
17use std::{
18    collections::HashSet,
19    fmt::{self, Display},
20};
21
22///
23/// IndexStoreRegistry
24///
25
26#[derive(Deref, DerefMut)]
27pub struct IndexStoreRegistry(StoreRegistry<IndexStore>);
28
29impl IndexStoreRegistry {
30    #[must_use]
31    #[allow(clippy::new_without_default)]
32    /// Create an empty index store registry.
33    pub fn new() -> Self {
34        Self(StoreRegistry::new())
35    }
36}
37
38///
39/// IndexStore
40///
41
42#[derive(Deref, DerefMut)]
43pub struct IndexStore(BTreeMap<IndexKey, IndexEntry, VirtualMemory<DefaultMemoryImpl>>);
44
45impl IndexStore {
46    #[must_use]
47    /// Initialize an index store with the provided backing memory.
48    pub fn init(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
49        Self(BTreeMap::init(memory))
50    }
51
52    /// Inserts the given entity into the index defined by `I`.
53    /// - If `I::UNIQUE`, insertion will fail if a conflicting entry already exists.
54    /// - If the entity is missing required fields for this index, insertion is skipped.
55    /// - Insert an entity's index entry, enforcing uniqueness where required.
56    pub fn insert_index_entry<E: EntityKind>(
57        &mut self,
58        entity: &E,
59        index: &IndexModel,
60    ) -> Result<(), InternalError> {
61        // Skip if index key can't be built (e.g. optional fields missing)
62        let Some(index_key) = IndexKey::new(entity, index) else {
63            return Ok(());
64        };
65        let key = entity.key();
66
67        if let Some(mut existing) = self.get(&index_key) {
68            if index.unique {
69                // Already present → skip redundant write
70                if existing.contains(&key) {
71                    return Ok(());
72                }
73
74                // Any different existing key violates UNIQUE
75                if !existing.is_empty() {
76                    sink::record(MetricsEvent::UniqueViolation {
77                        entity_path: E::PATH,
78                    });
79
80                    return Err(ExecutorError::index_violation(E::PATH, index.fields).into());
81                }
82
83                self.insert(index_key.clone(), IndexEntry::new(index.fields, key));
84            } else {
85                existing.insert_key(key); // <-- add to the set
86                self.insert(index_key.clone(), existing);
87            }
88        } else {
89            self.insert(index_key, IndexEntry::new(index.fields, key));
90        }
91        sink::record(MetricsEvent::IndexInsert {
92            entity_path: E::PATH,
93        });
94
95        Ok(())
96    }
97
98    // remove_index_entry
99    /// Remove an entity's entry for the given index if present.
100    pub fn remove_index_entry<E: EntityKind>(&mut self, entity: &E, index: &IndexModel) {
101        // Skip if index key can't be built (e.g. optional fields missing)
102        let Some(index_key) = IndexKey::new(entity, index) else {
103            return;
104        };
105
106        if let Some(mut existing) = self.get(&index_key) {
107            existing.remove_key(&entity.key()); // remove from the set
108
109            if existing.is_empty() {
110                self.remove(&index_key);
111            } else {
112                // Move the updated entry back without cloning
113                self.insert(index_key, existing);
114            }
115            sink::record(MetricsEvent::IndexRemove {
116                entity_path: E::PATH,
117            });
118        }
119    }
120
121    #[must_use]
122    /// Resolve data keys for a given index prefix.
123    pub fn resolve_data_values<E: EntityKind>(
124        &self,
125        index: &IndexModel,
126        prefix: &[Value],
127    ) -> Vec<DataKey> {
128        let mut out = Vec::new();
129
130        for (_, entry) in self.iter_with_hashed_prefix::<E>(index, prefix) {
131            out.extend(entry.keys.iter().map(|&k| DataKey::new::<E>(k)));
132        }
133
134        out
135    }
136
137    /// Sum of bytes used by all index entries.
138    pub fn memory_bytes(&self) -> u64 {
139        self.iter()
140            .map(|entry| u64::from(IndexKey::STORABLE_MAX_SIZE) + entry.value().len() as u64)
141            .sum()
142    }
143
144    /// Internal: iterate entries for this index whose `hashed_values` start with the hashed `prefix`.
145    /// Uses a bounded range for efficient scanning.
146    fn iter_with_hashed_prefix<E: EntityKind>(
147        &self,
148        index: &IndexModel,
149        prefix: &[Value],
150    ) -> impl Iterator<Item = (IndexKey, IndexEntry)> {
151        let index_id = IndexId::new::<E>(index);
152        let hashed_prefix_opt = Self::index_fingerprints(prefix); // Option<Vec<[u8;16]>>
153
154        // Compute start..end bounds. If the prefix isn't indexable, construct an empty range
155        // (same iterator type) by using identical start==end under the same index_id.
156        let (start_key, end_key) = if let Some(hp) = hashed_prefix_opt {
157            IndexKey::bounds_for_prefix(index_id, hp)
158        } else {
159            (
160                IndexKey {
161                    index_id,
162                    hashed_values: Vec::new(),
163                },
164                IndexKey {
165                    index_id,
166                    hashed_values: Vec::new(),
167                },
168            )
169        };
170
171        self.range(start_key..end_key)
172            .map(|entry| (entry.key().clone(), entry.value()))
173    }
174
175    fn index_fingerprints(values: &[Value]) -> Option<Vec<[u8; 16]>> {
176        // collects to Option<Vec<_>>: None if any element was non-indexable
177        values.iter().map(Value::to_index_fingerprint).collect()
178    }
179}
180
181///
182/// IndexId
183///
184
185#[derive(
186    Clone,
187    Debug,
188    Display,
189    Copy,
190    PartialEq,
191    Eq,
192    Hash,
193    PartialOrd,
194    Ord,
195    CandidType,
196    Serialize,
197    Deserialize,
198)]
199pub struct IndexId(u64);
200
201impl IndexId {
202    #[must_use]
203    /// Deterministic index identifier derived from entity path and field list.
204    pub fn new<E: EntityKind>(index: &IndexModel) -> Self {
205        Self::from_path_and_fields(E::PATH, index.fields)
206    }
207
208    fn from_path_and_fields(path: &str, fields: &[&str]) -> Self {
209        let cap = path.len() + fields.iter().map(|f| f.len() + 1).sum::<usize>();
210        let mut buffer = Vec::with_capacity(cap);
211
212        // much more efficient than format
213        buffer.extend_from_slice(path.as_bytes());
214        for field in fields {
215            buffer.extend_from_slice(field.as_bytes());
216            buffer.extend_from_slice(b"|");
217        }
218
219        Self(hash_u64(&buffer))
220    }
221
222    #[must_use]
223    /// Worst-case index identifier used for sizing tests.
224    pub fn max_storable() -> Self {
225        Self::from_path_and_fields(
226            "path::to::long::entity::name::Entity",
227            &[
228                "long_field_one",
229                "long_field_two",
230                "long_field_three",
231                "long_field_four",
232            ],
233        )
234    }
235}
236
237///
238/// IndexKey
239///
240
241#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
242pub struct IndexKey {
243    pub index_id: IndexId,
244    pub hashed_values: Vec<[u8; 16]>,
245}
246
247impl IndexKey {
248    // Sized with headroom for worst‑case hashed key payload
249    pub const STORABLE_MAX_SIZE: u32 = 180;
250
251    #[must_use]
252    /// Build an index key from an entity and spec, returning None if a component is missing/non-indexable.
253    pub fn new<E: EntityKind>(entity: &E, index: &IndexModel) -> Option<Self> {
254        let mut hashed_values = Vec::<[u8; 16]>::with_capacity(index.fields.len());
255
256        // get each value and convert to key
257        for field in index.fields {
258            let value = entity.get_value(field)?;
259            let fp = value.to_index_fingerprint()?; // bail if any component is non-indexable
260
261            hashed_values.push(fp);
262        }
263
264        Some(Self {
265            index_id: IndexId::new::<E>(index),
266            hashed_values,
267        })
268    }
269
270    // max_storable
271    #[must_use]
272    /// Largest representable index key (for sizing).
273    pub fn max_storable() -> Self {
274        Self {
275            index_id: IndexId::max_storable(),
276            hashed_values: (0..MAX_INDEX_FIELDS).map(|_| [u8::MAX; 16]).collect(),
277        }
278    }
279
280    /// Compute the bounded start..end keys for a given hashed prefix under an index id.
281    /// End is exclusive and created by appending a single 0xFF..0xFF block to the prefix.
282    #[must_use]
283    /// The returned range can be used with `BTreeMap::range`.
284    pub fn bounds_for_prefix(index_id: IndexId, mut prefix: Vec<[u8; 16]>) -> (Self, Self) {
285        let start = Self {
286            index_id,
287            hashed_values: prefix.clone(),
288        };
289        prefix.push([0xFF; 16]);
290        let end = Self {
291            index_id,
292            hashed_values: prefix,
293        };
294        (start, end)
295    }
296}
297
298impl Display for IndexKey {
299    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
300        write!(
301            f,
302            "id: {}, values: {}",
303            self.index_id,
304            self.hashed_values.len()
305        )
306    }
307}
308
309impl_storable_bounded!(IndexKey, IndexKey::STORABLE_MAX_SIZE, false);
310
311///
312/// IndexEntry
313///
314
315#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
316pub struct IndexEntry {
317    fields: Vec<String>,
318    keys: HashSet<Key>,
319}
320
321impl IndexEntry {
322    #[must_use]
323    /// Create an index entry with the initial key.
324    pub fn new(fields: &[&str], key: Key) -> Self {
325        let mut key_set = HashSet::with_capacity(1);
326        key_set.insert(key);
327
328        Self {
329            fields: fields.iter().map(ToString::to_string).collect(),
330            keys: key_set,
331        }
332    }
333
334    /// Insert a key into the entry's key set.
335    pub fn insert_key(&mut self, key: Key) {
336        let _ = self.keys.insert(key);
337    }
338
339    /// Removes the key from the set.
340    pub fn remove_key(&mut self, key: &Key) {
341        let _ = self.keys.remove(key);
342    }
343
344    /// Checks if the set contains the key.
345    #[must_use]
346    pub fn contains(&self, key: &Key) -> bool {
347        self.keys.contains(key)
348    }
349
350    /// Returns `true` if the set is empty.
351    #[must_use]
352    pub fn is_empty(&self) -> bool {
353        self.keys.is_empty()
354    }
355
356    /// Returns number of keys in the entry.
357    #[must_use]
358    pub fn len(&self) -> usize {
359        self.keys.len()
360    }
361
362    /// Returns the only key when exactly one exists.
363    #[must_use]
364    pub fn single_key(&self) -> Option<Key> {
365        if self.keys.len() == 1 {
366            self.keys.iter().copied().next()
367        } else {
368            None
369        }
370    }
371
372    /// Returns keys as a sorted `Vec<Key>` (useful for serialization/debug).
373    #[must_use]
374    pub fn to_sorted_vec(&self) -> Vec<Key> {
375        let mut keys: Vec<_> = self.keys.iter().copied().collect();
376        keys.sort_unstable(); // uses Ord, fast
377        keys
378    }
379}
380
381impl_storable_unbounded!(IndexEntry);
382
383///
384/// TESTS
385///
386
387#[cfg(test)]
388mod tests {
389    use super::*;
390    use crate::traits::*;
391
392    #[test]
393    fn index_key_max_size_is_bounded() {
394        let index_key = IndexKey::max_storable();
395        let size = Storable::to_bytes(&index_key).len();
396
397        assert!(
398            size <= IndexKey::STORABLE_MAX_SIZE as usize,
399            "serialized IndexKey too large: got {size} bytes (limit {})",
400            IndexKey::STORABLE_MAX_SIZE
401        );
402    }
403
404    #[test]
405    fn index_entry_round_trip() {
406        let original = IndexEntry::new(&["a", "b"], Key::from(1u64));
407        let encoded = Storable::to_bytes(&original);
408        let decoded = IndexEntry::from_bytes(encoded);
409
410        assert_eq!(original.fields, decoded.fields);
411        assert_eq!(original.to_sorted_vec(), decoded.to_sorted_vec());
412    }
413}