1use std::collections::HashMap;
6use std::path::PathBuf;
7use std::time::Duration;
8
9#[derive(Debug, Clone)]
11pub struct CacheConfig {
12 pub enabled: bool,
14
15 pub l1: L1Config,
17
18 pub l2: L2Config,
20
21 pub l3: L3Config,
23
24 pub invalidation: InvalidationConfig,
26
27 pub default_ttl: Duration,
29
30 pub max_result_size: usize,
32
33 pub table_configs: HashMap<String, TableCacheConfig>,
35
36 pub excluded_tables: Vec<String>,
38}
39
40impl Default for CacheConfig {
41 fn default() -> Self {
42 Self {
43 enabled: true,
44 l1: L1Config::default(),
45 l2: L2Config::default(),
46 l3: L3Config::default(),
47 invalidation: InvalidationConfig::default(),
48 default_ttl: Duration::from_secs(300), max_result_size: 10 * 1024 * 1024, table_configs: HashMap::new(),
51 excluded_tables: Vec::new(),
52 }
53 }
54}
55
56impl CacheConfig {
57 pub fn minimal() -> Self {
59 Self {
60 enabled: true,
61 l1: L1Config::default(),
62 l2: L2Config {
63 enabled: false,
64 ..Default::default()
65 },
66 l3: L3Config {
67 enabled: false,
68 ..Default::default()
69 },
70 ..Default::default()
71 }
72 }
73
74 pub fn high_throughput() -> Self {
76 Self {
77 enabled: true,
78 l1: L1Config {
79 size: 2000,
80 ttl: Duration::from_secs(60),
81 ..Default::default()
82 },
83 l2: L2Config {
84 size_mb: 1024,
85 ttl: Duration::from_secs(600),
86 ..Default::default()
87 },
88 l3: L3Config {
89 enabled: false,
90 ..Default::default()
91 },
92 default_ttl: Duration::from_secs(300),
93 ..Default::default()
94 }
95 }
96
97 pub fn ai_workload() -> Self {
99 Self {
100 enabled: true,
101 l1: L1Config::default(),
102 l2: L2Config::default(),
103 l3: L3Config {
104 enabled: true,
105 similarity_threshold: 0.90,
106 max_entries: 10000,
107 ..Default::default()
108 },
109 ..Default::default()
110 }
111 }
112
113 pub fn validate(&self) -> Result<(), String> {
115 if self.l1.size == 0 && self.l1.enabled {
116 return Err("L1 cache size cannot be 0 when enabled".to_string());
117 }
118
119 if self.l2.size_mb == 0 && self.l2.enabled {
120 return Err("L2 cache size cannot be 0 when enabled".to_string());
121 }
122
123 if self.l3.similarity_threshold < 0.0 || self.l3.similarity_threshold > 1.0 {
124 return Err("L3 similarity threshold must be between 0.0 and 1.0".to_string());
125 }
126
127 Ok(())
128 }
129}
130
131#[derive(Debug, Clone)]
133pub struct L1Config {
134 pub enabled: bool,
136
137 pub size: usize,
139
140 pub ttl: Duration,
142}
143
144impl Default for L1Config {
145 fn default() -> Self {
146 Self {
147 enabled: true,
148 size: 500,
149 ttl: Duration::from_secs(30),
150 }
151 }
152}
153
154#[derive(Debug, Clone)]
156pub struct L2Config {
157 pub enabled: bool,
159
160 pub size_mb: usize,
162
163 pub ttl: Duration,
165
166 pub normalize_queries: bool,
168
169 pub storage: StorageBackend,
171
172 pub mmap_path: Option<PathBuf>,
174}
175
176impl Default for L2Config {
177 fn default() -> Self {
178 Self {
179 enabled: true,
180 size_mb: 256,
181 ttl: Duration::from_secs(300),
182 normalize_queries: true,
183 storage: StorageBackend::Memory,
184 mmap_path: None,
185 }
186 }
187}
188
189#[derive(Debug, Clone, PartialEq, Eq, Default)]
191pub enum StorageBackend {
192 #[default]
194 Memory,
195 Mmap,
197}
198
199#[derive(Debug, Clone)]
201pub struct L3Config {
202 pub enabled: bool,
204
205 pub similarity_threshold: f32,
207
208 pub max_entries: usize,
210
211 pub ttl: Duration,
213
214 pub embedding_endpoint: String,
216
217 pub embedding_model: String,
219
220 pub embedding_dim: usize,
222}
223
224impl Default for L3Config {
225 fn default() -> Self {
226 Self {
227 enabled: false, similarity_threshold: 0.92,
229 max_entries: 5000,
230 ttl: Duration::from_secs(3600),
231 embedding_endpoint: "http://localhost:11434".to_string(),
232 embedding_model: "all-minilm".to_string(),
233 embedding_dim: 384, }
235 }
236}
237
238#[derive(Debug, Clone)]
240pub struct InvalidationConfig {
241 pub mode: super::InvalidationMode,
243
244 pub wal_subscribe: bool,
246
247 pub ttl_fallback: Duration,
249}
250
251impl Default for InvalidationConfig {
252 fn default() -> Self {
253 Self {
254 mode: super::InvalidationMode::Wal,
255 wal_subscribe: true,
256 ttl_fallback: Duration::from_secs(60),
257 }
258 }
259}
260
261#[derive(Debug, Clone)]
263pub struct TableCacheConfig {
264 pub ttl: Duration,
266
267 pub exclude: bool,
269
270 pub exclude_columns: Vec<String>,
272}
273
274impl Default for TableCacheConfig {
275 fn default() -> Self {
276 Self {
277 ttl: Duration::from_secs(300),
278 exclude: false,
279 exclude_columns: Vec::new(),
280 }
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn test_default_config() {
290 let config = CacheConfig::default();
291 assert!(config.enabled);
292 assert!(config.l1.enabled);
293 assert!(config.l2.enabled);
294 assert!(!config.l3.enabled); assert_eq!(config.l1.size, 500);
296 assert_eq!(config.l2.size_mb, 256);
297 }
298
299 #[test]
300 fn test_minimal_config() {
301 let config = CacheConfig::minimal();
302 assert!(config.l1.enabled);
303 assert!(!config.l2.enabled);
304 assert!(!config.l3.enabled);
305 }
306
307 #[test]
308 fn test_high_throughput_config() {
309 let config = CacheConfig::high_throughput();
310 assert_eq!(config.l1.size, 2000);
311 assert_eq!(config.l2.size_mb, 1024);
312 assert!(!config.l3.enabled);
313 }
314
315 #[test]
316 fn test_ai_workload_config() {
317 let config = CacheConfig::ai_workload();
318 assert!(config.l3.enabled);
319 assert_eq!(config.l3.similarity_threshold, 0.90);
320 }
321
322 #[test]
323 fn test_validation() {
324 let config = CacheConfig::default();
325 assert!(config.validate().is_ok());
326
327 let mut invalid = CacheConfig::default();
328 invalid.l1.size = 0;
329 assert!(invalid.validate().is_err());
330
331 let mut invalid2 = CacheConfig::default();
332 invalid2.l3.similarity_threshold = 1.5;
333 assert!(invalid2.validate().is_err());
334 }
335
336 #[test]
337 fn test_storage_backend() {
338 assert_eq!(StorageBackend::default(), StorageBackend::Memory);
339 }
340}