1use std::collections::HashMap;
11use std::hash::Hash;
12use std::sync::atomic::{AtomicU64, Ordering};
13use std::sync::{Arc, Mutex};
14use std::time::{Duration, Instant};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum CachePolicy {
23 Lru,
25 Lfu,
27 Fifo,
29 Ttl,
31}
32
33#[derive(Debug, Default)]
39pub struct CacheStats {
40 hits: AtomicU64,
41 misses: AtomicU64,
42 evictions: AtomicU64,
43 insertions: AtomicU64,
44}
45
46impl CacheStats {
47 pub fn new() -> Arc<Self> {
49 Arc::new(Self::default())
50 }
51
52 pub fn record_hit(&self) {
53 self.hits.fetch_add(1, Ordering::Relaxed);
54 }
55
56 pub fn record_miss(&self) {
57 self.misses.fetch_add(1, Ordering::Relaxed);
58 }
59
60 pub fn record_eviction(&self) {
61 self.evictions.fetch_add(1, Ordering::Relaxed);
62 }
63
64 pub fn record_insertion(&self) {
65 self.insertions.fetch_add(1, Ordering::Relaxed);
66 }
67
68 pub fn hits(&self) -> u64 {
69 self.hits.load(Ordering::Relaxed)
70 }
71
72 pub fn misses(&self) -> u64 {
73 self.misses.load(Ordering::Relaxed)
74 }
75
76 pub fn evictions(&self) -> u64 {
77 self.evictions.load(Ordering::Relaxed)
78 }
79
80 pub fn insertions(&self) -> u64 {
81 self.insertions.load(Ordering::Relaxed)
82 }
83
84 pub fn hit_rate(&self) -> f64 {
86 let h = self.hits.load(Ordering::Relaxed) as f64;
87 let m = self.misses.load(Ordering::Relaxed) as f64;
88 let total = h + m;
89 if total == 0.0 {
90 0.0
91 } else {
92 h / total
93 }
94 }
95
96 pub fn reset(&self) {
97 self.hits.store(0, Ordering::Relaxed);
98 self.misses.store(0, Ordering::Relaxed);
99 self.evictions.store(0, Ordering::Relaxed);
100 self.insertions.store(0, Ordering::Relaxed);
101 }
102}
103
104#[allow(dead_code)]
109struct Entry<V> {
110 value: V,
111 created_at: Instant,
112 expires_at: Option<Instant>,
113 last_access_seq: u64,
120 access_count: u64,
121 insertion_seq: u64,
122}
123
124impl<V: Clone> Entry<V> {
125 fn new(value: V, ttl: Option<Duration>, seq: u64) -> Self {
126 let now = Instant::now();
127 Self {
128 value,
129 created_at: now,
130 expires_at: ttl.map(|d| now + d),
131 last_access_seq: seq,
132 access_count: 0,
133 insertion_seq: seq,
134 }
135 }
136
137 fn is_expired(&self) -> bool {
138 self.expires_at
139 .map(|e| Instant::now() >= e)
140 .unwrap_or(false)
141 }
142
143 fn touch(&mut self, seq: u64) {
144 self.last_access_seq = seq;
145 self.access_count += 1;
146 }
147}
148
149struct TripleCacheInner<K, V> {
154 entries: HashMap<K, Entry<V>>,
155 policy: CachePolicy,
156 capacity: usize,
157 ttl: Option<Duration>,
158 seq_counter: u64,
159}
160
161impl<K: Eq + Hash + Clone, V: Clone> TripleCacheInner<K, V> {
162 fn new(capacity: usize, policy: CachePolicy, ttl: Option<Duration>) -> Self {
163 Self {
164 entries: HashMap::new(),
165 policy,
166 capacity,
167 ttl,
168 seq_counter: 0,
169 }
170 }
171
172 fn get_mut(&mut self, key: &K) -> Option<&V> {
173 let next_seq = self.seq_counter.wrapping_add(1);
179 if let Some(entry) = self.entries.get_mut(key) {
180 if entry.is_expired() {
181 return None;
182 }
183 self.seq_counter = next_seq;
184 entry.touch(next_seq);
185 Some(&self.entries[key].value)
187 } else {
188 None
189 }
190 }
191
192 fn insert(&mut self, key: K, value: V, stats: &CacheStats) {
193 self.entries.retain(|_, e| !e.is_expired());
195
196 if !self.entries.contains_key(&key) && self.entries.len() >= self.capacity {
198 if let Some(victim) = self.select_victim() {
199 self.entries.remove(&victim);
200 stats.record_eviction();
201 }
202 }
203
204 self.seq_counter = self.seq_counter.wrapping_add(1);
205 let entry = Entry::new(value, self.ttl, self.seq_counter);
206 self.entries.insert(key, entry);
210 stats.record_insertion();
211 }
212
213 fn select_victim(&self) -> Option<K> {
214 match self.policy {
215 CachePolicy::Lru | CachePolicy::Ttl => self
216 .entries
217 .iter()
218 .min_by_key(|(_, e)| e.last_access_seq)
219 .map(|(k, _)| k.clone()),
220 CachePolicy::Lfu => self
221 .entries
222 .iter()
223 .min_by_key(|(_, e)| e.access_count)
224 .map(|(k, _)| k.clone()),
225 CachePolicy::Fifo => self
226 .entries
227 .iter()
228 .min_by_key(|(_, e)| e.insertion_seq)
229 .map(|(k, _)| k.clone()),
230 }
231 }
232
233 fn remove(&mut self, key: &K) -> bool {
234 self.entries.remove(key).is_some()
235 }
236
237 fn len(&self) -> usize {
238 self.entries.iter().filter(|(_, e)| !e.is_expired()).count()
239 }
240
241 fn clear(&mut self) {
242 self.entries.clear();
243 }
244
245 fn contains_key(&self, key: &K) -> bool {
246 self.entries
247 .get(key)
248 .map(|e| !e.is_expired())
249 .unwrap_or(false)
250 }
251
252 fn keys(&self) -> Vec<K> {
253 self.entries
254 .iter()
255 .filter(|(_, e)| !e.is_expired())
256 .map(|(k, _)| k.clone())
257 .collect()
258 }
259}
260
261pub struct TripleCache<K, V> {
269 inner: Mutex<TripleCacheInner<K, V>>,
270 stats: Arc<CacheStats>,
271}
272
273impl<K: Eq + Hash + Clone, V: Clone> TripleCache<K, V> {
274 pub fn new(capacity: usize, policy: CachePolicy, ttl: Option<Duration>) -> Self {
276 Self {
277 inner: Mutex::new(TripleCacheInner::new(capacity, policy, ttl)),
278 stats: CacheStats::new(),
279 }
280 }
281
282 pub fn lru(capacity: usize) -> Self {
284 Self::new(capacity, CachePolicy::Lru, None)
285 }
286
287 pub fn lru_ttl(capacity: usize, ttl: Duration) -> Self {
289 Self::new(capacity, CachePolicy::Ttl, Some(ttl))
290 }
291
292 pub fn get(&self, key: &K) -> Option<V> {
294 let mut inner = self.inner.lock().expect("TripleCache lock poisoned");
295 let result = inner.get_mut(key).cloned();
296 if result.is_some() {
297 self.stats.record_hit();
298 } else {
299 self.stats.record_miss();
300 }
301 result
302 }
303
304 pub fn put(&self, key: K, value: V) {
306 let mut inner = self.inner.lock().expect("TripleCache lock poisoned");
307 inner.insert(key, value, &self.stats);
308 }
309
310 pub fn remove(&self, key: &K) -> bool {
312 let mut inner = self.inner.lock().expect("TripleCache lock poisoned");
313 inner.remove(key)
314 }
315
316 pub fn contains(&self, key: &K) -> bool {
318 let inner = self.inner.lock().expect("TripleCache lock poisoned");
319 inner.contains_key(key)
320 }
321
322 pub fn len(&self) -> usize {
324 let inner = self.inner.lock().expect("TripleCache lock poisoned");
325 inner.len()
326 }
327
328 pub fn is_empty(&self) -> bool {
329 self.len() == 0
330 }
331
332 pub fn clear(&self) {
334 let mut inner = self.inner.lock().expect("TripleCache lock poisoned");
335 inner.clear();
336 }
337
338 pub fn keys(&self) -> Vec<K> {
340 let inner = self.inner.lock().expect("TripleCache lock poisoned");
341 inner.keys()
342 }
343
344 pub fn stats(&self) -> Arc<CacheStats> {
346 Arc::clone(&self.stats)
347 }
348}
349
350pub type SparqlRow = HashMap<String, String>;
356
357#[derive(Debug, Clone)]
359pub struct QueryCacheEntry {
360 pub query_hash: u64,
362 pub rows: Vec<SparqlRow>,
364 pub variables: Vec<String>,
366 pub accessed_predicates: Vec<String>,
368 pub created_at: Instant,
370}
371
372pub struct QueryResultCache {
376 cache: TripleCache<(String, u64), QueryCacheEntry>,
377}
378
379impl QueryResultCache {
380 pub fn new(capacity: usize, ttl: Duration) -> Self {
382 Self {
383 cache: TripleCache::new(capacity, CachePolicy::Ttl, Some(ttl)),
384 }
385 }
386
387 fn hash_query(query: &str) -> u64 {
389 const FNV_OFFSET: u64 = 14_695_981_039_346_656_037;
390 const FNV_PRIME: u64 = 1_099_511_628_211;
391 let mut h = FNV_OFFSET;
392 for b in query.bytes() {
393 h ^= b as u64;
394 h = h.wrapping_mul(FNV_PRIME);
395 }
396 h
397 }
398
399 pub fn put(
401 &self,
402 dataset_id: &str,
403 query: &str,
404 rows: Vec<SparqlRow>,
405 variables: Vec<String>,
406 accessed_predicates: Vec<String>,
407 ) {
408 let hash = Self::hash_query(query);
409 let entry = QueryCacheEntry {
410 query_hash: hash,
411 rows,
412 variables,
413 accessed_predicates,
414 created_at: Instant::now(),
415 };
416 self.cache.put((dataset_id.to_string(), hash), entry);
417 }
418
419 pub fn get(&self, dataset_id: &str, query: &str) -> Option<QueryCacheEntry> {
421 let hash = Self::hash_query(query);
422 self.cache.get(&(dataset_id.to_string(), hash))
423 }
424
425 pub fn invalidate_by_predicate(&self, dataset_id: &str, predicate: &str) -> usize {
427 let keys_to_remove: Vec<_> = self
428 .cache
429 .keys()
430 .into_iter()
431 .filter(|(ds, _)| ds == dataset_id)
432 .filter(|k| {
433 self.cache
434 .get(k)
435 .map(|e| e.accessed_predicates.iter().any(|p| p == predicate))
436 .unwrap_or(false)
437 })
438 .collect();
439 let count = keys_to_remove.len();
440 for k in keys_to_remove {
441 self.cache.remove(&k);
442 }
443 count
444 }
445
446 pub fn invalidate_dataset(&self, dataset_id: &str) -> usize {
448 let keys_to_remove: Vec<_> = self
449 .cache
450 .keys()
451 .into_iter()
452 .filter(|(ds, _)| ds == dataset_id)
453 .collect();
454 let count = keys_to_remove.len();
455 for k in keys_to_remove {
456 self.cache.remove(&k);
457 }
458 count
459 }
460
461 pub fn len(&self) -> usize {
463 self.cache.len()
464 }
465
466 pub fn is_empty(&self) -> bool {
467 self.cache.is_empty()
468 }
469
470 pub fn stats(&self) -> Arc<CacheStats> {
472 self.cache.stats()
473 }
474}
475
476#[derive(Debug, Default, Clone)]
485pub struct PrefixCache {
486 prefix_to_iri: HashMap<String, String>,
487 iri_to_prefix: HashMap<String, String>,
488}
489
490impl PrefixCache {
491 pub fn new() -> Self {
492 Self::default()
493 }
494
495 pub fn register(&mut self, prefix: &str, iri: &str) {
499 if let Some(old_iri) = self.prefix_to_iri.get(prefix) {
501 self.iri_to_prefix.remove(old_iri.as_str());
502 }
503 self.prefix_to_iri
504 .insert(prefix.to_string(), iri.to_string());
505 self.iri_to_prefix
506 .insert(iri.to_string(), prefix.to_string());
507 }
508
509 pub fn resolve_prefix(&self, prefix: &str) -> Option<&str> {
511 self.prefix_to_iri.get(prefix).map(|s| s.as_str())
512 }
513
514 pub fn resolve_iri(&self, iri: &str) -> Option<&str> {
516 self.iri_to_prefix.get(iri).map(|s| s.as_str())
517 }
518
519 pub fn expand(&self, prefixed: &str) -> Option<String> {
523 let colon = prefixed.find(':')?;
524 let prefix = &prefixed[..colon];
525 let local = &prefixed[colon + 1..];
526 let namespace = self.prefix_to_iri.get(prefix)?;
527 Some(format!("{namespace}{local}"))
528 }
529
530 pub fn compact(&self, iri: &str) -> Option<String> {
534 let mut best: Option<(&str, &str)> = None;
535 for (namespace, prefix) in &self.iri_to_prefix {
536 if iri.starts_with(namespace.as_str())
537 && best.map(|(ns, _)| ns.len()).unwrap_or(0) < namespace.len()
538 {
539 best = Some((namespace.as_str(), prefix.as_str()));
540 }
541 }
542 best.map(|(ns, pfx)| format!("{}:{}", pfx, &iri[ns.len()..]))
543 }
544
545 pub fn remove(&mut self, prefix: &str) -> bool {
547 if let Some(iri) = self.prefix_to_iri.remove(prefix) {
548 self.iri_to_prefix.remove(&iri);
549 true
550 } else {
551 false
552 }
553 }
554
555 pub fn len(&self) -> usize {
557 self.prefix_to_iri.len()
558 }
559
560 pub fn is_empty(&self) -> bool {
561 self.prefix_to_iri.is_empty()
562 }
563
564 pub fn entries(&self) -> Vec<(&str, &str)> {
566 self.prefix_to_iri
567 .iter()
568 .map(|(p, i)| (p.as_str(), i.as_str()))
569 .collect()
570 }
571
572 pub fn with_standard_prefixes(mut self) -> Self {
574 self.register("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
575 self.register("rdfs", "http://www.w3.org/2000/01/rdf-schema#");
576 self.register("owl", "http://www.w3.org/2002/07/owl#");
577 self.register("xsd", "http://www.w3.org/2001/XMLSchema#");
578 self.register("dc", "http://purl.org/dc/elements/1.1/");
579 self.register("dcterms", "http://purl.org/dc/terms/");
580 self.register("foaf", "http://xmlns.com/foaf/0.1/");
581 self.register("skos", "http://www.w3.org/2004/02/skos/core#");
582 self
583 }
584}
585
586#[cfg(test)]
591mod tests {
592 use super::*;
593 use std::thread;
594 use std::time::Duration;
595
596 #[test]
599 fn test_cache_policy_variants_are_distinct() {
600 assert_ne!(CachePolicy::Lru, CachePolicy::Lfu);
601 assert_ne!(CachePolicy::Lfu, CachePolicy::Fifo);
602 assert_ne!(CachePolicy::Fifo, CachePolicy::Ttl);
603 }
604
605 #[test]
608 fn test_cache_stats_initial_zero() {
609 let s = CacheStats::new();
610 assert_eq!(s.hits(), 0);
611 assert_eq!(s.misses(), 0);
612 assert_eq!(s.evictions(), 0);
613 assert_eq!(s.insertions(), 0);
614 }
615
616 #[test]
617 fn test_cache_stats_hit_rate_empty() {
618 let s = CacheStats::new();
619 assert_eq!(s.hit_rate(), 0.0);
620 }
621
622 #[test]
623 fn test_cache_stats_hit_rate_all_hits() {
624 let s = CacheStats::new();
625 s.record_hit();
626 s.record_hit();
627 assert_eq!(s.hit_rate(), 1.0);
628 }
629
630 #[test]
631 fn test_cache_stats_hit_rate_half() {
632 let s = CacheStats::new();
633 s.record_hit();
634 s.record_miss();
635 assert!((s.hit_rate() - 0.5).abs() < 1e-9);
636 }
637
638 #[test]
639 fn test_cache_stats_reset() {
640 let s = CacheStats::new();
641 s.record_hit();
642 s.record_miss();
643 s.record_eviction();
644 s.reset();
645 assert_eq!(s.hits(), 0);
646 assert_eq!(s.misses(), 0);
647 assert_eq!(s.evictions(), 0);
648 }
649
650 #[test]
651 fn test_cache_stats_concurrent_updates() {
652 let s = Arc::new(CacheStats::default());
653 let mut handles = vec![];
654 for _ in 0..4 {
655 let sc = Arc::clone(&s);
656 handles.push(thread::spawn(move || {
657 for _ in 0..100 {
658 sc.record_hit();
659 sc.record_miss();
660 }
661 }));
662 }
663 for h in handles {
664 h.join().expect("thread panicked");
665 }
666 assert_eq!(s.hits(), 400);
667 assert_eq!(s.misses(), 400);
668 }
669
670 #[test]
673 fn test_triple_cache_lru_empty_get_returns_none() {
674 let cache: TripleCache<String, String> = TripleCache::lru(10);
675 assert!(cache.get(&"missing".to_string()).is_none());
676 }
677
678 #[test]
679 fn test_triple_cache_lru_put_and_get() {
680 let cache = TripleCache::lru(10);
681 cache.put("key1".to_string(), "val1".to_string());
682 assert_eq!(cache.get(&"key1".to_string()), Some("val1".to_string()));
683 }
684
685 #[test]
686 fn test_triple_cache_lru_overwrite() {
687 let cache = TripleCache::lru(10);
688 cache.put("k".to_string(), "v1".to_string());
689 cache.put("k".to_string(), "v2".to_string());
690 assert_eq!(cache.get(&"k".to_string()), Some("v2".to_string()));
691 }
692
693 #[test]
694 fn test_triple_cache_lru_eviction_at_capacity() {
695 let cache: TripleCache<usize, usize> = TripleCache::lru(3);
696 cache.put(1, 10);
697 cache.put(2, 20);
698 cache.put(3, 30);
699 let _ = cache.get(&1);
701 let _ = cache.get(&2);
702 cache.put(4, 40);
704 assert_eq!(cache.len(), 3);
705 assert!(cache.contains(&1));
706 assert!(cache.contains(&2));
707 assert!(cache.contains(&4));
708 }
709
710 #[test]
711 fn test_triple_cache_lru_remove() {
712 let cache = TripleCache::lru(10);
713 cache.put("a".to_string(), 1i32);
714 assert!(cache.remove(&"a".to_string()));
715 assert!(!cache.remove(&"a".to_string()));
716 assert!(cache.get(&"a".to_string()).is_none());
717 }
718
719 #[test]
720 fn test_triple_cache_lru_clear() {
721 let cache: TripleCache<i32, i32> = TripleCache::lru(10);
722 for i in 0..5 {
723 cache.put(i, i * 2);
724 }
725 cache.clear();
726 assert_eq!(cache.len(), 0);
727 assert!(cache.is_empty());
728 }
729
730 #[test]
731 fn test_triple_cache_lru_contains() {
732 let cache: TripleCache<i32, i32> = TripleCache::lru(10);
733 cache.put(42, 84);
734 assert!(cache.contains(&42));
735 assert!(!cache.contains(&43));
736 }
737
738 #[test]
739 fn test_triple_cache_lru_len() {
740 let cache: TripleCache<i32, i32> = TripleCache::lru(10);
741 assert_eq!(cache.len(), 0);
742 cache.put(1, 1);
743 assert_eq!(cache.len(), 1);
744 cache.put(2, 2);
745 assert_eq!(cache.len(), 2);
746 }
747
748 #[test]
749 fn test_triple_cache_lru_keys() {
750 let cache: TripleCache<i32, i32> = TripleCache::lru(10);
751 cache.put(1, 10);
752 cache.put(2, 20);
753 let mut keys = cache.keys();
754 keys.sort();
755 assert_eq!(keys, vec![1, 2]);
756 }
757
758 #[test]
759 fn test_triple_cache_lru_stats_incremented() {
760 let cache: TripleCache<i32, i32> = TripleCache::lru(10);
761 cache.put(1, 100);
762 let _ = cache.get(&1);
763 let _ = cache.get(&99);
764 let s = cache.stats();
765 assert_eq!(s.hits(), 1);
766 assert_eq!(s.misses(), 1);
767 assert_eq!(s.insertions(), 1);
768 }
769
770 #[test]
771 fn test_triple_cache_lfu_evicts_least_used() {
772 let cache: TripleCache<i32, i32> = TripleCache::new(3, CachePolicy::Lfu, None);
773 cache.put(1, 1);
774 cache.put(2, 2);
775 cache.put(3, 3);
776 for _ in 0..5 {
778 let _ = cache.get(&1);
779 }
780 let _ = cache.get(&2);
782 cache.put(4, 4);
784 assert!(cache.contains(&1));
786 }
787
788 #[test]
789 fn test_triple_cache_fifo_evicts_oldest() {
790 let cache: TripleCache<i32, i32> = TripleCache::new(3, CachePolicy::Fifo, None);
791 cache.put(1, 1); cache.put(2, 2); cache.put(3, 3); let _ = cache.get(&1);
796 let _ = cache.get(&1);
797 cache.put(4, 4);
798 assert_eq!(cache.len(), 3);
799 assert!(!cache.contains(&1)); }
801
802 #[test]
803 fn test_triple_cache_ttl_expiry() {
804 let ttl = Duration::from_millis(50);
805 let cache: TripleCache<i32, i32> = TripleCache::lru_ttl(10, ttl);
806 cache.put(1, 100);
807 assert!(cache.get(&1).is_some());
808 thread::sleep(Duration::from_millis(60));
809 assert!(cache.get(&1).is_none());
810 }
811
812 #[test]
815 fn test_query_result_cache_miss_on_empty() {
816 let qrc = QueryResultCache::new(100, Duration::from_secs(60));
817 assert!(qrc.get("ds", "SELECT * WHERE { ?s ?p ?o }").is_none());
818 }
819
820 #[test]
821 fn test_query_result_cache_put_and_get() {
822 let qrc = QueryResultCache::new(100, Duration::from_secs(60));
823 let rows = vec![[("s".to_string(), "Alice".to_string())]
824 .into_iter()
825 .collect()];
826 qrc.put(
827 "ds1",
828 "SELECT ?s WHERE {?s a :Person}",
829 rows.clone(),
830 vec!["s".to_string()],
831 vec![],
832 );
833 let entry = qrc
834 .get("ds1", "SELECT ?s WHERE {?s a :Person}")
835 .expect("should hit");
836 assert_eq!(entry.rows.len(), 1);
837 }
838
839 #[test]
840 fn test_query_result_cache_different_datasets_no_collision() {
841 let qrc = QueryResultCache::new(100, Duration::from_secs(60));
842 let q = "SELECT * WHERE { ?s ?p ?o }";
843 let rows1 = vec![[("x".to_string(), "A".to_string())].into_iter().collect()];
844 let rows2 = vec![[("x".to_string(), "B".to_string())].into_iter().collect()];
845 qrc.put("ds1", q, rows1, vec![], vec![]);
846 qrc.put("ds2", q, rows2, vec![], vec![]);
847 let e1 = qrc.get("ds1", q).expect("hit");
848 let e2 = qrc.get("ds2", q).expect("hit");
849 assert_ne!(e1.rows[0]["x"], e2.rows[0]["x"],);
850 }
851
852 #[test]
853 fn test_query_result_cache_invalidate_by_predicate() {
854 let qrc = QueryResultCache::new(100, Duration::from_secs(60));
855 qrc.put("ds", "q1", vec![], vec![], vec!["http://p/age".to_string()]);
856 qrc.put(
857 "ds",
858 "q2",
859 vec![],
860 vec![],
861 vec!["http://p/name".to_string()],
862 );
863 let removed = qrc.invalidate_by_predicate("ds", "http://p/age");
864 assert_eq!(removed, 1);
865 assert!(qrc.get("ds", "q1").is_none());
866 assert!(qrc.get("ds", "q2").is_some());
867 }
868
869 #[test]
870 fn test_query_result_cache_invalidate_dataset() {
871 let qrc = QueryResultCache::new(100, Duration::from_secs(60));
872 qrc.put("ds", "q1", vec![], vec![], vec![]);
873 qrc.put("ds", "q2", vec![], vec![], vec![]);
874 qrc.put("other_ds", "q1", vec![], vec![], vec![]);
875 let removed = qrc.invalidate_dataset("ds");
876 assert_eq!(removed, 2);
877 assert!(qrc.get("other_ds", "q1").is_some());
878 }
879
880 #[test]
881 fn test_query_result_cache_len() {
882 let qrc = QueryResultCache::new(100, Duration::from_secs(60));
883 assert_eq!(qrc.len(), 0);
884 qrc.put("ds", "q1", vec![], vec![], vec![]);
885 assert_eq!(qrc.len(), 1);
886 }
887
888 #[test]
889 fn test_query_result_cache_is_empty() {
890 let qrc = QueryResultCache::new(100, Duration::from_secs(60));
891 assert!(qrc.is_empty());
892 qrc.put("ds", "q", vec![], vec![], vec![]);
893 assert!(!qrc.is_empty());
894 }
895
896 #[test]
897 fn test_query_result_cache_variables_preserved() {
898 let qrc = QueryResultCache::new(100, Duration::from_secs(60));
899 let vars = vec!["?s".to_string(), "?p".to_string(), "?o".to_string()];
900 qrc.put("ds", "q", vec![], vars.clone(), vec![]);
901 let entry = qrc.get("ds", "q").expect("hit");
902 assert_eq!(entry.variables, vars);
903 }
904
905 #[test]
908 fn test_prefix_cache_empty() {
909 let pc = PrefixCache::new();
910 assert!(pc.is_empty());
911 assert_eq!(pc.len(), 0);
912 }
913
914 #[test]
915 fn test_prefix_cache_register_and_resolve_prefix() {
916 let mut pc = PrefixCache::new();
917 pc.register("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
918 assert_eq!(
919 pc.resolve_prefix("rdf"),
920 Some("http://www.w3.org/1999/02/22-rdf-syntax-ns#")
921 );
922 }
923
924 #[test]
925 fn test_prefix_cache_register_and_resolve_iri() {
926 let mut pc = PrefixCache::new();
927 pc.register("rdfs", "http://www.w3.org/2000/01/rdf-schema#");
928 assert_eq!(
929 pc.resolve_iri("http://www.w3.org/2000/01/rdf-schema#"),
930 Some("rdfs")
931 );
932 }
933
934 #[test]
935 fn test_prefix_cache_expand() {
936 let mut pc = PrefixCache::new();
937 pc.register("owl", "http://www.w3.org/2002/07/owl#");
938 assert_eq!(
939 pc.expand("owl:Class"),
940 Some("http://www.w3.org/2002/07/owl#Class".to_string())
941 );
942 }
943
944 #[test]
945 fn test_prefix_cache_compact() {
946 let mut pc = PrefixCache::new();
947 pc.register("xsd", "http://www.w3.org/2001/XMLSchema#");
948 assert_eq!(
949 pc.compact("http://www.w3.org/2001/XMLSchema#string"),
950 Some("xsd:string".to_string())
951 );
952 }
953
954 #[test]
955 fn test_prefix_cache_expand_unknown_prefix_returns_none() {
956 let pc = PrefixCache::new();
957 assert!(pc.expand("unknown:Term").is_none());
958 }
959
960 #[test]
961 fn test_prefix_cache_compact_no_match_returns_none() {
962 let pc = PrefixCache::new();
963 assert!(pc.compact("http://example.org/x").is_none());
964 }
965
966 #[test]
967 fn test_prefix_cache_overwrite_prefix() {
968 let mut pc = PrefixCache::new();
969 pc.register("ex", "http://example.org/");
970 pc.register("ex", "http://example.com/");
971 assert_eq!(pc.resolve_prefix("ex"), Some("http://example.com/"));
972 assert_eq!(pc.len(), 1);
973 }
974
975 #[test]
976 fn test_prefix_cache_remove() {
977 let mut pc = PrefixCache::new();
978 pc.register("ex", "http://example.org/");
979 assert!(pc.remove("ex"));
980 assert!(pc.resolve_prefix("ex").is_none());
981 assert!(!pc.remove("ex")); }
983
984 #[test]
985 fn test_prefix_cache_standard_prefixes() {
986 let pc = PrefixCache::new().with_standard_prefixes();
987 assert!(pc.resolve_prefix("rdf").is_some());
988 assert!(pc.resolve_prefix("rdfs").is_some());
989 assert!(pc.resolve_prefix("owl").is_some());
990 assert!(pc.resolve_prefix("xsd").is_some());
991 assert!(pc.resolve_prefix("foaf").is_some());
992 }
993
994 #[test]
995 fn test_prefix_cache_longest_namespace_wins_on_compact() {
996 let mut pc = PrefixCache::new();
997 pc.register("schema", "http://schema.org/");
998 pc.register("schema_person", "http://schema.org/Person/");
999 let result = pc.compact("http://schema.org/Person/name");
1001 assert_eq!(result, Some("schema_person:name".to_string()));
1002 }
1003
1004 #[test]
1005 fn test_prefix_cache_entries_returns_all() {
1006 let mut pc = PrefixCache::new();
1007 pc.register("a", "http://a.org/");
1008 pc.register("b", "http://b.org/");
1009 let mut entries: Vec<_> = pc.entries().into_iter().map(|(p, _)| p).collect();
1010 entries.sort();
1011 assert_eq!(entries, vec!["a", "b"]);
1012 }
1013}