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 { enabled: false, ..Default::default() },
63 l3: L3Config { enabled: false, ..Default::default() },
64 ..Default::default()
65 }
66 }
67
68 pub fn high_throughput() -> Self {
70 Self {
71 enabled: true,
72 l1: L1Config {
73 size: 2000,
74 ttl: Duration::from_secs(60),
75 ..Default::default()
76 },
77 l2: L2Config {
78 size_mb: 1024,
79 ttl: Duration::from_secs(600),
80 ..Default::default()
81 },
82 l3: L3Config { enabled: false, ..Default::default() },
83 default_ttl: Duration::from_secs(300),
84 ..Default::default()
85 }
86 }
87
88 pub fn ai_workload() -> Self {
90 Self {
91 enabled: true,
92 l1: L1Config::default(),
93 l2: L2Config::default(),
94 l3: L3Config {
95 enabled: true,
96 similarity_threshold: 0.90,
97 max_entries: 10000,
98 ..Default::default()
99 },
100 ..Default::default()
101 }
102 }
103
104 pub fn validate(&self) -> Result<(), String> {
106 if self.l1.size == 0 && self.l1.enabled {
107 return Err("L1 cache size cannot be 0 when enabled".to_string());
108 }
109
110 if self.l2.size_mb == 0 && self.l2.enabled {
111 return Err("L2 cache size cannot be 0 when enabled".to_string());
112 }
113
114 if self.l3.similarity_threshold < 0.0 || self.l3.similarity_threshold > 1.0 {
115 return Err("L3 similarity threshold must be between 0.0 and 1.0".to_string());
116 }
117
118 Ok(())
119 }
120}
121
122#[derive(Debug, Clone)]
124pub struct L1Config {
125 pub enabled: bool,
127
128 pub size: usize,
130
131 pub ttl: Duration,
133}
134
135impl Default for L1Config {
136 fn default() -> Self {
137 Self {
138 enabled: true,
139 size: 500,
140 ttl: Duration::from_secs(30),
141 }
142 }
143}
144
145#[derive(Debug, Clone)]
147pub struct L2Config {
148 pub enabled: bool,
150
151 pub size_mb: usize,
153
154 pub ttl: Duration,
156
157 pub normalize_queries: bool,
159
160 pub storage: StorageBackend,
162
163 pub mmap_path: Option<PathBuf>,
165}
166
167impl Default for L2Config {
168 fn default() -> Self {
169 Self {
170 enabled: true,
171 size_mb: 256,
172 ttl: Duration::from_secs(300),
173 normalize_queries: true,
174 storage: StorageBackend::Memory,
175 mmap_path: None,
176 }
177 }
178}
179
180#[derive(Debug, Clone, PartialEq, Eq)]
182pub enum StorageBackend {
183 Memory,
185 Mmap,
187}
188
189impl Default for StorageBackend {
190 fn default() -> Self {
191 Self::Memory
192 }
193}
194
195#[derive(Debug, Clone)]
197pub struct L3Config {
198 pub enabled: bool,
200
201 pub similarity_threshold: f32,
203
204 pub max_entries: usize,
206
207 pub ttl: Duration,
209
210 pub embedding_endpoint: String,
212
213 pub embedding_model: String,
215
216 pub embedding_dim: usize,
218}
219
220impl Default for L3Config {
221 fn default() -> Self {
222 Self {
223 enabled: false, similarity_threshold: 0.92,
225 max_entries: 5000,
226 ttl: Duration::from_secs(3600),
227 embedding_endpoint: "http://localhost:11434".to_string(),
228 embedding_model: "all-minilm".to_string(),
229 embedding_dim: 384, }
231 }
232}
233
234#[derive(Debug, Clone)]
236pub struct InvalidationConfig {
237 pub mode: super::InvalidationMode,
239
240 pub wal_subscribe: bool,
242
243 pub ttl_fallback: Duration,
245}
246
247impl Default for InvalidationConfig {
248 fn default() -> Self {
249 Self {
250 mode: super::InvalidationMode::Wal,
251 wal_subscribe: true,
252 ttl_fallback: Duration::from_secs(60),
253 }
254 }
255}
256
257#[derive(Debug, Clone)]
259pub struct TableCacheConfig {
260 pub ttl: Duration,
262
263 pub exclude: bool,
265
266 pub exclude_columns: Vec<String>,
268}
269
270impl Default for TableCacheConfig {
271 fn default() -> Self {
272 Self {
273 ttl: Duration::from_secs(300),
274 exclude: false,
275 exclude_columns: Vec::new(),
276 }
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_default_config() {
286 let config = CacheConfig::default();
287 assert!(config.enabled);
288 assert!(config.l1.enabled);
289 assert!(config.l2.enabled);
290 assert!(!config.l3.enabled); assert_eq!(config.l1.size, 500);
292 assert_eq!(config.l2.size_mb, 256);
293 }
294
295 #[test]
296 fn test_minimal_config() {
297 let config = CacheConfig::minimal();
298 assert!(config.l1.enabled);
299 assert!(!config.l2.enabled);
300 assert!(!config.l3.enabled);
301 }
302
303 #[test]
304 fn test_high_throughput_config() {
305 let config = CacheConfig::high_throughput();
306 assert_eq!(config.l1.size, 2000);
307 assert_eq!(config.l2.size_mb, 1024);
308 assert!(!config.l3.enabled);
309 }
310
311 #[test]
312 fn test_ai_workload_config() {
313 let config = CacheConfig::ai_workload();
314 assert!(config.l3.enabled);
315 assert_eq!(config.l3.similarity_threshold, 0.90);
316 }
317
318 #[test]
319 fn test_validation() {
320 let config = CacheConfig::default();
321 assert!(config.validate().is_ok());
322
323 let mut invalid = CacheConfig::default();
324 invalid.l1.size = 0;
325 assert!(invalid.validate().is_err());
326
327 let mut invalid2 = CacheConfig::default();
328 invalid2.l3.similarity_threshold = 1.5;
329 assert!(invalid2.validate().is_err());
330 }
331
332 #[test]
333 fn test_storage_backend() {
334 assert_eq!(StorageBackend::default(), StorageBackend::Memory);
335 }
336}