codeprism_storage/
lib.rs

1//! Storage layer for CodePrism code intelligence
2//!
3//! This module provides persistent storage for code graphs, analysis results,
4//! and cached data to improve performance and enable incremental analysis.
5
6use anyhow::Result;
7use async_trait::async_trait;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::time::{Duration, SystemTime};
11
12pub mod backends;
13pub mod cache;
14pub mod config;
15pub mod graph;
16pub mod serialization;
17
18pub use backends::*;
19pub use cache::*;
20pub use config::*;
21pub use graph::*;
22
23/// Core storage trait for code graphs
24#[async_trait]
25pub trait GraphStorage: Send + Sync {
26    /// Store a complete code graph
27    async fn store_graph(&self, graph: &SerializableGraph) -> Result<()>;
28
29    /// Load a code graph by repository ID
30    async fn load_graph(&self, repo_id: &str) -> Result<Option<SerializableGraph>>;
31
32    /// Update specific nodes in the graph
33    async fn update_nodes(&self, repo_id: &str, nodes: &[SerializableNode]) -> Result<()>;
34
35    /// Update specific edges in the graph
36    async fn update_edges(&self, repo_id: &str, edges: &[SerializableEdge]) -> Result<()>;
37
38    /// Delete nodes by IDs
39    async fn delete_nodes(&self, repo_id: &str, node_ids: &[String]) -> Result<()>;
40
41    /// Delete edges by source and target
42    async fn delete_edges(&self, repo_id: &str, edge_refs: &[EdgeReference]) -> Result<()>;
43
44    /// Get graph metadata
45    async fn get_graph_metadata(&self, repo_id: &str) -> Result<Option<GraphMetadata>>;
46
47    /// Update graph metadata
48    async fn update_graph_metadata(&self, repo_id: &str, metadata: &GraphMetadata) -> Result<()>;
49
50    /// List all stored repositories
51    async fn list_repositories(&self) -> Result<Vec<String>>;
52
53    /// Delete entire graph for a repository
54    async fn delete_graph(&self, repo_id: &str) -> Result<()>;
55
56    /// Check if a graph exists
57    async fn graph_exists(&self, repo_id: &str) -> Result<bool>;
58}
59
60/// Cache storage trait for temporary data
61#[async_trait]
62pub trait CacheStorage: Send + Sync {
63    /// Get a cached value by key
64    async fn get<T>(&self, key: &str) -> Result<Option<T>>
65    where
66        T: for<'de> Deserialize<'de> + Send;
67
68    /// Set a cached value with optional TTL
69    async fn set<T>(&self, key: &str, value: &T, ttl: Option<Duration>) -> Result<()>
70    where
71        T: Serialize + Send + Sync;
72
73    /// Delete a cached value
74    async fn delete(&self, key: &str) -> Result<()>;
75
76    /// Invalidate all keys matching a pattern
77    async fn invalidate_pattern(&self, pattern: &str) -> Result<()>;
78
79    /// Get cache statistics
80    async fn get_stats(&self) -> Result<CacheStats>;
81
82    /// Clear all cached data
83    async fn clear(&self) -> Result<()>;
84}
85
86/// Analysis results storage trait
87#[async_trait]
88pub trait AnalysisStorage: Send + Sync {
89    /// Store analysis results
90    async fn store_analysis(&self, result: &AnalysisResult) -> Result<()>;
91
92    /// Load analysis results by ID
93    async fn load_analysis(&self, result_id: &str) -> Result<Option<AnalysisResult>>;
94
95    /// Find analysis results by repository and type
96    async fn find_analysis(
97        &self,
98        repo_id: &str,
99        analysis_type: Option<&str>,
100        since: Option<SystemTime>,
101    ) -> Result<Vec<AnalysisResult>>;
102
103    /// Delete analysis results
104    async fn delete_analysis(&self, result_id: &str) -> Result<()>;
105
106    /// Clean up old analysis results
107    async fn cleanup_old_results(&self, older_than: SystemTime) -> Result<usize>;
108}
109
110/// Edge reference for deletion operations
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct EdgeReference {
113    pub source: String,
114    pub target: String,
115    pub kind: String,
116}
117
118/// Cache statistics
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct CacheStats {
121    pub total_keys: usize,
122    pub memory_usage_bytes: usize,
123    pub hit_count: u64,
124    pub miss_count: u64,
125    pub eviction_count: u64,
126}
127
128/// Analysis result storage
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct AnalysisResult {
131    pub id: String,
132    pub repo_id: String,
133    pub analysis_type: String,
134    pub timestamp: SystemTime,
135    pub data: serde_json::Value,
136    pub metadata: HashMap<String, String>,
137}
138
139/// Storage backend type
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
141pub enum StorageBackend {
142    InMemory,
143    File,
144    Sqlite,
145}
146
147/// Main storage manager that coordinates different storage backends
148pub struct StorageManager {
149    graph_storage: Box<dyn GraphStorage>,
150    cache_storage: cache::LruCacheStorage,
151    analysis_storage: Box<dyn AnalysisStorage>,
152    config: StorageConfig,
153}
154
155impl StorageManager {
156    /// Create a new storage manager with the specified configuration
157    pub async fn new(config: StorageConfig) -> Result<Self> {
158        let graph_storage = create_graph_storage(&config).await?;
159        let cache_storage = cache::LruCacheStorage::new(config.cache_size_mb * 1024 * 1024);
160        let analysis_storage = create_analysis_storage(&config).await?;
161
162        Ok(Self {
163            graph_storage,
164            cache_storage,
165            analysis_storage,
166            config,
167        })
168    }
169
170    /// Get a reference to the graph storage
171    pub fn graph(&self) -> &dyn GraphStorage {
172        self.graph_storage.as_ref()
173    }
174
175    /// Get a reference to the cache storage
176    pub fn cache(&self) -> &cache::LruCacheStorage {
177        &self.cache_storage
178    }
179
180    /// Get a reference to the analysis storage
181    pub fn analysis(&self) -> &dyn AnalysisStorage {
182        self.analysis_storage.as_ref()
183    }
184
185    /// Get the storage configuration
186    pub fn config(&self) -> &StorageConfig {
187        &self.config
188    }
189
190    /// Perform maintenance operations (cleanup, optimization, etc.)
191    pub async fn maintenance(&self) -> Result<()> {
192        // Clean up old analysis results
193        let cutoff = SystemTime::now() - self.config.retention_period;
194        let deleted = self.analysis_storage.cleanup_old_results(cutoff).await?;
195        tracing::info!("Cleaned up {} old analysis results", deleted);
196
197        // Additional maintenance operations can be added here
198        Ok(())
199    }
200}
201
202/// Create appropriate graph storage backend
203async fn create_graph_storage(config: &StorageConfig) -> Result<Box<dyn GraphStorage>> {
204    match config.backend {
205        StorageBackend::InMemory => Ok(Box::new(backends::InMemoryGraphStorage::new())),
206        StorageBackend::File => Ok(Box::new(
207            backends::FileGraphStorage::new(&config.data_path).await?,
208        )),
209        StorageBackend::Sqlite => Ok(Box::new(
210            backends::SqliteGraphStorage::new(&config.data_path).await?,
211        )),
212    }
213}
214
215/// Create appropriate analysis storage backend
216async fn create_analysis_storage(config: &StorageConfig) -> Result<Box<dyn AnalysisStorage>> {
217    match config.backend {
218        StorageBackend::InMemory => Ok(Box::new(backends::InMemoryAnalysisStorage::new())),
219        _ => Ok(Box::new(
220            backends::FileAnalysisStorage::new(&config.data_path).await?,
221        )),
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use tempfile::tempdir;
229
230    #[tokio::test]
231    async fn test_storage_manager_creation() {
232        let temp_dir = tempdir().unwrap();
233        let config = StorageConfig {
234            backend: StorageBackend::InMemory,
235            data_path: temp_dir.path().to_path_buf(),
236            cache_size_mb: 64,
237            persistence_interval: Duration::from_secs(60),
238            compression_enabled: false,
239            retention_period: Duration::from_secs(86400),
240            connection_string: None,
241        };
242
243        let storage = StorageManager::new(config).await.unwrap();
244        assert_eq!(storage.config().backend, StorageBackend::InMemory);
245    }
246
247    #[tokio::test]
248    async fn test_cache_operations() {
249        let temp_dir = tempdir().unwrap();
250        let config = StorageConfig::default_for_testing(temp_dir.path());
251        let storage = StorageManager::new(config).await.unwrap();
252
253        // Test cache set/get
254        storage
255            .cache()
256            .set("test_key", &"test_value", None)
257            .await
258            .unwrap();
259        let value: Option<String> = storage.cache().get("test_key").await.unwrap();
260        assert_eq!(value, Some("test_value".to_string()));
261
262        // Test cache delete
263        storage.cache().delete("test_key").await.unwrap();
264        let value: Option<String> = storage.cache().get("test_key").await.unwrap();
265        assert_eq!(value, None);
266    }
267}