Skip to main content

icydb_core/db/index/
store.rs

1//! Module: index::store
2//! Responsibility: stable index-entry persistence primitives.
3//! Does not own: range-scan resolution, continuation semantics, or predicate execution.
4//! Boundary: scan/executor layers depend on this storage boundary.
5
6use crate::{
7    db::{
8        data::StorageKey,
9        index::{entry::RawIndexEntry, key::RawIndexKey},
10    },
11    error::InternalError,
12};
13
14use canic_cdk::structures::{BTreeMap, DefaultMemoryImpl, memory::VirtualMemory};
15
16///
17/// IndexStore
18///
19/// Thin persistence wrapper over one stable BTreeMap.
20///
21/// Invariant: callers provide already-validated `RawIndexKey`/`RawIndexEntry`.
22///
23
24pub struct IndexStore {
25    pub(super) map: BTreeMap<RawIndexKey, RawIndexEntry, VirtualMemory<DefaultMemoryImpl>>,
26    generation: u64,
27    secondary_covering_authoritative: bool,
28    secondary_existence_witness_authoritative: bool,
29}
30
31impl IndexStore {
32    #[must_use]
33    pub fn init(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
34        Self {
35            map: BTreeMap::init(memory),
36            generation: 0,
37            secondary_covering_authoritative: false,
38            secondary_existence_witness_authoritative: false,
39        }
40    }
41
42    /// Snapshot all index entry pairs (diagnostics only).
43    #[allow(clippy::redundant_closure_for_method_calls)]
44    pub(crate) fn entries(&self) -> Vec<(RawIndexKey, RawIndexEntry)> {
45        self.map.iter().map(|entry| entry.into_pair()).collect()
46    }
47
48    pub(in crate::db) fn get(&self, key: &RawIndexKey) -> Option<RawIndexEntry> {
49        self.map.get(key)
50    }
51
52    pub fn len(&self) -> u64 {
53        self.map.len()
54    }
55
56    pub fn is_empty(&self) -> bool {
57        self.map.is_empty()
58    }
59
60    #[must_use]
61    pub(in crate::db) const fn generation(&self) -> u64 {
62        self.generation
63    }
64
65    pub(crate) fn insert(
66        &mut self,
67        key: RawIndexKey,
68        entry: RawIndexEntry,
69    ) -> Option<RawIndexEntry> {
70        let previous = self.map.insert(key, entry);
71        self.bump_generation();
72        self.invalidate_secondary_covering_authority();
73        self.invalidate_secondary_existence_witness_authority();
74        previous
75    }
76
77    pub(crate) fn remove(&mut self, key: &RawIndexKey) -> Option<RawIndexEntry> {
78        let previous = self.map.remove(key);
79        self.bump_generation();
80        self.invalidate_secondary_covering_authority();
81        self.invalidate_secondary_existence_witness_authority();
82        previous
83    }
84
85    pub fn clear(&mut self) {
86        self.map.clear();
87        self.bump_generation();
88        self.invalidate_secondary_covering_authority();
89        self.invalidate_secondary_existence_witness_authority();
90    }
91
92    /// Return whether this secondary-index store currently participates in a
93    /// synchronized covering-authority witness with its paired row store.
94    #[must_use]
95    pub(in crate::db) const fn secondary_covering_authoritative(&self) -> bool {
96        self.secondary_covering_authoritative
97    }
98
99    /// Mark this secondary-index store as synchronized with its paired row
100    /// store after successful commit or recovery.
101    pub(in crate::db) const fn mark_secondary_covering_authoritative(&mut self) {
102        self.secondary_covering_authoritative = true;
103    }
104
105    /// Return whether this secondary-index store currently carries explicit
106    /// per-entry row-existence witness state.
107    #[must_use]
108    pub(in crate::db) const fn secondary_existence_witness_authoritative(&self) -> bool {
109        self.secondary_existence_witness_authoritative
110    }
111
112    /// Mark this secondary-index store as synchronized with one explicit
113    /// storage-owned existence witness contract.
114    pub(in crate::db) const fn mark_secondary_existence_witness_authoritative(&mut self) {
115        self.secondary_existence_witness_authoritative = true;
116    }
117
118    /// Mark one storage key as missing anywhere it still appears inside this
119    /// secondary index store, while preserving the surrounding entry itself.
120    pub(in crate::db) fn mark_memberships_missing_for_storage_key(
121        &mut self,
122        storage_key: StorageKey,
123    ) -> Result<usize, InternalError> {
124        let mut entries = Vec::new();
125
126        for entry in self.map.iter() {
127            entries.push(entry.into_pair());
128        }
129
130        let mut marked = 0usize;
131
132        for (raw_key, mut raw_entry) in entries {
133            if raw_entry
134                .mark_key_missing(storage_key)
135                .map_err(InternalError::index_entry_decode_failed)?
136            {
137                self.map.insert(raw_key, raw_entry);
138                marked = marked.saturating_add(1);
139            }
140        }
141
142        if marked > 0 {
143            self.bump_generation();
144            self.invalidate_secondary_covering_authority();
145            self.invalidate_secondary_existence_witness_authority();
146        }
147
148        Ok(marked)
149    }
150
151    /// Sum of bytes used by all stored index entries.
152    pub fn memory_bytes(&self) -> u64 {
153        self.map
154            .iter()
155            .map(|entry| entry.key().as_bytes().len() as u64 + entry.value().len() as u64)
156            .sum()
157    }
158
159    const fn bump_generation(&mut self) {
160        self.generation = self.generation.saturating_add(1);
161    }
162
163    const fn invalidate_secondary_covering_authority(&mut self) {
164        self.secondary_covering_authoritative = false;
165    }
166
167    const fn invalidate_secondary_existence_witness_authority(&mut self) {
168        self.secondary_existence_witness_authoritative = false;
169    }
170}