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