Skip to main content

sqry_core/graph/unified/analysis/
cache.rs

1//! Analysis cache with memory-mapped file support
2
3use super::{CondensationDag, CsrAdjacency, SccData};
4use crate::graph::unified::edge::EdgeKind;
5#[cfg(test)]
6use crate::graph::unified::edge::ResolvedVia;
7use crate::graph::unified::persistence::{GraphStorage, load_from_path};
8use anyhow::Result;
9use std::collections::HashMap;
10use std::path::Path;
11use std::sync::Arc;
12
13/// Memory-mapped analysis cache for fast loading
14pub struct AnalysisCache {
15    /// Hash of the manifest file
16    pub manifest_hash: String,
17    /// Hash of node IDs for identity verification
18    pub node_id_hash: [u8; 32],
19    csr: Option<Arc<CsrAdjacency>>,
20    sccs: HashMap<EdgeKind, Arc<SccData>>,
21    condensations: HashMap<EdgeKind, Arc<CondensationDag>>,
22}
23
24impl AnalysisCache {
25    /// Load analysis cache from index directory
26    /// Returns an error if the operation fails.
27    ///
28    /// # Errors
29    ///
30    pub fn load(index_path: &Path) -> Result<Self> {
31        let storage = GraphStorage::new(index_path);
32
33        let manifest_hash = super::persistence::compute_manifest_hash(storage.manifest_path())?;
34        let graph = load_from_path(storage.snapshot_path(), None)?;
35        let node_id_hash = super::persistence::compute_node_id_hash(&graph.snapshot());
36        let identity =
37            super::persistence::AnalysisIdentity::new(manifest_hash.clone(), node_id_hash);
38
39        let csr = super::persistence::load_csr_checked(&storage.analysis_csr_path(), &identity)?;
40
41        let scc_calls =
42            super::persistence::load_scc_checked(&storage.analysis_scc_path("calls"), &identity)?;
43        let scc_imports =
44            super::persistence::load_scc_checked(&storage.analysis_scc_path("imports"), &identity)?;
45        let scc_references = super::persistence::load_scc_checked(
46            &storage.analysis_scc_path("references"),
47            &identity,
48        )?;
49        let scc_inherits = super::persistence::load_scc_checked(
50            &storage.analysis_scc_path("inherits"),
51            &identity,
52        )?;
53
54        let cond_calls = super::persistence::load_condensation_checked(
55            &storage.analysis_cond_path("calls"),
56            &identity,
57        )?;
58        let cond_imports = super::persistence::load_condensation_checked(
59            &storage.analysis_cond_path("imports"),
60            &identity,
61        )?;
62        let cond_references = super::persistence::load_condensation_checked(
63            &storage.analysis_cond_path("references"),
64            &identity,
65        )?;
66        let cond_inherits = super::persistence::load_condensation_checked(
67            &storage.analysis_cond_path("inherits"),
68            &identity,
69        )?;
70
71        let mut sccs = HashMap::new();
72        sccs.insert(scc_calls.edge_kind.clone(), Arc::new(scc_calls));
73        sccs.insert(scc_imports.edge_kind.clone(), Arc::new(scc_imports));
74        sccs.insert(scc_references.edge_kind.clone(), Arc::new(scc_references));
75        sccs.insert(scc_inherits.edge_kind.clone(), Arc::new(scc_inherits));
76
77        let mut condensations = HashMap::new();
78        condensations.insert(cond_calls.edge_kind.clone(), Arc::new(cond_calls));
79        condensations.insert(cond_imports.edge_kind.clone(), Arc::new(cond_imports));
80        condensations.insert(cond_references.edge_kind.clone(), Arc::new(cond_references));
81        condensations.insert(cond_inherits.edge_kind.clone(), Arc::new(cond_inherits));
82
83        Ok(Self {
84            manifest_hash,
85            node_id_hash,
86            csr: Some(Arc::new(csr)),
87            sccs,
88            condensations,
89        })
90    }
91
92    /// Get CSR adjacency
93    /// Returns an error if the operation fails.
94    ///
95    /// # Errors
96    ///
97    pub fn get_csr(&self) -> Result<Arc<CsrAdjacency>> {
98        self.csr
99            .clone()
100            .ok_or_else(|| anyhow::anyhow!("CSR not loaded"))
101    }
102
103    /// Get SCC data for an edge kind (matches by discriminant, ignores metadata)
104    /// Returns an error if the operation fails.
105    ///
106    /// # Errors
107    ///
108    pub fn get_scc(&self, kind: &EdgeKind) -> Result<Arc<SccData>> {
109        let kind_discriminant = std::mem::discriminant(kind);
110        self.sccs
111            .iter()
112            .find(|(k, _)| std::mem::discriminant(*k) == kind_discriminant)
113            .map(|(_, v)| v.clone())
114            .ok_or_else(|| anyhow::anyhow!("SCC for {kind:?} not loaded"))
115    }
116
117    /// Get condensation DAG for an edge kind (matches by discriminant, ignores metadata)
118    /// Returns an error if the operation fails.
119    ///
120    /// # Errors
121    ///
122    pub fn get_condensation(&self, kind: &EdgeKind) -> Result<Arc<CondensationDag>> {
123        let kind_discriminant = std::mem::discriminant(kind);
124        self.condensations
125            .iter()
126            .find(|(k, _)| std::mem::discriminant(*k) == kind_discriminant)
127            .map(|(_, v)| v.clone())
128            .ok_or_else(|| anyhow::anyhow!("Condensation for {kind:?} not loaded"))
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use crate::graph::unified::analysis::{AnalysisIdentity, GraphAnalyses};
136    use crate::graph::unified::compaction::snapshot_edges;
137    use crate::graph::unified::concurrent::CodeGraph;
138    use crate::graph::unified::edge::EdgeKind;
139    use crate::graph::unified::node::NodeKind;
140    use crate::graph::unified::persistence::{BuildProvenance, Manifest, save_to_path};
141    use crate::graph::unified::storage::NodeEntry;
142    use sha2::{Digest, Sha256};
143    use std::path::Path;
144    use tempfile::TempDir;
145
146    #[test]
147    fn test_analysis_cache_load_roundtrip() {
148        let temp_dir = TempDir::new().unwrap();
149        let root = temp_dir.path();
150        let storage = GraphStorage::new(root);
151        std::fs::create_dir_all(storage.graph_dir()).unwrap();
152
153        let mut graph = CodeGraph::new();
154        let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
155        let name = graph.strings_mut().intern("A").unwrap();
156        let node = graph
157            .nodes_mut()
158            .alloc(NodeEntry::new(NodeKind::Function, name, file_id))
159            .unwrap();
160        let kind = EdgeKind::Calls {
161            argument_count: 0,
162            is_async: false,
163            resolved_via: ResolvedVia::Direct,
164        };
165        graph
166            .edges_mut()
167            .add_edge(node, node, kind.clone(), file_id);
168
169        save_to_path(&graph, storage.snapshot_path()).unwrap();
170
171        let snapshot_bytes = std::fs::read(storage.snapshot_path()).unwrap();
172        let mut hasher = Sha256::new();
173        hasher.update(&snapshot_bytes);
174        let snapshot_hash = hex::encode(hasher.finalize());
175
176        let node_count = graph.nodes().len();
177        let edge_stats = graph.edges().stats();
178        let edge_count = edge_stats.forward.csr_edge_count + edge_stats.forward.delta_edge_count;
179
180        let provenance = BuildProvenance::default();
181        let manifest = Manifest::new(
182            root.to_string_lossy().to_string(),
183            node_count,
184            edge_count,
185            snapshot_hash,
186            provenance,
187        );
188        manifest.save(storage.manifest_path()).unwrap();
189
190        let snapshot = graph.snapshot();
191        let edges = snapshot.edges();
192        let forward_store = edges.forward();
193        let compaction_snapshot = snapshot_edges(&forward_store, snapshot.nodes().len());
194        let analyses = GraphAnalyses::build_all(&compaction_snapshot).unwrap();
195
196        let manifest_hash =
197            crate::graph::unified::analysis::compute_manifest_hash(storage.manifest_path())
198                .unwrap();
199        let node_id_hash = crate::graph::unified::analysis::compute_node_id_hash(&snapshot);
200        let identity = AnalysisIdentity::new(manifest_hash, node_id_hash);
201        analyses.persist_all(&storage, &identity).unwrap();
202
203        let cache = AnalysisCache::load(root).unwrap();
204        assert!(cache.get_csr().is_ok());
205        assert!(cache.get_scc(&kind).is_ok());
206        assert!(cache.get_condensation(&kind).is_ok());
207    }
208}