1use crate::{
7 algebra::{Algebra, Solution, Term, Variable},
8 query::Query,
9 Result,
10};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::hash::{Hash, Hasher};
14use std::sync::Arc;
15use std::time::Duration;
16
17pub trait CacheKey: Clone + std::hash::Hash + Eq + Send + Sync {}
20pub trait CacheValue: Clone + Send + Sync {}
21
22impl CacheKey for String {}
24
25impl CacheValue for StatisticsSnapshot {}
27
28#[derive(Debug)]
30pub struct AdvancedCache<K, V> {
31 _phantom: std::marker::PhantomData<(K, V)>,
32}
33
34impl<K: CacheKey, V: CacheValue> AdvancedCache<K, V> {
35 pub fn new(_config: AdvancedCacheConfig) -> Self {
36 Self {
37 _phantom: std::marker::PhantomData,
38 }
39 }
40
41 pub fn get(&self, _key: &K) -> Option<V> {
42 None
43 }
44
45 pub fn put(&self, _key: K, _value: V) -> Result<()> {
46 Ok(())
47 }
48
49 pub fn warm_cache(&self) -> Result<()> {
50 Ok(())
51 }
52
53 pub fn clear(&self) {
54 }
56}
57
58#[derive(Debug, Clone)]
60pub struct AdvancedCacheConfig {
61 pub l1_cache_size: usize,
62 pub l2_cache_size: usize,
63 pub l3_cache_size: usize,
64 pub enable_compression: bool,
65}
66
67impl Default for AdvancedCacheConfig {
68 fn default() -> Self {
69 Self {
70 l1_cache_size: 1024,
71 l2_cache_size: 4096,
72 l3_cache_size: 16384,
73 enable_compression: false,
74 }
75 }
76}
77
78#[derive(Debug, Clone)]
80pub struct ArqCacheConfig {
81 pub query_plan_cache: AdvancedCacheConfig,
83 pub query_result_cache: AdvancedCacheConfig,
85 pub bgp_cache: AdvancedCacheConfig,
87 pub statistics_cache: AdvancedCacheConfig,
89 pub enable_cross_query_caching: bool,
91 pub max_result_size: usize,
93 pub query_similarity_threshold: f64,
95}
96
97impl Default for ArqCacheConfig {
98 fn default() -> Self {
99 let base_config = AdvancedCacheConfig {
100 l1_cache_size: 5000, l2_cache_size: 20000,
102 l3_cache_size: 100000,
103 ..Default::default()
104 };
105
106 let result_config = AdvancedCacheConfig {
107 l1_cache_size: 1000, l2_cache_size: 5000,
109 l3_cache_size: 20000,
110 enable_compression: true,
111 };
112
113 Self {
114 query_plan_cache: base_config.clone(),
115 query_result_cache: result_config,
116 bgp_cache: base_config.clone(),
117 statistics_cache: base_config,
118 enable_cross_query_caching: true,
119 max_result_size: 10 * 1024 * 1024, query_similarity_threshold: 0.8,
121 }
122 }
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct CachedQueryPlan {
128 pub algebra: Algebra,
130 pub estimated_cost: f64,
132 pub optimization_metadata: OptimizationMetadata,
134 pub statistics_snapshot: StatisticsSnapshot,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct OptimizationMetadata {
141 pub optimizations_applied: Vec<String>,
143 pub optimization_time_ms: u64,
145 pub selectivity_estimates: HashMap<String, f64>,
147 pub join_order: Vec<String>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct StatisticsSnapshot {
154 pub dataset_size: usize,
156 pub predicate_cardinalities: HashMap<String, usize>,
158 pub timestamp: u64,
160 pub version: String,
162}
163
164#[derive(Debug, Clone)]
166pub struct CachedQueryResult {
167 pub solutions: Vec<Solution>,
169 pub metadata: QueryResultMetadata,
171 pub size_bytes: usize,
173}
174
175#[derive(Debug, Clone)]
177pub struct QueryResultMetadata {
178 pub execution_time: Duration,
180 pub dataset_version: String,
182 pub variables: Vec<Variable>,
184 pub solution_count: usize,
186 pub is_complete: bool,
188}
189
190#[derive(Debug, Clone, Hash, PartialEq, Eq)]
192pub struct QueryPlanCacheKey {
193 pub query_hash: u64,
195 pub schema_hash: u64,
197 pub optimization_level: OptimizationLevel,
199 pub config_hash: u64,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq)]
205pub struct QueryResultCacheKey {
206 pub query_signature: QuerySignature,
208 pub dataset_version: String,
210 pub parameter_bindings: HashMap<String, String>,
212}
213
214impl std::hash::Hash for QueryResultCacheKey {
215 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
216 self.query_signature.hash(state);
217 self.dataset_version.hash(state);
218 let mut sorted_params: Vec<_> = self.parameter_bindings.iter().collect();
220 sorted_params.sort_by_key(|(k, _)| *k);
221 for (k, v) in sorted_params {
222 k.hash(state);
223 v.hash(state);
224 }
225 }
226}
227
228#[derive(Debug, Clone, Hash, PartialEq, Eq)]
230pub struct QuerySignature {
231 pub canonical_form: String,
233 pub variables: Vec<String>,
235 pub operation_type: QueryOperationType,
237 pub complexity_score: u32,
239}
240
241#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
243pub enum QueryOperationType {
244 Select,
245 Construct,
246 Ask,
247 Describe,
248 Update,
249}
250
251#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
253pub enum OptimizationLevel {
254 Basic,
255 Standard,
256 Aggressive,
257 Custom(u32),
258}
259
260#[derive(Debug, Clone, Hash, PartialEq, Eq)]
262pub struct BgpCacheKey {
263 pub pattern_hash: u64,
265 pub bindings_hash: u64,
267 pub graph_context: Option<String>,
269}
270
271#[derive(Debug, Clone)]
273pub struct CachedBgpResult {
274 pub solutions: Vec<Solution>,
276 pub metadata: BgpEvaluationMetadata,
278}
279
280#[derive(Debug, Clone)]
282pub struct BgpEvaluationMetadata {
283 pub evaluation_time: Duration,
285 pub solution_count: usize,
287 pub index_hits: usize,
289 pub selectivity: f64,
291}
292
293pub struct ArqCacheManager {
295 query_plan_cache: Arc<AdvancedCache<QueryPlanCacheKey, CachedQueryPlan>>,
297 query_result_cache: Arc<AdvancedCache<QueryResultCacheKey, CachedQueryResult>>,
299 bgp_cache: Arc<AdvancedCache<BgpCacheKey, CachedBgpResult>>,
301 statistics_cache: Arc<AdvancedCache<String, StatisticsSnapshot>>,
303 config: ArqCacheConfig,
305 cache_stats: Arc<std::sync::RwLock<ArqCacheStatistics>>,
307}
308
309#[derive(Debug, Clone, Default)]
311pub struct ArqCacheStatistics {
312 pub plan_cache_hits: usize,
314 pub plan_cache_misses: usize,
316 pub result_cache_hits: usize,
318 pub result_cache_misses: usize,
320 pub bgp_cache_hits: usize,
322 pub bgp_cache_misses: usize,
324 pub time_saved_ms: u64,
326 pub avg_lookup_time_us: f64,
328 pub efficiency_score: f64,
330}
331
332impl ArqCacheManager {
333 pub fn new(config: ArqCacheConfig) -> Self {
335 Self {
336 query_plan_cache: Arc::new(AdvancedCache::new(config.query_plan_cache.clone())),
337 query_result_cache: Arc::new(AdvancedCache::new(config.query_result_cache.clone())),
338 bgp_cache: Arc::new(AdvancedCache::new(config.bgp_cache.clone())),
339 statistics_cache: Arc::new(AdvancedCache::new(config.statistics_cache.clone())),
340 config,
341 cache_stats: Arc::new(std::sync::RwLock::new(ArqCacheStatistics::default())),
342 }
343 }
344
345 pub fn get_query_plan(&self, key: &QueryPlanCacheKey) -> Option<CachedQueryPlan> {
347 let start_time = std::time::Instant::now();
348 let result = self.query_plan_cache.get(key);
349
350 {
351 let mut stats = self.cache_stats.write().expect("lock poisoned");
352 if result.is_some() {
353 stats.plan_cache_hits += 1;
354 } else {
355 stats.plan_cache_misses += 1;
356 }
357 self.update_avg_lookup_time(&mut stats, start_time.elapsed());
358 }
359
360 result
361 }
362
363 pub fn cache_query_plan(&self, key: QueryPlanCacheKey, plan: CachedQueryPlan) -> Result<()> {
365 self.query_plan_cache.put(key, plan)?;
366 Ok(())
367 }
368
369 pub fn get_query_result(&self, key: &QueryResultCacheKey) -> Option<CachedQueryResult> {
371 let start_time = std::time::Instant::now();
372
373 if !self.is_result_cache_valid(key) {
375 return None;
376 }
377
378 let result = self.query_result_cache.get(key);
379
380 {
381 let mut stats = self.cache_stats.write().expect("lock poisoned");
382 if let Some(ref cached_result) = result {
383 stats.result_cache_hits += 1;
384 stats.time_saved_ms += cached_result.metadata.execution_time.as_millis() as u64;
385 } else {
386 stats.result_cache_misses += 1;
387 }
388 self.update_avg_lookup_time(&mut stats, start_time.elapsed());
389 }
390
391 result
392 }
393
394 pub fn cache_query_result(
396 &self,
397 key: QueryResultCacheKey,
398 result: CachedQueryResult,
399 ) -> Result<()> {
400 if result.size_bytes > self.config.max_result_size {
402 return Ok(()); }
404
405 self.query_result_cache.put(key, result)?;
406 Ok(())
407 }
408
409 pub fn get_bgp_result(&self, key: &BgpCacheKey) -> Option<CachedBgpResult> {
411 let start_time = std::time::Instant::now();
412 let result = self.bgp_cache.get(key);
413
414 {
415 let mut stats = self.cache_stats.write().expect("lock poisoned");
416 if result.is_some() {
417 stats.bgp_cache_hits += 1;
418 } else {
419 stats.bgp_cache_misses += 1;
420 }
421 self.update_avg_lookup_time(&mut stats, start_time.elapsed());
422 }
423
424 result
425 }
426
427 pub fn cache_bgp_result(&self, key: BgpCacheKey, result: CachedBgpResult) -> Result<()> {
429 self.bgp_cache.put(key, result)?;
430 Ok(())
431 }
432
433 pub fn create_plan_cache_key(
435 &self,
436 query: &Query,
437 schema_hash: u64,
438 optimization_level: OptimizationLevel,
439 ) -> QueryPlanCacheKey {
440 let query_hash = self.hash_query(query);
441 let config_hash = self.hash_config();
442
443 QueryPlanCacheKey {
444 query_hash,
445 schema_hash,
446 optimization_level,
447 config_hash,
448 }
449 }
450
451 pub fn create_result_cache_key(
453 &self,
454 query: &Query,
455 dataset_version: String,
456 parameter_bindings: HashMap<String, String>,
457 ) -> QueryResultCacheKey {
458 let query_signature = self.create_query_signature(query);
459
460 QueryResultCacheKey {
461 query_signature,
462 dataset_version,
463 parameter_bindings,
464 }
465 }
466
467 pub fn create_bgp_cache_key(
469 &self,
470 pattern_hash: u64,
471 bindings: &HashMap<Variable, Term>,
472 graph_context: Option<&str>,
473 ) -> BgpCacheKey {
474 let bindings_hash = self.hash_bindings(bindings);
475
476 BgpCacheKey {
477 pattern_hash,
478 bindings_hash,
479 graph_context: graph_context.map(|s| s.to_string()),
480 }
481 }
482
483 pub fn get_statistics(&self) -> ArqCacheStatistics {
485 let stats = self.cache_stats.read().expect("lock poisoned");
486 stats.clone()
487 }
488
489 pub fn warm_caches(&self) -> Result<()> {
491 self.query_plan_cache.warm_cache()?;
493
494 self.query_result_cache.warm_cache()?;
496
497 self.bgp_cache.warm_cache()?;
499
500 Ok(())
501 }
502
503 pub fn clear_all_caches(&self) {
505 self.query_plan_cache.clear();
506 self.query_result_cache.clear();
507 self.bgp_cache.clear();
508 self.statistics_cache.clear();
509
510 {
512 let mut stats = self.cache_stats.write().expect("lock poisoned");
513 *stats = ArqCacheStatistics::default();
514 }
515 }
516
517 pub fn invalidate_on_dataset_change(&self, _changed_predicates: &[String]) -> Result<()> {
519 self.clear_all_caches();
522 Ok(())
523 }
524
525 fn hash_query(&self, query: &Query) -> u64 {
527 let mut hasher = std::collections::hash_map::DefaultHasher::new();
529 format!("{query:?}").hash(&mut hasher);
531 hasher.finish()
532 }
533
534 fn hash_config(&self) -> u64 {
535 let mut hasher = std::collections::hash_map::DefaultHasher::new();
537 self.config
538 .query_similarity_threshold
539 .to_bits()
540 .hash(&mut hasher);
541 self.config.enable_cross_query_caching.hash(&mut hasher);
542 hasher.finish()
543 }
544
545 fn hash_bindings(&self, bindings: &HashMap<Variable, Term>) -> u64 {
546 let mut hasher = std::collections::hash_map::DefaultHasher::new();
547 let mut sorted_bindings: Vec<_> = bindings.iter().collect();
549 sorted_bindings.sort_by_key(|(var, _)| var.as_str());
550
551 for (var, term) in sorted_bindings {
552 var.hash(&mut hasher);
553 format!("{term:?}").hash(&mut hasher);
554 }
555
556 hasher.finish()
557 }
558
559 fn create_query_signature(&self, query: &Query) -> QuerySignature {
560 QuerySignature {
562 canonical_form: format!("{query:?}"), variables: query
564 .select_variables
565 .iter()
566 .map(|v| v.as_str().to_string())
567 .collect(),
568 operation_type: self.determine_operation_type(query),
569 complexity_score: self.calculate_complexity_score(query),
570 }
571 }
572
573 fn determine_operation_type(&self, _query: &Query) -> QueryOperationType {
574 QueryOperationType::Select }
577
578 fn calculate_complexity_score(&self, _query: &Query) -> u32 {
579 100 }
582
583 fn is_result_cache_valid(&self, _key: &QueryResultCacheKey) -> bool {
584 true }
588
589 fn update_avg_lookup_time(&self, stats: &mut ArqCacheStatistics, lookup_time: Duration) {
590 let total_lookups = stats.plan_cache_hits
591 + stats.plan_cache_misses
592 + stats.result_cache_hits
593 + stats.result_cache_misses
594 + stats.bgp_cache_hits
595 + stats.bgp_cache_misses;
596
597 let lookup_time_us = lookup_time.as_micros() as f64;
598
599 if total_lookups == 1 {
600 stats.avg_lookup_time_us = lookup_time_us;
601 } else {
602 stats.avg_lookup_time_us = (stats.avg_lookup_time_us * (total_lookups - 1) as f64
603 + lookup_time_us)
604 / total_lookups as f64;
605 }
606
607 let hit_rate = (stats.plan_cache_hits + stats.result_cache_hits + stats.bgp_cache_hits)
609 as f64
610 / total_lookups.max(1) as f64;
611 stats.efficiency_score =
612 hit_rate * 0.7 + (1.0 - stats.avg_lookup_time_us / 1000.0).max(0.0) * 0.3;
613 }
614}
615
616impl CacheKey for QueryPlanCacheKey {}
618impl CacheValue for CachedQueryPlan {}
619impl CacheKey for QueryResultCacheKey {}
620impl CacheValue for CachedQueryResult {}
621impl CacheKey for BgpCacheKey {}
622impl CacheValue for CachedBgpResult {}
623
624#[cfg(test)]
625mod tests {
626 use super::*;
627
628 #[test]
629 fn test_arq_cache_manager_creation() {
630 let config = ArqCacheConfig::default();
631 let cache_manager = ArqCacheManager::new(config);
632
633 let stats = cache_manager.get_statistics();
634 assert_eq!(stats.plan_cache_hits, 0);
635 assert_eq!(stats.result_cache_hits, 0);
636 }
637
638 #[test]
639 fn test_cache_key_creation() {
640 let config = ArqCacheConfig::default();
641 let _cache_manager = ArqCacheManager::new(config);
642
643 }
646}