Skip to main content

oxirs_gql/
intelligent_query_cache.rs

1//! Intelligent Query Cache
2//!
3//! This module implements an AI-driven query caching system that learns from query patterns
4//! and proactively caches frequently used queries for enhanced performance.
5
6// anyhow::Result used with full path in function signatures
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, VecDeque};
9use std::hash::{Hash, Hasher};
10use std::sync::Arc;
11use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
12use tokio::sync::RwLock;
13use tracing::{debug, info, warn};
14
15use crate::ast::{Definition, Document, FragmentDefinition, OperationDefinition, Value};
16use crate::distributed_cache::{CacheConfig, GraphQLQueryCache};
17
18/// Configuration for intelligent query caching
19#[derive(Debug, Clone)]
20pub struct IntelligentCacheConfig {
21    pub max_cache_entries: usize,
22    pub max_pattern_history: usize,
23    pub prediction_confidence_threshold: f64,
24    pub cache_ttl_seconds: u64,
25    pub pattern_analysis_interval_seconds: u64,
26    pub enable_predictive_caching: bool,
27    pub enable_pattern_learning: bool,
28    pub enable_usage_analytics: bool,
29}
30
31impl Default for IntelligentCacheConfig {
32    fn default() -> Self {
33        Self {
34            max_cache_entries: 10000,
35            max_pattern_history: 1000,
36            prediction_confidence_threshold: 0.75,
37            cache_ttl_seconds: 3600,                // 1 hour
38            pattern_analysis_interval_seconds: 300, // 5 minutes
39            enable_predictive_caching: true,
40            enable_pattern_learning: true,
41            enable_usage_analytics: true,
42        }
43    }
44}
45
46/// Query pattern for similarity analysis
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct QueryPattern {
49    pub query_type: String,
50    pub field_count: usize,
51    pub depth: usize,
52    pub has_arguments: bool,
53    pub has_fragments: bool,
54    pub complexity_score: f64,
55    pub timestamp: u64,
56    pub access_frequency: f64,
57    pub execution_time_ms: u64,
58}
59
60impl QueryPattern {
61    pub fn from_document(doc: &Document) -> Self {
62        let operations = Self::extract_operations(doc);
63        let fragments = Self::extract_fragments(doc);
64
65        let query_type = operations
66            .first()
67            .map(|op| format!("{:?}", op.operation_type).to_lowercase())
68            .unwrap_or_else(|| "query".to_string());
69
70        let field_count = Self::count_fields_from_operations(&operations);
71        let depth = Self::calculate_depth_from_operations(&operations);
72        let has_arguments = Self::has_arguments_from_operations(&operations);
73        let has_fragments = !fragments.is_empty();
74        let complexity_score = Self::calculate_complexity_from_operations(&operations);
75
76        let timestamp = SystemTime::now()
77            .duration_since(UNIX_EPOCH)
78            .unwrap_or_default()
79            .as_secs();
80
81        Self {
82            query_type,
83            field_count,
84            depth,
85            has_arguments,
86            has_fragments,
87            complexity_score,
88            timestamp,
89            access_frequency: 1.0,
90            execution_time_ms: 0,
91        }
92    }
93
94    fn extract_operations(doc: &Document) -> Vec<&OperationDefinition> {
95        doc.definitions
96            .iter()
97            .filter_map(|def| {
98                if let Definition::Operation(op) = def {
99                    Some(op)
100                } else {
101                    None
102                }
103            })
104            .collect()
105    }
106
107    fn extract_fragments(doc: &Document) -> Vec<&FragmentDefinition> {
108        doc.definitions
109            .iter()
110            .filter_map(|def| {
111                if let Definition::Fragment(frag) = def {
112                    Some(frag)
113                } else {
114                    None
115                }
116            })
117            .collect()
118    }
119
120    fn count_fields_from_operations(operations: &[&OperationDefinition]) -> usize {
121        // Simplified field counting
122        operations
123            .iter()
124            .map(|op| op.selection_set.selections.len())
125            .sum()
126    }
127
128    fn calculate_depth_from_operations(operations: &[&OperationDefinition]) -> usize {
129        // Simplified depth calculation
130        operations
131            .iter()
132            .map(|op| Self::selection_depth(&op.selection_set.selections))
133            .max()
134            .unwrap_or(1)
135    }
136
137    fn selection_depth(selections: &[crate::ast::Selection]) -> usize {
138        selections
139            .iter()
140            .map(|sel| match sel {
141                crate::ast::Selection::Field(field) => {
142                    if let Some(ref selection_set) = field.selection_set {
143                        1 + Self::selection_depth(&selection_set.selections)
144                    } else {
145                        1
146                    }
147                }
148                crate::ast::Selection::InlineFragment(frag) => {
149                    1 + Self::selection_depth(&frag.selection_set.selections)
150                }
151                crate::ast::Selection::FragmentSpread(_) => 1,
152            })
153            .max()
154            .unwrap_or(1)
155    }
156
157    fn has_arguments_from_operations(operations: &[&OperationDefinition]) -> bool {
158        operations
159            .iter()
160            .any(|op| Self::selection_has_args(&op.selection_set.selections))
161    }
162
163    fn selection_has_args(selections: &[crate::ast::Selection]) -> bool {
164        selections.iter().any(|sel| match sel {
165            crate::ast::Selection::Field(field) => {
166                !field.arguments.is_empty()
167                    || field
168                        .selection_set
169                        .as_ref()
170                        .map(|ss| Self::selection_has_args(&ss.selections))
171                        .unwrap_or(false)
172            }
173            crate::ast::Selection::InlineFragment(frag) => {
174                Self::selection_has_args(&frag.selection_set.selections)
175            }
176            crate::ast::Selection::FragmentSpread(_) => false,
177        })
178    }
179
180    fn calculate_complexity_from_operations(operations: &[&OperationDefinition]) -> f64 {
181        let field_weight = Self::count_fields_from_operations(operations) as f64 * 1.0;
182        let depth_weight = Self::calculate_depth_from_operations(operations) as f64 * 2.0;
183        let args_weight = if Self::has_arguments_from_operations(operations) {
184            3.0
185        } else {
186            0.0
187        };
188
189        field_weight + depth_weight + args_weight
190    }
191
192    /// Calculate similarity to another pattern (0.0 to 1.0)
193    pub fn similarity(&self, other: &QueryPattern) -> f64 {
194        let type_similarity = if self.query_type == other.query_type {
195            1.0
196        } else {
197            0.0
198        };
199        let field_similarity =
200            1.0 - ((self.field_count as f64 - other.field_count as f64).abs() / 10.0).min(1.0);
201        let depth_similarity =
202            1.0 - ((self.depth as f64 - other.depth as f64).abs() / 5.0).min(1.0);
203        let args_similarity = if self.has_arguments == other.has_arguments {
204            1.0
205        } else {
206            0.5
207        };
208        let fragments_similarity = if self.has_fragments == other.has_fragments {
209            1.0
210        } else {
211            0.5
212        };
213        let complexity_similarity =
214            1.0 - ((self.complexity_score - other.complexity_score).abs() / 20.0).min(1.0);
215
216        type_similarity * 0.3
217            + field_similarity * 0.2
218            + depth_similarity * 0.2
219            + args_similarity * 0.1
220            + fragments_similarity * 0.1
221            + complexity_similarity * 0.1
222    }
223}
224
225/// Query usage statistics
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct QueryUsageStats {
228    pub hit_count: u64,
229    pub last_access: u64,
230    pub average_execution_time_ms: f64,
231    pub cache_hits: u64,
232    pub cache_misses: u64,
233    pub pattern: QueryPattern,
234}
235
236impl QueryUsageStats {
237    pub fn new(pattern: QueryPattern) -> Self {
238        let timestamp = SystemTime::now()
239            .duration_since(UNIX_EPOCH)
240            .unwrap_or_default()
241            .as_secs();
242
243        Self {
244            hit_count: 1,
245            last_access: timestamp,
246            average_execution_time_ms: 0.0,
247            cache_hits: 0,
248            cache_misses: 0,
249            pattern,
250        }
251    }
252
253    pub fn update_access(&mut self, execution_time_ms: f64, was_cache_hit: bool) {
254        self.hit_count += 1;
255        self.last_access = SystemTime::now()
256            .duration_since(UNIX_EPOCH)
257            .unwrap_or_default()
258            .as_secs();
259
260        // Update average execution time using exponential moving average
261        let alpha = 0.1; // Smoothing factor
262        self.average_execution_time_ms =
263            alpha * execution_time_ms + (1.0 - alpha) * self.average_execution_time_ms;
264
265        if was_cache_hit {
266            self.cache_hits += 1;
267        } else {
268            self.cache_misses += 1;
269        }
270    }
271
272    pub fn cache_hit_ratio(&self) -> f64 {
273        if self.cache_hits + self.cache_misses == 0 {
274            0.0
275        } else {
276            self.cache_hits as f64 / (self.cache_hits + self.cache_misses) as f64
277        }
278    }
279
280    pub fn access_frequency(&self) -> f64 {
281        let now = SystemTime::now()
282            .duration_since(UNIX_EPOCH)
283            .unwrap_or_default()
284            .as_secs();
285
286        let age_hours = ((now - self.pattern.timestamp) as f64) / 3600.0;
287        if age_hours == 0.0 {
288            self.hit_count as f64
289        } else {
290            self.hit_count as f64 / age_hours
291        }
292    }
293}
294
295/// Cache entry with metadata
296#[derive(Debug, Clone)]
297pub struct CacheEntry {
298    pub value: Value,
299    pub created_at: Instant,
300    pub last_accessed: Instant,
301    pub access_count: u64,
302    pub prediction_confidence: f64,
303}
304
305impl CacheEntry {
306    pub fn new(value: Value, prediction_confidence: f64) -> Self {
307        let now = Instant::now();
308        Self {
309            value,
310            created_at: now,
311            last_accessed: now,
312            access_count: 0,
313            prediction_confidence,
314        }
315    }
316
317    pub fn access(&mut self) -> &Value {
318        self.last_accessed = Instant::now();
319        self.access_count += 1;
320        &self.value
321    }
322
323    pub fn is_expired(&self, ttl: Duration) -> bool {
324        self.created_at.elapsed() > ttl
325    }
326
327    pub fn access_score(&self) -> f64 {
328        let recency_score = 1.0 / (1.0 + self.last_accessed.elapsed().as_secs() as f64 / 3600.0);
329        let frequency_score = (self.access_count as f64).ln().max(1.0);
330        let confidence_score = self.prediction_confidence;
331
332        recency_score * 0.4 + frequency_score * 0.3 + confidence_score * 0.3
333    }
334}
335
336/// Intelligent query cache with pattern learning and prediction
337pub struct IntelligentQueryCache {
338    config: IntelligentCacheConfig,
339    cache_entries: Arc<RwLock<HashMap<String, CacheEntry>>>,
340    usage_stats: Arc<RwLock<HashMap<String, QueryUsageStats>>>,
341    pattern_history: Arc<RwLock<VecDeque<QueryPattern>>>,
342    distributed_cache: Option<Arc<GraphQLQueryCache>>,
343}
344
345impl IntelligentQueryCache {
346    pub fn new(config: IntelligentCacheConfig) -> Self {
347        Self {
348            config,
349            cache_entries: Arc::new(RwLock::new(HashMap::new())),
350            usage_stats: Arc::new(RwLock::new(HashMap::new())),
351            pattern_history: Arc::new(RwLock::new(VecDeque::new())),
352            distributed_cache: None,
353        }
354    }
355
356    pub async fn with_distributed_cache(
357        mut self,
358        cache_config: CacheConfig,
359    ) -> anyhow::Result<Self> {
360        let distributed_cache = Arc::new(GraphQLQueryCache::new(cache_config).await?);
361        self.distributed_cache = Some(distributed_cache);
362        Ok(self)
363    }
364
365    /// Generate cache key for a query
366    pub fn generate_cache_key(&self, query: &str, variables: &HashMap<String, Value>) -> String {
367        use std::collections::hash_map::DefaultHasher;
368
369        let mut hasher = DefaultHasher::new();
370        query.hash(&mut hasher);
371
372        // Sort variables for consistent hashing
373        let mut var_pairs: Vec<_> = variables.iter().collect();
374        var_pairs.sort_by_key(|(k, _)| *k);
375
376        for (key, value) in var_pairs {
377            key.hash(&mut hasher);
378            format!("{value:?}").hash(&mut hasher);
379        }
380
381        format!("iqc:{:x}", hasher.finish())
382    }
383
384    /// Get cached query result
385    pub async fn get(&self, query: &str, variables: &HashMap<String, Value>) -> Option<Value> {
386        let cache_key = self.generate_cache_key(query, variables);
387        let start_time = Instant::now();
388
389        // Try local cache first
390        {
391            let mut cache = self.cache_entries.write().await;
392            if let Some(entry) = cache.get_mut(&cache_key) {
393                if !entry.is_expired(Duration::from_secs(self.config.cache_ttl_seconds)) {
394                    let result = entry.access().clone();
395
396                    // Update usage stats
397                    self.update_usage_stats(
398                        &cache_key,
399                        start_time.elapsed().as_millis() as f64,
400                        true,
401                    )
402                    .await;
403
404                    debug!("Cache hit for query key: {}", cache_key);
405                    return Some(result);
406                }
407            }
408        }
409
410        // Try distributed cache if available
411        if let Some(ref distributed_cache) = self.distributed_cache {
412            if let Ok(Some(data)) = distributed_cache.raw_get(&cache_key).await {
413                // Convert bytes back to Value
414                if let Ok(value) = serde_json::from_slice::<Value>(&data) {
415                    // Store in local cache for faster future access
416                    self.store_local(&cache_key, value.clone(), 0.8).await;
417
418                    // Update usage stats
419                    self.update_usage_stats(
420                        &cache_key,
421                        start_time.elapsed().as_millis() as f64,
422                        true,
423                    )
424                    .await;
425
426                    debug!("Distributed cache hit for query key: {}", cache_key);
427                    return Some(value);
428                }
429            }
430        }
431
432        // Cache miss
433        self.update_usage_stats(&cache_key, start_time.elapsed().as_millis() as f64, false)
434            .await;
435        None
436    }
437
438    /// Store query result in cache
439    pub async fn set(
440        &self,
441        query: &str,
442        variables: &HashMap<String, Value>,
443        result: Value,
444        doc: &Document,
445    ) -> anyhow::Result<()> {
446        let cache_key = self.generate_cache_key(query, variables);
447        let pattern = QueryPattern::from_document(doc);
448
449        // Analyze pattern and calculate prediction confidence
450        let confidence = self.calculate_prediction_confidence(&pattern).await;
451
452        // Store in local cache
453        self.store_local(&cache_key, result.clone(), confidence)
454            .await;
455
456        // Store in distributed cache if available
457        if let Some(ref distributed_cache) = self.distributed_cache {
458            if let Ok(data) = serde_json::to_vec(&result) {
459                if let Err(e) = distributed_cache
460                    .raw_set(
461                        &cache_key,
462                        data,
463                        Some(Duration::from_secs(self.config.cache_ttl_seconds)),
464                    )
465                    .await
466                {
467                    warn!("Failed to store in distributed cache: {}", e);
468                }
469            }
470        }
471
472        // Record pattern for learning
473        if self.config.enable_pattern_learning {
474            self.record_pattern(pattern).await;
475        }
476
477        // Update usage stats
478        {
479            let mut stats = self.usage_stats.write().await;
480            stats
481                .entry(cache_key)
482                .or_insert_with(|| QueryUsageStats::new(QueryPattern::from_document(doc)));
483        }
484
485        Ok(())
486    }
487
488    async fn store_local(&self, cache_key: &str, value: Value, confidence: f64) {
489        let mut cache = self.cache_entries.write().await;
490
491        // Evict expired entries and maintain size limit
492        let ttl = Duration::from_secs(self.config.cache_ttl_seconds);
493        cache.retain(|_, entry| !entry.is_expired(ttl));
494
495        // If cache is full, evict least valuable entries
496        while cache.len() >= self.config.max_cache_entries {
497            if let Some(evict_key) = cache
498                .iter()
499                .min_by(|(_, a), (_, b)| {
500                    a.access_score()
501                        .partial_cmp(&b.access_score())
502                        .unwrap_or(std::cmp::Ordering::Equal)
503                })
504                .map(|(k, _)| k.clone())
505            {
506                cache.remove(&evict_key);
507            } else {
508                break;
509            }
510        }
511
512        cache.insert(cache_key.to_string(), CacheEntry::new(value, confidence));
513    }
514
515    async fn calculate_prediction_confidence(&self, pattern: &QueryPattern) -> f64 {
516        if !self.config.enable_predictive_caching {
517            return 0.5; // Default confidence
518        }
519
520        let pattern_history = self.pattern_history.read().await;
521        let similar_patterns: Vec<_> = pattern_history
522            .iter()
523            .map(|p| (p.similarity(pattern), p))
524            .filter(|(sim, _)| *sim > 0.5)
525            .collect();
526
527        if similar_patterns.is_empty() {
528            return 0.3; // Low confidence for new patterns
529        }
530
531        // Calculate confidence based on similarity and frequency of similar patterns
532        let total_similarity: f64 = similar_patterns.iter().map(|(sim, _)| sim).sum();
533        let avg_similarity = total_similarity / similar_patterns.len() as f64;
534
535        // Higher confidence for patterns with more similar historical patterns
536        let frequency_boost = (similar_patterns.len() as f64 / 10.0).min(0.3);
537
538        (avg_similarity + frequency_boost).min(1.0)
539    }
540
541    async fn record_pattern(&self, pattern: QueryPattern) {
542        let mut history = self.pattern_history.write().await;
543
544        history.push_back(pattern);
545
546        // Maintain history size limit
547        while history.len() > self.config.max_pattern_history {
548            history.pop_front();
549        }
550    }
551
552    async fn update_usage_stats(
553        &self,
554        cache_key: &str,
555        execution_time_ms: f64,
556        was_cache_hit: bool,
557    ) {
558        if !self.config.enable_usage_analytics {
559            return;
560        }
561
562        let mut stats = self.usage_stats.write().await;
563        if let Some(stat) = stats.get_mut(cache_key) {
564            stat.update_access(execution_time_ms, was_cache_hit);
565        }
566    }
567
568    /// Get cache statistics
569    pub async fn get_statistics(&self) -> anyhow::Result<HashMap<String, serde_json::Value>> {
570        let mut stats = HashMap::new();
571
572        let cache_entries = self.cache_entries.read().await;
573        let usage_stats = self.usage_stats.read().await;
574        let pattern_history = self.pattern_history.read().await;
575
576        stats.insert(
577            "cache_size".to_string(),
578            serde_json::Value::Number(cache_entries.len().into()),
579        );
580
581        stats.insert(
582            "pattern_history_size".to_string(),
583            serde_json::Value::Number(pattern_history.len().into()),
584        );
585
586        stats.insert(
587            "usage_stats_size".to_string(),
588            serde_json::Value::Number(usage_stats.len().into()),
589        );
590
591        // Calculate overall cache hit ratio
592        let total_hits: u64 = usage_stats.values().map(|s| s.cache_hits).sum();
593        let total_misses: u64 = usage_stats.values().map(|s| s.cache_misses).sum();
594        let hit_ratio = if total_hits + total_misses > 0 {
595            total_hits as f64 / (total_hits + total_misses) as f64
596        } else {
597            0.0
598        };
599
600        stats.insert(
601            "overall_hit_ratio".to_string(),
602            serde_json::Value::Number(
603                serde_json::Number::from_f64(hit_ratio)
604                    .expect("hit_ratio should be a valid f64 for JSON"),
605            ),
606        );
607
608        // Average execution time
609        let avg_exec_time: f64 = usage_stats
610            .values()
611            .map(|s| s.average_execution_time_ms)
612            .sum::<f64>()
613            / usage_stats.len().max(1) as f64;
614
615        stats.insert(
616            "average_execution_time_ms".to_string(),
617            serde_json::Value::Number(
618                serde_json::Number::from_f64(avg_exec_time)
619                    .expect("avg_exec_time should be a valid f64 for JSON"),
620            ),
621        );
622
623        Ok(stats)
624    }
625
626    /// Predict queries that should be pre-cached based on patterns
627    pub async fn predict_queries(&self) -> Vec<(String, f64)> {
628        if !self.config.enable_predictive_caching {
629            return Vec::new();
630        }
631
632        let _pattern_history = self.pattern_history.read().await;
633        let usage_stats = self.usage_stats.read().await;
634
635        // Find patterns that occur frequently and might be requested soon
636        let mut predictions = Vec::new();
637
638        for (cache_key, stats) in usage_stats.iter() {
639            let frequency = stats.access_frequency();
640            let recency_factor = {
641                let hours_since_access = (SystemTime::now()
642                    .duration_since(UNIX_EPOCH)
643                    .unwrap_or_default()
644                    .as_secs()
645                    - stats.last_access) as f64
646                    / 3600.0;
647                1.0 / (1.0 + hours_since_access / 24.0) // Decay over days
648            };
649
650            let prediction_score = frequency * recency_factor * stats.cache_hit_ratio();
651
652            if prediction_score > self.config.prediction_confidence_threshold {
653                predictions.push((cache_key.clone(), prediction_score));
654            }
655        }
656
657        // Sort by prediction score descending
658        predictions.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
659
660        info!("Generated {} query predictions", predictions.len());
661        predictions
662    }
663
664    /// Clear expired entries from cache
665    pub async fn cleanup_expired(&self) -> usize {
666        let mut cache = self.cache_entries.write().await;
667        let initial_size = cache.len();
668
669        let ttl = Duration::from_secs(self.config.cache_ttl_seconds);
670        cache.retain(|_, entry| !entry.is_expired(ttl));
671
672        let removed = initial_size - cache.len();
673        if removed > 0 {
674            info!("Cleaned up {} expired cache entries", removed);
675        }
676
677        removed
678    }
679
680    /// Advanced performance analytics and optimization system
681    pub async fn get_advanced_analytics(&self) -> AdvancedCacheAnalytics {
682        let cache = self.cache_entries.read().await;
683        let stats = self.usage_stats.read().await;
684        let patterns = self.pattern_history.read().await;
685
686        let total_entries = cache.len();
687        let total_queries = stats.values().map(|s| s.hit_count as usize).sum::<usize>();
688        let total_hit_time: Duration = stats
689            .values()
690            .map(|s| Duration::from_millis(s.average_execution_time_ms as u64))
691            .sum();
692        let cache_efficiency = if total_queries > 0 {
693            stats.values().filter(|s| s.cache_hit_ratio() > 0.0).count() as f64
694                / total_queries as f64
695        } else {
696            0.0
697        };
698
699        // Calculate pattern diversity score
700        let unique_patterns = patterns.len();
701        let pattern_diversity = if unique_patterns > 0 {
702            1.0 - (patterns.iter().map(|p| p.complexity_score).sum::<f64>()
703                / (unique_patterns as f64).powi(2))
704        } else {
705            0.0
706        };
707
708        // Memory usage estimation
709        let estimated_memory_bytes = total_entries * 1024; // Rough estimate per entry
710
711        // Performance predictions
712        let predicted_hit_ratio = self.predict_future_hit_ratio(&stats).await;
713        let optimization_recommendations = self
714            .generate_optimization_recommendations(&cache, &stats)
715            .await;
716
717        AdvancedCacheAnalytics {
718            total_entries,
719            total_queries,
720            cache_efficiency,
721            pattern_diversity,
722            estimated_memory_bytes,
723            average_response_time: if total_queries > 0 {
724                total_hit_time / total_queries as u32
725            } else {
726                Duration::from_millis(0)
727            },
728            predicted_hit_ratio,
729            optimization_recommendations,
730            cache_heat_map: self.generate_cache_heat_map(&stats).await,
731            performance_trends: self.calculate_performance_trends(&patterns).await,
732        }
733    }
734
735    /// Predict future cache hit ratio based on current trends
736    async fn predict_future_hit_ratio(&self, stats: &HashMap<String, QueryUsageStats>) -> f64 {
737        if stats.is_empty() {
738            return 0.0;
739        }
740
741        let recent_hits: Vec<f64> = stats.values().map(|s| s.cache_hit_ratio()).collect();
742
743        // Simple exponential moving average prediction
744        let mut ema = recent_hits[0];
745        let alpha = 0.3; // Smoothing factor
746
747        for &hit_ratio in recent_hits.iter().skip(1) {
748            ema = alpha * hit_ratio + (1.0 - alpha) * ema;
749        }
750
751        ema.clamp(0.0, 1.0)
752    }
753
754    /// Generate optimization recommendations based on cache analysis
755    async fn generate_optimization_recommendations(
756        &self,
757        cache: &HashMap<String, CacheEntry>,
758        stats: &HashMap<String, QueryUsageStats>,
759    ) -> Vec<OptimizationRecommendation> {
760        let mut recommendations = Vec::new();
761
762        // Check for low hit ratio queries
763        for (key, stat) in stats {
764            if stat.cache_hit_ratio() < 0.3 && stat.hit_count > 10 {
765                recommendations.push(OptimizationRecommendation {
766                    recommendation_type: OptimizationType::IncreaseQueryTTL,
767                    query_pattern: key.clone(),
768                    impact_score: 0.8,
769                    description: format!("Query '{}' has low hit ratio ({:.2}%) but high access count ({}). Consider increasing TTL.", 
770                                       key, stat.cache_hit_ratio() * 100.0, stat.hit_count),
771                });
772            }
773        }
774
775        // Check for memory optimization opportunities
776        if cache.len() > self.config.max_cache_entries * 80 / 100 {
777            recommendations.push(OptimizationRecommendation {
778                recommendation_type: OptimizationType::MemoryOptimization,
779                query_pattern: "global".to_string(),
780                impact_score: 0.9,
781                description: "Cache is approaching capacity limit. Consider implementing more aggressive eviction policies.".to_string(),
782            });
783        }
784
785        // Check for pattern-based optimizations
786        if self.pattern_history.read().await.len() > 100 {
787            recommendations.push(OptimizationRecommendation {
788                recommendation_type: OptimizationType::PatternOptimization,
789                query_pattern: "patterns".to_string(),
790                impact_score: 0.7,
791                description: "Rich pattern history detected. Consider implementing predictive pre-caching for similar queries.".to_string(),
792            });
793        }
794
795        recommendations
796    }
797
798    /// Generate a heat map showing cache performance by query pattern
799    async fn generate_cache_heat_map(
800        &self,
801        stats: &HashMap<String, QueryUsageStats>,
802    ) -> CacheHeatMap {
803        let mut hot_queries = Vec::new();
804        let mut warm_queries = Vec::new();
805        let mut cold_queries = Vec::new();
806
807        for (key, stat) in stats {
808            let heat_score = stat.hit_count as f64 * stat.cache_hit_ratio();
809
810            if heat_score > 50.0 {
811                hot_queries.push((key.clone(), heat_score));
812            } else if heat_score > 10.0 {
813                warm_queries.push((key.clone(), heat_score));
814            } else {
815                cold_queries.push((key.clone(), heat_score));
816            }
817        }
818
819        // Sort by heat score
820        hot_queries.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
821        warm_queries.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
822        cold_queries.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
823
824        CacheHeatMap {
825            hot_queries: hot_queries.into_iter().take(10).collect(),
826            warm_queries: warm_queries.into_iter().take(20).collect(),
827            cold_queries: cold_queries.into_iter().take(10).collect(),
828        }
829    }
830
831    /// Calculate performance trends over time
832    async fn calculate_performance_trends(
833        &self,
834        patterns: &VecDeque<QueryPattern>,
835    ) -> PerformanceTrends {
836        if patterns.is_empty() {
837            return PerformanceTrends::default();
838        }
839
840        let recent_patterns: Vec<&QueryPattern> = patterns.iter().rev().take(50).collect();
841
842        let complexity_trend = self.calculate_complexity_trend(&recent_patterns);
843        let frequency_trend = self.calculate_frequency_trend(&recent_patterns);
844        let efficiency_trend = self.calculate_efficiency_trend(&recent_patterns);
845
846        PerformanceTrends {
847            complexity_trend,
848            frequency_trend,
849            efficiency_trend,
850            prediction_accuracy: self.calculate_prediction_accuracy(&recent_patterns),
851        }
852    }
853
854    fn calculate_complexity_trend(&self, patterns: &[&QueryPattern]) -> TrendDirection {
855        if patterns.len() < 10 {
856            return TrendDirection::Stable;
857        }
858
859        let recent_avg = patterns
860            .iter()
861            .rev()
862            .take(10)
863            .map(|p| p.complexity_score)
864            .sum::<f64>()
865            / 10.0;
866        let older_avg = patterns
867            .iter()
868            .take(10)
869            .map(|p| p.complexity_score)
870            .sum::<f64>()
871            / 10.0;
872
873        if recent_avg > older_avg * 1.1 {
874            TrendDirection::Increasing
875        } else if recent_avg < older_avg * 0.9 {
876            TrendDirection::Decreasing
877        } else {
878            TrendDirection::Stable
879        }
880    }
881
882    fn calculate_frequency_trend(&self, patterns: &[&QueryPattern]) -> TrendDirection {
883        if patterns.len() < 10 {
884            return TrendDirection::Stable;
885        }
886
887        let recent_avg = patterns
888            .iter()
889            .rev()
890            .take(10)
891            .map(|p| p.complexity_score)
892            .sum::<f64>()
893            / 10.0;
894        let older_avg = patterns
895            .iter()
896            .take(10)
897            .map(|p| p.complexity_score)
898            .sum::<f64>()
899            / 10.0;
900
901        if recent_avg > older_avg * 1.1 {
902            TrendDirection::Increasing
903        } else if recent_avg < older_avg * 0.9 {
904            TrendDirection::Decreasing
905        } else {
906            TrendDirection::Stable
907        }
908    }
909
910    fn calculate_efficiency_trend(&self, patterns: &[&QueryPattern]) -> TrendDirection {
911        // For efficiency, we use a combination of access frequency and execution time
912        if patterns.len() < 10 {
913            return TrendDirection::Stable;
914        }
915
916        let recent_efficiency: f64 = patterns
917            .iter()
918            .rev()
919            .take(10)
920            .map(|p| p.complexity_score / (p.field_count as f64 + 1.0))
921            .sum::<f64>()
922            / 10.0;
923
924        let older_efficiency: f64 = patterns
925            .iter()
926            .take(10)
927            .map(|p| p.complexity_score / (p.field_count as f64 + 1.0))
928            .sum::<f64>()
929            / 10.0;
930
931        if recent_efficiency > older_efficiency * 1.1 {
932            TrendDirection::Increasing
933        } else if recent_efficiency < older_efficiency * 0.9 {
934            TrendDirection::Decreasing
935        } else {
936            TrendDirection::Stable
937        }
938    }
939
940    fn calculate_prediction_accuracy(&self, patterns: &[&QueryPattern]) -> f64 {
941        // This would be implemented based on actual vs predicted access patterns
942        // For now, return a simulated accuracy based on pattern consistency
943        if patterns.is_empty() {
944            return 0.0;
945        }
946
947        let consistency_score = patterns
948            .iter()
949            .map(|p| p.complexity_score)
950            .collect::<Vec<_>>()
951            .windows(2)
952            .map(|w| (w[0] - w[1]).abs())
953            .sum::<f64>()
954            / patterns.len() as f64;
955
956        (1.0f64 - consistency_score.min(1.0f64)).max(0.0f64)
957    }
958}
959
960/// Advanced cache analytics data structure
961#[derive(Debug, Clone, Serialize, Deserialize)]
962pub struct AdvancedCacheAnalytics {
963    pub total_entries: usize,
964    pub total_queries: usize,
965    pub cache_efficiency: f64,
966    pub pattern_diversity: f64,
967    pub estimated_memory_bytes: usize,
968    pub average_response_time: Duration,
969    pub predicted_hit_ratio: f64,
970    pub optimization_recommendations: Vec<OptimizationRecommendation>,
971    pub cache_heat_map: CacheHeatMap,
972    pub performance_trends: PerformanceTrends,
973}
974
975/// Optimization recommendation
976#[derive(Debug, Clone, Serialize, Deserialize)]
977pub struct OptimizationRecommendation {
978    pub recommendation_type: OptimizationType,
979    pub query_pattern: String,
980    pub impact_score: f64,
981    pub description: String,
982}
983
984/// Types of optimizations that can be recommended
985#[derive(Debug, Clone, Serialize, Deserialize)]
986pub enum OptimizationType {
987    IncreaseQueryTTL,
988    DecreaseQueryTTL,
989    MemoryOptimization,
990    PatternOptimization,
991    PredictiveCaching,
992    EvictionPolicyAdjustment,
993}
994
995/// Cache heat map showing query performance
996#[derive(Debug, Clone, Serialize, Deserialize)]
997pub struct CacheHeatMap {
998    pub hot_queries: Vec<(String, f64)>,
999    pub warm_queries: Vec<(String, f64)>,
1000    pub cold_queries: Vec<(String, f64)>,
1001}
1002
1003/// Performance trends over time
1004#[derive(Debug, Clone, Serialize, Deserialize)]
1005pub struct PerformanceTrends {
1006    pub complexity_trend: TrendDirection,
1007    pub frequency_trend: TrendDirection,
1008    pub efficiency_trend: TrendDirection,
1009    pub prediction_accuracy: f64,
1010}
1011
1012impl Default for PerformanceTrends {
1013    fn default() -> Self {
1014        Self {
1015            complexity_trend: TrendDirection::Stable,
1016            frequency_trend: TrendDirection::Stable,
1017            efficiency_trend: TrendDirection::Stable,
1018            prediction_accuracy: 0.0,
1019        }
1020    }
1021}
1022
1023/// Direction of performance trends
1024#[derive(Debug, Clone, Serialize, Deserialize)]
1025pub enum TrendDirection {
1026    Increasing,
1027    Decreasing,
1028    Stable,
1029}
1030
1031#[cfg(test)]
1032mod tests {
1033    use super::*;
1034    use crate::ast::{Definition, Document, OperationDefinition, OperationType, SelectionSet};
1035
1036    #[tokio::test]
1037    async fn test_intelligent_cache_creation() {
1038        let config = IntelligentCacheConfig::default();
1039        let cache = IntelligentQueryCache::new(config);
1040
1041        assert!(cache.cache_entries.read().await.is_empty());
1042        assert!(cache.usage_stats.read().await.is_empty());
1043        assert!(cache.pattern_history.read().await.is_empty());
1044    }
1045
1046    #[tokio::test]
1047    async fn test_cache_key_generation() {
1048        let cache = IntelligentQueryCache::new(IntelligentCacheConfig::default());
1049
1050        let query = "query { user { name } }";
1051        let variables = HashMap::new();
1052
1053        let key1 = cache.generate_cache_key(query, &variables);
1054        let key2 = cache.generate_cache_key(query, &variables);
1055
1056        assert_eq!(key1, key2);
1057        assert!(key1.starts_with("iqc:"));
1058    }
1059
1060    #[tokio::test]
1061    async fn test_pattern_similarity() {
1062        let doc1 = Document {
1063            definitions: vec![Definition::Operation(OperationDefinition {
1064                operation_type: OperationType::Query,
1065                name: None,
1066                variable_definitions: Vec::new(),
1067                directives: Vec::new(),
1068                selection_set: SelectionSet {
1069                    selections: Vec::new(),
1070                },
1071            })],
1072        };
1073
1074        let pattern1 = QueryPattern::from_document(&doc1);
1075        let pattern2 = QueryPattern::from_document(&doc1);
1076
1077        let similarity = pattern1.similarity(&pattern2);
1078        assert!((similarity - 1.0).abs() < 0.001); // Should be identical
1079    }
1080
1081    #[tokio::test]
1082    async fn test_cache_statistics() {
1083        let cache = IntelligentQueryCache::new(IntelligentCacheConfig::default());
1084        let stats = cache.get_statistics().await.expect("should succeed");
1085
1086        assert!(stats.contains_key("cache_size"));
1087        assert!(stats.contains_key("pattern_history_size"));
1088        assert!(stats.contains_key("overall_hit_ratio"));
1089    }
1090}