icydb_core/db/index/
store.rs

1use crate::{
2    db::{
3        index::{
4            entry::{IndexEntry, IndexEntryCorruption, MAX_INDEX_ENTRY_KEYS, RawIndexEntry},
5            fingerprint,
6            key::{IndexId, IndexKey, RawIndexKey},
7        },
8        store::{DataKey, StoreRegistry},
9    },
10    prelude::{EntityKind, IndexModel, Value},
11};
12use canic_cdk::structures::{BTreeMap, DefaultMemoryImpl, memory::VirtualMemory};
13use derive_more::{Deref, DerefMut};
14use thiserror::Error as ThisError;
15
16///
17/// IndexStoreRegistry
18///
19/// Registry managing all index stores for the database.
20/// Provides lifecycle and lookup for per-index stable-memory stores.
21///
22
23#[derive(Deref, DerefMut)]
24pub struct IndexStoreRegistry(StoreRegistry<IndexStore>);
25
26impl IndexStoreRegistry {
27    #[must_use]
28    pub fn new() -> Self {
29        Self(StoreRegistry::new())
30    }
31}
32
33impl Default for IndexStoreRegistry {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39///
40/// IndexInsertOutcome
41///
42/// Result of attempting to insert an entity into an index.
43/// Distinguishes between a successful mutation and a no-op.
44///
45
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47pub enum IndexInsertOutcome {
48    Inserted,
49    Skipped,
50}
51
52///
53/// IndexRemoveOutcome
54///
55/// Result of attempting to remove an entity from an index.
56///
57
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub enum IndexRemoveOutcome {
60    Removed,
61    Skipped,
62}
63
64///
65/// IndexInsertError
66///
67/// Errors that may occur while inserting into an index.
68/// Represents both logical constraint violations and corruption.
69///
70
71#[derive(Debug, ThisError)]
72pub enum IndexInsertError {
73    #[error("unique index violation")]
74    UniqueViolation,
75    #[error("index entry corrupted: {0}")]
76    CorruptedEntry(#[from] IndexEntryCorruption),
77    #[error("index entry exceeds max keys: {keys} (limit {MAX_INDEX_ENTRY_KEYS})")]
78    EntryTooLarge { keys: usize },
79}
80
81///
82/// IndexStore
83///
84/// Stable-memory backed secondary index.
85/// Maps composite index keys to one or more entity primary keys.
86///
87
88#[derive(Deref, DerefMut)]
89pub struct IndexStore(BTreeMap<RawIndexKey, RawIndexEntry, VirtualMemory<DefaultMemoryImpl>>);
90
91impl IndexStore {
92    #[must_use]
93    pub fn init(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
94        Self(BTreeMap::init(memory))
95    }
96
97    pub fn insert_index_entry<E: EntityKind>(
98        &mut self,
99        entity: &E,
100        index: &IndexModel,
101    ) -> Result<IndexInsertOutcome, IndexInsertError> {
102        let Some(index_key) = IndexKey::new(entity, index) else {
103            return Ok(IndexInsertOutcome::Skipped);
104        };
105        let raw_key = index_key.to_raw();
106        let key = entity.key();
107
108        if let Some(raw_entry) = self.get(&raw_key) {
109            let mut entry = raw_entry.try_decode()?;
110            if index.unique {
111                if entry.len() > 1 {
112                    return Err(IndexEntryCorruption::NonUniqueEntry { keys: entry.len() }.into());
113                }
114                if entry.contains(&key) {
115                    return Ok(IndexInsertOutcome::Skipped);
116                }
117                if !entry.is_empty() {
118                    return Err(IndexInsertError::UniqueViolation);
119                }
120                let entry = IndexEntry::new(key);
121                let raw_entry = RawIndexEntry::try_from_entry(&entry)
122                    .map_err(|err| IndexInsertError::EntryTooLarge { keys: err.keys() })?;
123                self.insert(raw_key, raw_entry);
124            } else {
125                entry.insert_key(key);
126                let raw_entry = RawIndexEntry::try_from_entry(&entry)
127                    .map_err(|err| IndexInsertError::EntryTooLarge { keys: err.keys() })?;
128                self.insert(raw_key, raw_entry);
129            }
130        } else {
131            let entry = IndexEntry::new(key);
132            let raw_entry = RawIndexEntry::try_from_entry(&entry)
133                .map_err(|err| IndexInsertError::EntryTooLarge { keys: err.keys() })?;
134            self.insert(raw_key, raw_entry);
135        }
136
137        Ok(IndexInsertOutcome::Inserted)
138    }
139
140    pub fn remove_index_entry<E: EntityKind>(
141        &mut self,
142        entity: &E,
143        index: &IndexModel,
144    ) -> Result<IndexRemoveOutcome, IndexEntryCorruption> {
145        let Some(index_key) = IndexKey::new(entity, index) else {
146            return Ok(IndexRemoveOutcome::Skipped);
147        };
148        let raw_key = index_key.to_raw();
149
150        if let Some(raw_entry) = self.get(&raw_key) {
151            let mut entry = raw_entry.try_decode()?;
152            entry.remove_key(&entity.key());
153            if entry.is_empty() {
154                self.remove(&raw_key);
155            } else {
156                let raw_entry = RawIndexEntry::try_from_entry(&entry)
157                    .map_err(|err| IndexEntryCorruption::TooManyKeys { count: err.keys() })?;
158                self.insert(raw_key, raw_entry);
159            }
160            return Ok(IndexRemoveOutcome::Removed);
161        }
162
163        Ok(IndexRemoveOutcome::Skipped)
164    }
165
166    pub fn resolve_data_values<E: EntityKind>(
167        &self,
168        index: &IndexModel,
169        prefix: &[Value],
170    ) -> Result<Vec<DataKey>, &'static str> {
171        let mut out = Vec::new();
172        let index_id = IndexId::new::<E>(index);
173
174        let Some(fps) = prefix
175            .iter()
176            .map(fingerprint::to_index_fingerprint)
177            .collect::<Option<Vec<_>>>()
178        else {
179            return Ok(out);
180        };
181
182        let (start, end) = IndexKey::bounds_for_prefix(index_id, index.fields.len(), &fps);
183        let start_raw = start.to_raw();
184        let end_raw = end.to_raw();
185
186        for entry in self.range(start_raw..=end_raw) {
187            let _ = IndexKey::try_from_raw(entry.key())?;
188            let decoded = entry.value().try_decode().map_err(|err| err.message())?;
189            out.extend(decoded.iter_keys().map(|k| DataKey::new::<E>(k)));
190        }
191
192        Ok(out)
193    }
194
195    /// Sum of bytes used by all index entries.
196    pub fn memory_bytes(&self) -> u64 {
197        self.iter()
198            .map(|entry| u64::from(IndexKey::STORED_SIZE) + entry.value().len() as u64)
199            .sum()
200    }
201}