Skip to main content

icydb_core/db/index/
store.rs

1use crate::{
2    db::{
3        index::{
4            entry::RawIndexEntry,
5            fingerprint,
6            key::{IndexId, IndexKey, RawIndexKey},
7        },
8        store::{DataKey, StoreRegistry},
9    },
10    error::{ErrorClass, ErrorOrigin, InternalError},
11    model::index::IndexModel,
12    traits::{EntityKind, Storable},
13    value::Value,
14};
15use canic_cdk::structures::{BTreeMap, DefaultMemoryImpl, memory::VirtualMemory, storable::Bound};
16use canic_utils::hash::Xxh3;
17use derive_more::{Deref, DerefMut};
18use std::borrow::Cow;
19
20/*
21Index Fingerprints — Design Contract (0.6)
22
23Fingerprints are *non-authoritative diagnostic witnesses* stored alongside
24index entries. They exist solely to detect divergence during development.
25
26Authoritative correctness comes from:
27- Stored index entries
28- Decoded row data
29- Commit/recovery replay
30
31Key properties:
32- Fingerprints are written and removed in lockstep with index entries.
33- Release builds do not read or validate fingerprints.
34- Debug builds verify fingerprints opportunistically and panic on mismatch.
35- Divergence is detectable, not repaired.
36- Rebuild is the migration boundary for fingerprint format changes.
37
38This file intentionally does *not* attempt healing, validation in release,
39or correctness enforcement via fingerprints.
40*/
41
42/// Raw, fixed-size fingerprint bytes stored alongside index entries.
43#[derive(Clone, Copy, Debug, Eq, PartialEq)]
44struct RawIndexFingerprint([u8; 16]);
45
46impl RawIndexFingerprint {
47    const STORED_SIZE: u32 = 16;
48}
49
50impl Storable for RawIndexFingerprint {
51    fn to_bytes(&self) -> Cow<'_, [u8]> {
52        Cow::Borrowed(&self.0)
53    }
54
55    fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
56        let mut out = [0u8; 16];
57        if bytes.len() == out.len() {
58            out.copy_from_slice(bytes.as_ref());
59        }
60        Self(out)
61    }
62
63    fn into_bytes(self) -> Vec<u8> {
64        self.0.to_vec()
65    }
66
67    const BOUND: Bound = Bound::Bounded {
68        max_size: Self::STORED_SIZE,
69        is_fixed_size: true,
70    };
71}
72
73///
74/// IndexStoreRegistry
75///
76
77#[derive(Deref, DerefMut)]
78pub struct IndexStoreRegistry(StoreRegistry<IndexStore>);
79
80impl IndexStoreRegistry {
81    #[must_use]
82    pub fn new() -> Self {
83        Self(StoreRegistry::new())
84    }
85}
86
87impl Default for IndexStoreRegistry {
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93///
94/// IndexStore
95///
96
97pub struct IndexStore {
98    entry: VirtualMemory<DefaultMemoryImpl>,
99    fingerprint: VirtualMemory<DefaultMemoryImpl>,
100}
101
102impl IndexStore {
103    #[must_use]
104    pub const fn init(
105        entry: VirtualMemory<DefaultMemoryImpl>,
106        fingerprint: VirtualMemory<DefaultMemoryImpl>,
107    ) -> Self {
108        Self { entry, fingerprint }
109    }
110
111    /// Snapshot all index entry pairs (diagnostics only).
112    pub fn entries(&self) -> Vec<(RawIndexKey, RawIndexEntry)> {
113        self.entry_map()
114            .iter()
115            .map(|e| (*e.key(), e.value()))
116            .collect()
117    }
118
119    pub fn len(&self) -> u64 {
120        self.entry_map().len()
121    }
122
123    pub fn is_empty(&self) -> bool {
124        self.entry_map().is_empty()
125    }
126
127    pub fn get(&self, key: &RawIndexKey) -> Option<RawIndexEntry> {
128        let entry = self.entry_map().get(key);
129
130        // Debug-only verification: fingerprints are non-authoritative and
131        // checked only to surface divergence during development.
132        #[cfg(debug_assertions)]
133        if let Some(ref value) = entry
134            && let Err(err) = self.verify_entry_fingerprint(None, key, value)
135        {
136            panic!("index fingerprint verification failed: {err:?} (debug-only)");
137        }
138
139        entry
140    }
141
142    pub fn insert(&mut self, key: RawIndexKey, value: RawIndexEntry) -> Option<RawIndexEntry> {
143        let fingerprint = Self::entry_fingerprint(&key, &value);
144        let prev = self.entry_map().insert(key, value);
145
146        // NOTE: Mid-write traps may cause divergence. This is acceptable;
147        // fingerprints are diagnostic only and verified in debug builds.
148        let _ = self.fingerprint_map().insert(key, fingerprint);
149
150        prev
151    }
152
153    pub fn remove(&mut self, key: &RawIndexKey) -> Option<RawIndexEntry> {
154        let removed = self.entry_map().remove(key);
155
156        // See insert(): divergence is acceptable and detectable in debug builds.
157        let _ = self.fingerprint_map().remove(key);
158
159        removed
160    }
161
162    pub fn clear(&mut self) {
163        self.entry_map().clear();
164        self.fingerprint_map().clear();
165    }
166
167    pub(crate) fn resolve_data_values<E: EntityKind>(
168        &self,
169        index: &IndexModel,
170        prefix: &[Value],
171    ) -> Result<Vec<DataKey>, InternalError> {
172        if prefix.len() > index.fields.len() {
173            return Err(InternalError::new(
174                ErrorClass::Unsupported,
175                ErrorOrigin::Index,
176                format!(
177                    "index prefix length {} exceeds field count {}",
178                    prefix.len(),
179                    index.fields.len()
180                ),
181            ));
182        }
183
184        let index_id = IndexId::new::<E>(index);
185
186        let mut fps = Vec::with_capacity(prefix.len());
187        for value in prefix {
188            let Some(fp) = fingerprint::to_index_fingerprint(value)? else {
189                return Err(InternalError::new(
190                    ErrorClass::Unsupported,
191                    ErrorOrigin::Index,
192                    "index prefix value is not indexable",
193                ));
194            };
195            fps.push(fp);
196        }
197
198        let (start, end) = IndexKey::bounds_for_prefix(index_id, index.fields.len(), &fps);
199        let (start_raw, end_raw) = (start.to_raw(), end.to_raw());
200
201        let mut out = Vec::new();
202
203        for entry in self.entry_map().range(start_raw..=end_raw) {
204            let raw_key = entry.key();
205            let raw_entry = entry.value();
206
207            #[cfg(debug_assertions)]
208            if let Err(err) = self.verify_entry_fingerprint(Some(index), raw_key, &raw_entry) {
209                panic!("index fingerprint verification failed: {err:?} (debug-only)");
210            }
211
212            // Validate index key structure
213            IndexKey::try_from_raw(raw_key).map_err(|err| {
214                InternalError::new(
215                    ErrorClass::Corruption,
216                    ErrorOrigin::Index,
217                    format!("index key corrupted during resolve: {err}"),
218                )
219            })?;
220
221            // Decode storage keys
222            let storage_keys = raw_entry.decode_keys().map_err(|err| {
223                InternalError::new(ErrorClass::Corruption, ErrorOrigin::Index, err.to_string())
224            })?;
225
226            if index.unique && storage_keys.len() != 1 {
227                return Err(InternalError::new(
228                    ErrorClass::Corruption,
229                    ErrorOrigin::Index,
230                    "unique index entry contains an unexpected number of keys",
231                ));
232            }
233
234            // Convert to DataKeys (storage boundary — no typed IDs)
235            out.extend(
236                storage_keys
237                    .into_iter()
238                    .map(|sk| DataKey::from_storage_key::<E>(sk)),
239            );
240        }
241
242        #[cfg(debug_assertions)]
243        self.debug_verify_no_orphaned_fingerprints(index, &start_raw, &end_raw);
244
245        Ok(out)
246    }
247
248    pub fn memory_bytes(&self) -> u64 {
249        let entry_bytes = self
250            .entry_map()
251            .iter()
252            .map(|e| {
253                let v: RawIndexEntry = e.value();
254                IndexKey::STORED_SIZE_BYTES + v.len() as u64
255            })
256            .sum::<u64>();
257
258        let fingerprint_bytes = self
259            .fingerprint_map()
260            .iter()
261            .map(|_| IndexKey::STORED_SIZE_BYTES + u64::from(RawIndexFingerprint::STORED_SIZE))
262            .sum::<u64>();
263
264        entry_bytes.saturating_add(fingerprint_bytes)
265    }
266
267    fn entry_map(&self) -> BTreeMap<RawIndexKey, RawIndexEntry, VirtualMemory<DefaultMemoryImpl>> {
268        BTreeMap::init(self.entry.clone())
269    }
270
271    fn fingerprint_map(
272        &self,
273    ) -> BTreeMap<RawIndexKey, RawIndexFingerprint, VirtualMemory<DefaultMemoryImpl>> {
274        BTreeMap::init(self.fingerprint.clone())
275    }
276
277    fn entry_fingerprint(key: &RawIndexKey, entry: &RawIndexEntry) -> RawIndexFingerprint {
278        const VERSION: u8 = 1;
279
280        let mut h = Xxh3::with_seed(0);
281        h.update(&[VERSION]);
282        h.update(key.as_bytes());
283        h.update(entry.as_bytes());
284
285        RawIndexFingerprint(h.digest128().to_be_bytes())
286    }
287
288    #[cfg(debug_assertions)]
289    fn verify_entry_fingerprint(
290        &self,
291        index: Option<&IndexModel>,
292        key: &RawIndexKey,
293        entry: &RawIndexEntry,
294    ) -> Result<(), Box<FingerprintVerificationError>> {
295        let expected = Self::entry_fingerprint(key, entry);
296        let stored = self.fingerprint_map().get(key);
297
298        let label = index
299            .map(|idx| format!("index='{}'", idx.name))
300            .or_else(|| {
301                IndexKey::try_from_raw(key)
302                    .ok()
303                    .map(|decoded| format!("index_key={decoded:?}"))
304            })
305            .unwrap_or_else(|| "index=<unknown>".to_string());
306
307        match stored {
308            None => Err(Box::new(FingerprintVerificationError::Missing {
309                label,
310                key: *key,
311            })),
312            Some(actual) if actual != expected => {
313                Err(Box::new(FingerprintVerificationError::Mismatch {
314                    label,
315                    key: *key,
316                    expected,
317                    actual,
318                }))
319            }
320            Some(_) => Ok(()),
321        }
322    }
323
324    #[cfg(test)]
325    #[expect(dead_code)]
326    pub(crate) fn debug_fingerprint_for(&self, key: &RawIndexKey) -> Option<[u8; 16]> {
327        self.fingerprint_map().get(key).map(|fp| fp.0)
328    }
329
330    #[cfg(debug_assertions)]
331    fn debug_verify_no_orphaned_fingerprints(
332        &self,
333        index: &IndexModel,
334        start: &RawIndexKey,
335        end: &RawIndexKey,
336    ) {
337        for fp in self.fingerprint_map().range(*start..=*end) {
338            assert!(
339                self.entry_map().get(fp.key()).is_some(),
340                "index fingerprint orphaned: index='{}' key={:?} (debug-only)",
341                index.name,
342                fp.key()
343            );
344        }
345    }
346}
347
348///
349/// FingerprintVerificationError
350///
351
352#[cfg(debug_assertions)]
353#[allow(dead_code)]
354#[derive(Debug)]
355enum FingerprintVerificationError {
356    Missing {
357        label: String,
358        key: RawIndexKey,
359    },
360    Mismatch {
361        label: String,
362        key: RawIndexKey,
363        expected: RawIndexFingerprint,
364        actual: RawIndexFingerprint,
365    },
366}