Skip to main content

reddb_server/runtime/
impl_index_integrity.rs

1use super::*;
2
3/// A derived checkpoint index which does not match its authoritative source.
4#[derive(Debug)]
5pub struct IndexIntegrityFinding {
6    pub index: String,
7    pub reason: String,
8}
9
10impl RedDBRuntime {
11    /// Verify HASH/BTREE checkpoint postings against persisted source rows.
12    ///
13    /// This explicit, potentially expensive pass does not modify data or repair
14    /// indexes. It checks the checkpoint generation, not uncheckpointed WAL or
15    /// resident index overlays, and does not replace the offline physical scrub.
16    /// Other index methods are rejected rather than reported as verified.
17    pub fn verify_persisted_indexes(
18        &self,
19        collection: &str,
20    ) -> RedDBResult<Vec<IndexIntegrityFinding>> {
21        use super::index_store::IndexMethodKind;
22        let topology = self.index_store_ref().collection_topology_lock(collection);
23        let _topology = topology.read();
24        let indexes = self.index_store_ref().list_indices(collection);
25        let store = self.db().store();
26        let manager = store
27            .get_collection(collection)
28            .ok_or_else(|| RedDBError::NotFound(collection.into()))?;
29        if !store.is_paged()
30            || indexes.iter().any(|index| {
31                !matches!(index.method, IndexMethodKind::Hash | IndexMethodKind::BTree)
32            })
33        {
34            return Err(RedDBError::InvalidOperation(
35                "persisted index verification requires paged HASH/BTREE indexes".into(),
36            ));
37        }
38        let (_scratch, limit_bytes) = self.reserve_index_scan(&manager)?;
39        let mut findings = Vec::new();
40        for index in indexes {
41            let mut names = vec![index.name.clone()];
42            if index.method == IndexMethodKind::BTree && (index.columns.len() == 1 || index.unique)
43            {
44                names.push(super::paged_btree_index::equality_storage_name(&index.name));
45            }
46            for name in names {
47                if let Some(reason) =
48                    store.verify_persisted_secondary_index(collection, &name, limit_bytes)?
49                {
50                    findings.push(IndexIntegrityFinding {
51                        index: index.name.clone(),
52                        reason: format!("{name:?}: {reason}"),
53                    });
54                }
55            }
56        }
57        Ok(findings)
58    }
59}