1use crate::dictionary::Term;
8use crate::error::Result;
9use dashmap::DashMap;
10use std::collections::VecDeque;
11use std::hash::{Hash, Hasher};
12use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
13use std::sync::Arc;
14use std::time::{Duration, Instant};
15
16#[derive(Debug, Clone)]
18pub struct QueryCacheConfig {
19 pub max_entries: usize,
21 pub ttl: Duration,
23 pub enabled: bool,
25 pub max_result_size: usize,
27}
28
29impl Default for QueryCacheConfig {
30 fn default() -> Self {
31 Self {
32 max_entries: 1000,
33 ttl: Duration::from_secs(300), enabled: true,
35 max_result_size: 10000, }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub struct QueryPattern {
43 pub subject: Option<String>,
45 pub predicate: Option<String>,
47 pub object: Option<String>,
49}
50
51impl QueryPattern {
52 pub fn new(subject: Option<&Term>, predicate: Option<&Term>, object: Option<&Term>) -> Self {
54 Self {
55 subject: subject.map(|t| format!("{:?}", t)),
56 predicate: predicate.map(|t| format!("{:?}", t)),
57 object: object.map(|t| format!("{:?}", t)),
58 }
59 }
60
61 pub fn is_cacheable(&self) -> bool {
64 self.subject.is_some() || self.predicate.is_some() || self.object.is_some()
65 }
66}
67
68#[derive(Debug, Clone)]
70struct CachedResult {
71 results: Vec<(Term, Term, Term)>,
73 cached_at: Instant,
75 access_count: u64,
77 last_accessed: Instant,
79}
80
81impl CachedResult {
82 fn new(results: Vec<(Term, Term, Term)>) -> Self {
83 let now = Instant::now();
84 Self {
85 results,
86 cached_at: now,
87 access_count: 0,
88 last_accessed: now,
89 }
90 }
91
92 fn is_expired(&self, ttl: Duration) -> bool {
93 self.cached_at.elapsed() > ttl
94 }
95
96 fn access(&mut self) {
97 self.access_count += 1;
98 self.last_accessed = Instant::now();
99 }
100}
101
102#[derive(Debug, Clone)]
104struct LruEntry {
105 pattern: QueryPattern,
106 last_accessed: Instant,
107}
108
109pub struct QueryCache {
111 config: QueryCacheConfig,
113 cache: Arc<DashMap<QueryPattern, CachedResult>>,
115 lru_queue: parking_lot::Mutex<VecDeque<LruEntry>>,
117 stats: QueryCacheStats,
119}
120
121#[derive(Debug, Default)]
123pub struct QueryCacheStats {
124 pub hits: AtomicU64,
126 pub misses: AtomicU64,
128 pub evictions: AtomicU64,
130 pub invalidations: AtomicU64,
132 pub current_size: AtomicUsize,
134}
135
136impl QueryCacheStats {
137 pub fn hit_rate(&self) -> f64 {
139 let hits = self.hits.load(Ordering::Relaxed) as f64;
140 let misses = self.misses.load(Ordering::Relaxed) as f64;
141 let total = hits + misses;
142
143 if total == 0.0 {
144 0.0
145 } else {
146 hits / total
147 }
148 }
149
150 pub fn total_requests(&self) -> u64 {
152 self.hits.load(Ordering::Relaxed) + self.misses.load(Ordering::Relaxed)
153 }
154}
155
156impl QueryCache {
157 pub fn new(config: QueryCacheConfig) -> Self {
159 Self {
160 config,
161 cache: Arc::new(DashMap::new()),
162 lru_queue: parking_lot::Mutex::new(VecDeque::new()),
163 stats: QueryCacheStats::default(),
164 }
165 }
166
167 pub fn get(&self, pattern: &QueryPattern) -> Option<Vec<(Term, Term, Term)>> {
169 if !self.config.enabled || !pattern.is_cacheable() {
170 return None;
171 }
172
173 if let Some(mut entry) = self.cache.get_mut(pattern) {
175 if entry.is_expired(self.config.ttl) {
177 drop(entry);
179 self.cache.remove(pattern);
180 self.stats.misses.fetch_add(1, Ordering::Relaxed);
181 self.stats.current_size.fetch_sub(1, Ordering::Relaxed);
182 return None;
183 }
184
185 entry.access();
187 self.stats.hits.fetch_add(1, Ordering::Relaxed);
188
189 self.update_lru(pattern);
191
192 return Some(entry.results.clone());
193 }
194
195 self.stats.misses.fetch_add(1, Ordering::Relaxed);
197 None
198 }
199
200 pub fn put(&self, pattern: QueryPattern, results: Vec<(Term, Term, Term)>) -> Result<()> {
202 if !self.config.enabled || !pattern.is_cacheable() {
203 return Ok(());
204 }
205
206 if results.len() > self.config.max_result_size {
208 return Ok(());
209 }
210
211 while self.cache.len() >= self.config.max_entries {
213 self.evict_lru()?;
214 }
215
216 let entry = CachedResult::new(results);
218 self.cache.insert(pattern.clone(), entry);
219 self.stats.current_size.fetch_add(1, Ordering::Relaxed);
220
221 let mut lru = self.lru_queue.lock();
223 lru.push_back(LruEntry {
224 pattern,
225 last_accessed: Instant::now(),
226 });
227
228 Ok(())
229 }
230
231 pub fn invalidate_all(&self) {
235 let count = self.cache.len();
236 self.cache.clear();
237 self.lru_queue.lock().clear();
238
239 self.stats.current_size.store(0, Ordering::Relaxed);
240 self.stats
241 .invalidations
242 .fetch_add(count as u64, Ordering::Relaxed);
243 }
244
245 pub fn invalidate_pattern(
250 &self,
251 subject: Option<&str>,
252 predicate: Option<&str>,
253 object: Option<&str>,
254 ) {
255 let mut invalidated = 0;
256
257 self.cache.retain(|pattern, _| {
259 let should_keep = !self.pattern_overlaps(pattern, subject, predicate, object);
260 if !should_keep {
261 invalidated += 1;
262 }
263 should_keep
264 });
265
266 self.stats
268 .current_size
269 .fetch_sub(invalidated, Ordering::Relaxed);
270 self.stats
271 .invalidations
272 .fetch_add(invalidated as u64, Ordering::Relaxed);
273
274 let mut lru = self.lru_queue.lock();
276 lru.retain(|entry| self.cache.contains_key(&entry.pattern));
277 }
278
279 fn pattern_overlaps(
281 &self,
282 cached: &QueryPattern,
283 subject: Option<&str>,
284 predicate: Option<&str>,
285 object: Option<&str>,
286 ) -> bool {
287 let s_overlaps = match (&cached.subject, subject) {
289 (Some(cs), Some(s)) => cs == s,
290 _ => true, };
292
293 let p_overlaps = match (&cached.predicate, predicate) {
294 (Some(cp), Some(p)) => cp == p,
295 _ => true,
296 };
297
298 let o_overlaps = match (&cached.object, object) {
299 (Some(co), Some(o)) => co == o,
300 _ => true,
301 };
302
303 s_overlaps && p_overlaps && o_overlaps
304 }
305
306 fn evict_lru(&self) -> Result<()> {
308 let mut lru = self.lru_queue.lock();
309
310 while let Some(entry) = lru.pop_front() {
312 if self.cache.remove(&entry.pattern).is_some() {
314 self.stats.evictions.fetch_add(1, Ordering::Relaxed);
315 self.stats.current_size.fetch_sub(1, Ordering::Relaxed);
316 return Ok(());
317 }
318 }
319
320 Ok(())
321 }
322
323 fn update_lru(&self, pattern: &QueryPattern) {
325 let mut lru = self.lru_queue.lock();
326
327 if let Some(pos) = lru.iter().position(|e| &e.pattern == pattern) {
329 if let Some(mut entry) = lru.remove(pos) {
330 entry.last_accessed = Instant::now();
331 lru.push_back(entry);
332 }
333 }
334 }
335
336 pub fn stats(&self) -> &QueryCacheStats {
338 &self.stats
339 }
340
341 pub fn max_entries(&self) -> usize {
345 self.config.max_entries
346 }
347
348 pub fn clear(&self) {
350 self.cache.clear();
351 self.lru_queue.lock().clear();
352 self.stats.current_size.store(0, Ordering::Relaxed);
353 }
354
355 pub fn len(&self) -> usize {
357 self.cache.len()
358 }
359
360 pub fn is_empty(&self) -> bool {
362 self.cache.is_empty()
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369 use crate::dictionary::Term;
370
371 fn create_test_pattern(s: Option<&str>, p: Option<&str>, o: Option<&str>) -> QueryPattern {
372 QueryPattern {
373 subject: s.map(String::from),
374 predicate: p.map(String::from),
375 object: o.map(String::from),
376 }
377 }
378
379 fn create_test_results(count: usize) -> Vec<(Term, Term, Term)> {
380 (0..count)
381 .map(|i| {
382 (
383 Term::Iri(format!("http://example.org/s{}", i)),
384 Term::Iri("http://example.org/knows".to_string()),
385 Term::Iri(format!("http://example.org/o{}", i)),
386 )
387 })
388 .collect()
389 }
390
391 #[test]
392 fn test_query_cache_creation() {
393 let config = QueryCacheConfig::default();
394 let cache = QueryCache::new(config);
395
396 assert_eq!(cache.len(), 0);
397 assert!(cache.is_empty());
398 }
399
400 #[test]
401 fn test_cache_put_and_get() {
402 let cache = QueryCache::new(QueryCacheConfig::default());
403 let pattern = create_test_pattern(Some("s1"), None, None);
404 let results = create_test_results(5);
405
406 cache.put(pattern.clone(), results.clone()).unwrap();
407
408 let cached = cache.get(&pattern);
409 assert!(cached.is_some());
410 assert_eq!(cached.unwrap().len(), 5);
411 }
412
413 #[test]
414 fn test_cache_miss() {
415 let cache = QueryCache::new(QueryCacheConfig::default());
416 let pattern = create_test_pattern(Some("s1"), None, None);
417
418 let cached = cache.get(&pattern);
419 assert!(cached.is_none());
420
421 let stats = cache.stats();
422 assert_eq!(stats.misses.load(Ordering::Relaxed), 1);
423 }
424
425 #[test]
426 fn test_cache_hit() {
427 let cache = QueryCache::new(QueryCacheConfig::default());
428 let pattern = create_test_pattern(Some("s1"), None, None);
429 let results = create_test_results(5);
430
431 cache.put(pattern.clone(), results).unwrap();
432 cache.get(&pattern);
433
434 let stats = cache.stats();
435 assert_eq!(stats.hits.load(Ordering::Relaxed), 1);
436 assert!(stats.hit_rate() > 0.0);
437 }
438
439 #[test]
440 fn test_cache_expiration() {
441 let config = QueryCacheConfig {
442 ttl: Duration::from_millis(10),
443 ..Default::default()
444 };
445 let cache = QueryCache::new(config);
446 let pattern = create_test_pattern(Some("s1"), None, None);
447 let results = create_test_results(5);
448
449 cache.put(pattern.clone(), results).unwrap();
450
451 std::thread::sleep(Duration::from_millis(20));
453
454 let cached = cache.get(&pattern);
455 assert!(cached.is_none()); }
457
458 #[test]
459 fn test_lru_eviction() {
460 let config = QueryCacheConfig {
461 max_entries: 3,
462 ..Default::default()
463 };
464 let cache = QueryCache::new(config);
465
466 for i in 0..3 {
468 let pattern = create_test_pattern(Some(&format!("s{}", i)), None, None);
469 cache.put(pattern, create_test_results(1)).unwrap();
470 }
471
472 assert_eq!(cache.len(), 3);
473
474 let pattern4 = create_test_pattern(Some("s4"), None, None);
476 cache.put(pattern4, create_test_results(1)).unwrap();
477
478 assert_eq!(cache.len(), 3); let stats = cache.stats();
481 assert_eq!(stats.evictions.load(Ordering::Relaxed), 1);
482 }
483
484 #[test]
485 fn test_invalidate_all() {
486 let cache = QueryCache::new(QueryCacheConfig::default());
487
488 for i in 0..5 {
489 let pattern = create_test_pattern(Some(&format!("s{}", i)), None, None);
490 cache.put(pattern, create_test_results(1)).unwrap();
491 }
492
493 assert_eq!(cache.len(), 5);
494
495 cache.invalidate_all();
496
497 assert_eq!(cache.len(), 0);
498 let stats = cache.stats();
499 assert_eq!(stats.invalidations.load(Ordering::Relaxed), 5);
500 }
501
502 #[test]
503 fn test_invalidate_pattern() {
504 let cache = QueryCache::new(QueryCacheConfig::default());
505
506 cache
508 .put(
509 create_test_pattern(Some("s1"), Some("p1"), None),
510 create_test_results(1),
511 )
512 .unwrap();
513 cache
514 .put(
515 create_test_pattern(Some("s2"), Some("p1"), None),
516 create_test_results(1),
517 )
518 .unwrap();
519 cache
520 .put(
521 create_test_pattern(Some("s3"), Some("p2"), None),
522 create_test_results(1),
523 )
524 .unwrap();
525
526 assert_eq!(cache.len(), 3);
527
528 cache.invalidate_pattern(None, Some("p1"), None);
530
531 assert_eq!(cache.len(), 1); }
533
534 #[test]
535 fn test_max_result_size() {
536 let config = QueryCacheConfig {
537 max_result_size: 10,
538 ..Default::default()
539 };
540 let cache = QueryCache::new(config);
541
542 let pattern = create_test_pattern(Some("s1"), None, None);
543 let large_results = create_test_results(100); cache.put(pattern.clone(), large_results).unwrap();
546
547 assert_eq!(cache.len(), 0);
549 }
550
551 #[test]
552 fn test_pattern_is_cacheable() {
553 let pattern1 = create_test_pattern(None, None, None);
555 assert!(!pattern1.is_cacheable());
556
557 let pattern2 = create_test_pattern(Some("s1"), None, None);
559 assert!(pattern2.is_cacheable());
560
561 let pattern3 = create_test_pattern(None, Some("p1"), None);
562 assert!(pattern3.is_cacheable());
563
564 let pattern4 = create_test_pattern(None, None, Some("o1"));
565 assert!(pattern4.is_cacheable());
566 }
567
568 #[test]
569 fn test_cache_disabled() {
570 let config = QueryCacheConfig {
571 enabled: false,
572 ..Default::default()
573 };
574 let cache = QueryCache::new(config);
575
576 let pattern = create_test_pattern(Some("s1"), None, None);
577 let results = create_test_results(5);
578
579 cache.put(pattern.clone(), results).unwrap();
580
581 assert_eq!(cache.len(), 0);
583
584 let cached = cache.get(&pattern);
585 assert!(cached.is_none());
586 }
587
588 #[test]
589 fn test_hit_rate_calculation() {
590 let cache = QueryCache::new(QueryCacheConfig::default());
591 let pattern = create_test_pattern(Some("s1"), None, None);
592 let results = create_test_results(5);
593
594 cache.put(pattern.clone(), results).unwrap();
595
596 cache.get(&pattern);
598 cache.get(&pattern);
599 cache.get(&pattern);
600
601 cache.get(&create_test_pattern(Some("s2"), None, None));
603 cache.get(&create_test_pattern(Some("s3"), None, None));
604
605 let stats = cache.stats();
606 assert_eq!(stats.total_requests(), 5);
607 assert_eq!(stats.hit_rate(), 0.6); }
609
610 #[test]
611 fn test_clear() {
612 let cache = QueryCache::new(QueryCacheConfig::default());
613
614 for i in 0..5 {
615 let pattern = create_test_pattern(Some(&format!("s{}", i)), None, None);
616 cache.put(pattern, create_test_results(1)).unwrap();
617 }
618
619 assert_eq!(cache.len(), 5);
620
621 cache.clear();
622
623 assert_eq!(cache.len(), 0);
624 assert_eq!(cache.stats().current_size.load(Ordering::Relaxed), 0);
625 }
626}