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/// IndexState
18///
19/// Explicit lifecycle validity state for one index store.
20/// Validity matters because probe-free covering authority only makes sense once
21/// the index contents are fully built and query-visible for reads.
22///
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub enum IndexState {
25    Building,
26    Valid,
27    Dropping,
28}
29
30///
31/// IndexStore
32///
33/// Thin persistence wrapper over one stable BTreeMap.
34///
35/// Invariant: callers provide already-validated `RawIndexKey`/`RawIndexEntry`.
36///
37
38pub struct IndexStore {
39    pub(super) map: BTreeMap<RawIndexKey, RawIndexEntry, VirtualMemory<DefaultMemoryImpl>>,
40    generation: u64,
41    state: IndexState,
42    secondary_covering_authoritative: bool,
43    secondary_existence_witness_authoritative: bool,
44}
45
46impl IndexStore {
47    #[must_use]
48    pub fn init(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
49        Self {
50            map: BTreeMap::init(memory),
51            generation: 0,
52            // Existing stores default to Valid until one explicit build/drop
53            // lifecycle is introduced. Probe-free routing still also requires
54            // the narrower authority witnesses below.
55            state: IndexState::Valid,
56            secondary_covering_authoritative: false,
57            secondary_existence_witness_authoritative: false,
58        }
59    }
60
61    /// Snapshot all index entry pairs (diagnostics only).
62    #[allow(clippy::redundant_closure_for_method_calls)]
63    pub(crate) fn entries(&self) -> Vec<(RawIndexKey, RawIndexEntry)> {
64        self.map.iter().map(|entry| entry.into_pair()).collect()
65    }
66
67    pub(in crate::db) fn get(&self, key: &RawIndexKey) -> Option<RawIndexEntry> {
68        self.map.get(key)
69    }
70
71    pub fn len(&self) -> u64 {
72        self.map.len()
73    }
74
75    pub fn is_empty(&self) -> bool {
76        self.map.is_empty()
77    }
78
79    #[must_use]
80    pub(in crate::db) const fn generation(&self) -> u64 {
81        self.generation
82    }
83
84    /// Return the explicit lifecycle state for this index store.
85    #[must_use]
86    pub(in crate::db) const fn state(&self) -> IndexState {
87        self.state
88    }
89
90    /// Return whether this index store is query-visible for probe-free reads.
91    #[must_use]
92    pub(in crate::db) const fn is_valid(&self) -> bool {
93        matches!(self.state, IndexState::Valid)
94    }
95
96    /// Mark this index store as in-progress and therefore ineligible for
97    /// probe-free covering authority until a full authoritative rebuild ends.
98    pub(in crate::db) const fn mark_building(&mut self) {
99        self.state = IndexState::Building;
100        self.invalidate_secondary_covering_authority();
101        self.invalidate_secondary_existence_witness_authority();
102    }
103
104    /// Mark this index store as fully built and eligible for later authority
105    /// promotion once the narrower synchronized witness bits are also restored.
106    pub(in crate::db) const fn mark_valid(&mut self) {
107        self.state = IndexState::Valid;
108    }
109
110    /// Mark this index store as dropping and therefore fail closed for
111    /// authority-sensitive covering execution.
112    pub(in crate::db) const fn mark_dropping(&mut self) {
113        self.state = IndexState::Dropping;
114        self.invalidate_secondary_covering_authority();
115        self.invalidate_secondary_existence_witness_authority();
116    }
117
118    pub(crate) fn insert(
119        &mut self,
120        key: RawIndexKey,
121        entry: RawIndexEntry,
122    ) -> Option<RawIndexEntry> {
123        let previous = self.map.insert(key, entry);
124        self.bump_generation();
125        self.invalidate_secondary_covering_authority();
126        self.invalidate_secondary_existence_witness_authority();
127        previous
128    }
129
130    pub(crate) fn remove(&mut self, key: &RawIndexKey) -> Option<RawIndexEntry> {
131        let previous = self.map.remove(key);
132        self.bump_generation();
133        self.invalidate_secondary_covering_authority();
134        self.invalidate_secondary_existence_witness_authority();
135        previous
136    }
137
138    pub fn clear(&mut self) {
139        self.map.clear();
140        self.bump_generation();
141        self.invalidate_secondary_covering_authority();
142        self.invalidate_secondary_existence_witness_authority();
143    }
144
145    /// Return whether this secondary-index store currently participates in a
146    /// synchronized covering-authority witness with its paired row store.
147    #[must_use]
148    pub(in crate::db) const fn secondary_covering_authoritative(&self) -> bool {
149        self.secondary_covering_authoritative
150    }
151
152    /// Mark this secondary-index store as synchronized with its paired row
153    /// store after successful commit or recovery.
154    pub(in crate::db) fn mark_secondary_covering_authoritative(&mut self) {
155        debug_assert!(
156            self.is_valid(),
157            "secondary covering authority must not be restored while the index is Building or Dropping",
158        );
159        self.secondary_covering_authoritative = true;
160    }
161
162    /// Return whether this secondary-index store currently carries explicit
163    /// per-entry row-existence witness state.
164    #[must_use]
165    pub(in crate::db) const fn secondary_existence_witness_authoritative(&self) -> bool {
166        self.secondary_existence_witness_authoritative
167    }
168
169    /// Mark this secondary-index store as synchronized with one explicit
170    /// storage-owned existence witness contract.
171    pub(in crate::db) fn mark_secondary_existence_witness_authoritative(&mut self) {
172        debug_assert!(
173            self.is_valid(),
174            "storage existence witness authority must not be restored while the index is Building or Dropping",
175        );
176        self.secondary_existence_witness_authoritative = true;
177    }
178
179    /// Mark one storage key as missing anywhere it still appears inside this
180    /// secondary index store, while preserving the surrounding entry itself.
181    pub(in crate::db) fn mark_memberships_missing_for_storage_key(
182        &mut self,
183        storage_key: StorageKey,
184    ) -> Result<usize, InternalError> {
185        let mut entries = Vec::new();
186
187        for entry in self.map.iter() {
188            entries.push(entry.into_pair());
189        }
190
191        let mut marked = 0usize;
192
193        for (raw_key, mut raw_entry) in entries {
194            if raw_entry
195                .mark_key_missing(storage_key)
196                .map_err(InternalError::index_entry_decode_failed)?
197            {
198                self.map.insert(raw_key, raw_entry);
199                marked = marked.saturating_add(1);
200            }
201        }
202
203        if marked > 0 {
204            self.bump_generation();
205            self.invalidate_secondary_covering_authority();
206            self.invalidate_secondary_existence_witness_authority();
207        }
208
209        Ok(marked)
210    }
211
212    /// Sum of bytes used by all stored index entries.
213    pub fn memory_bytes(&self) -> u64 {
214        self.map
215            .iter()
216            .map(|entry| entry.key().as_bytes().len() as u64 + entry.value().len() as u64)
217            .sum()
218    }
219
220    const fn bump_generation(&mut self) {
221        self.generation = self.generation.saturating_add(1);
222    }
223
224    const fn invalidate_secondary_covering_authority(&mut self) {
225        self.secondary_covering_authoritative = false;
226    }
227
228    const fn invalidate_secondary_existence_witness_authority(&mut self) {
229        self.secondary_existence_witness_authoritative = false;
230    }
231}