reddb_server/runtime/
impl_index_integrity.rs1use super::*;
2
3#[derive(Debug)]
5pub struct IndexIntegrityFinding {
6 pub index: String,
7 pub reason: String,
8}
9
10impl RedDBRuntime {
11 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 || store.config().resident_memory_bytes_max.is_none()
31 || indexes.iter().any(|index| {
32 !matches!(index.method, IndexMethodKind::Hash | IndexMethodKind::BTree)
33 })
34 {
35 return Err(RedDBError::InvalidOperation(
36 "persisted index verification requires disk-resident paged HASH/BTREE indexes"
37 .into(),
38 ));
39 }
40 let (_scratch, limit_bytes) = self.reserve_index_scan(&manager)?;
41 let mut findings = Vec::new();
42 for index in indexes {
43 let mut names = vec![index.name.clone()];
44 if index.method == IndexMethodKind::BTree && (index.columns.len() == 1 || index.unique)
45 {
46 names.push(super::paged_btree_index::equality_storage_name(&index.name));
47 }
48 for name in names {
49 if let Some(reason) =
50 store.verify_persisted_secondary_index(collection, &name, limit_bytes)?
51 {
52 findings.push(IndexIntegrityFinding {
53 index: index.name.clone(),
54 reason: format!("{name:?}: {reason}"),
55 });
56 }
57 }
58 }
59 Ok(findings)
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn resident_serverless_indexes_reject_physical_projection_verification() {
69 let directory = tempfile::tempdir().expect("serverless index fixture");
70 let options = crate::api::RedDBOptions::persistent(directory.path().join("index.rdb"))
71 .with_memory_budget(64 * 1024 * 1024)
72 .with_storage_profile(
73 crate::storage::profile::StorageDeployPreset::Serverless.selection(),
74 )
75 .expect("serverless profile");
76 let runtime = RedDBRuntime::with_options(options).expect("serverless runtime");
77 runtime
78 .execute_query("CREATE TABLE records (id INTEGER)")
79 .expect("create table");
80 runtime
81 .execute_query("CREATE INDEX records_id ON records (id) USING HASH")
82 .expect("create index");
83
84 let error = runtime
85 .verify_persisted_indexes("records")
86 .expect_err("resident index has no physical projection to verify");
87 assert!(error.to_string().contains(
88 "persisted index verification requires disk-resident paged HASH/BTREE indexes"
89 ));
90 }
91}