1use 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#[async_trait]
25pub trait GraphStorage: Send + Sync {
26 async fn store_graph(&self, graph: &SerializableGraph) -> Result<()>;
28
29 async fn load_graph(&self, repo_id: &str) -> Result<Option<SerializableGraph>>;
31
32 async fn update_nodes(&self, repo_id: &str, nodes: &[SerializableNode]) -> Result<()>;
34
35 async fn update_edges(&self, repo_id: &str, edges: &[SerializableEdge]) -> Result<()>;
37
38 async fn delete_nodes(&self, repo_id: &str, node_ids: &[String]) -> Result<()>;
40
41 async fn delete_edges(&self, repo_id: &str, edge_refs: &[EdgeReference]) -> Result<()>;
43
44 async fn get_graph_metadata(&self, repo_id: &str) -> Result<Option<GraphMetadata>>;
46
47 async fn update_graph_metadata(&self, repo_id: &str, metadata: &GraphMetadata) -> Result<()>;
49
50 async fn list_repositories(&self) -> Result<Vec<String>>;
52
53 async fn delete_graph(&self, repo_id: &str) -> Result<()>;
55
56 async fn graph_exists(&self, repo_id: &str) -> Result<bool>;
58}
59
60#[async_trait]
62pub trait CacheStorage: Send + Sync {
63 async fn get<T>(&self, key: &str) -> Result<Option<T>>
65 where
66 T: for<'de> Deserialize<'de> + Send;
67
68 async fn set<T>(&self, key: &str, value: &T, ttl: Option<Duration>) -> Result<()>
70 where
71 T: Serialize + Send + Sync;
72
73 async fn delete(&self, key: &str) -> Result<()>;
75
76 async fn invalidate_pattern(&self, pattern: &str) -> Result<()>;
78
79 async fn get_stats(&self) -> Result<CacheStats>;
81
82 async fn clear(&self) -> Result<()>;
84}
85
86#[async_trait]
88pub trait AnalysisStorage: Send + Sync {
89 async fn store_analysis(&self, result: &AnalysisResult) -> Result<()>;
91
92 async fn load_analysis(&self, result_id: &str) -> Result<Option<AnalysisResult>>;
94
95 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 async fn delete_analysis(&self, result_id: &str) -> Result<()>;
105
106 async fn cleanup_old_results(&self, older_than: SystemTime) -> Result<usize>;
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct EdgeReference {
113 pub source: String,
114 pub target: String,
115 pub kind: String,
116}
117
118#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
141pub enum StorageBackend {
142 InMemory,
143 File,
144 Sqlite,
145}
146
147pub 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 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 pub fn graph(&self) -> &dyn GraphStorage {
172 self.graph_storage.as_ref()
173 }
174
175 pub fn cache(&self) -> &cache::LruCacheStorage {
177 &self.cache_storage
178 }
179
180 pub fn analysis(&self) -> &dyn AnalysisStorage {
182 self.analysis_storage.as_ref()
183 }
184
185 pub fn config(&self) -> &StorageConfig {
187 &self.config
188 }
189
190 pub async fn maintenance(&self) -> Result<()> {
192 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 Ok(())
199 }
200}
201
202async 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
215async 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 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 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}