use std::collections::HashMap;
use std::time::{Duration, Instant};
use crate::algebra::Algebra;
#[derive(Clone)]
pub struct OptimizationCache {
plan_cache: HashMap<u64, CachedPlan>,
decision_cache: HashMap<u64, CachedDecision>,
config: CacheConfig,
statistics: CacheStatistics,
}
#[derive(Debug, Clone)]
pub struct CacheConfig {
pub max_plan_entries: usize,
pub max_decision_entries: usize,
pub ttl_seconds: u64,
pub enable_statistics: bool,
}
#[derive(Debug, Clone)]
pub struct CachedPlan {
pub optimized_plan: Algebra,
pub estimated_cost: f64,
pub creation_time: Instant,
pub access_count: usize,
pub last_access: Instant,
}
#[derive(Debug, Clone)]
pub struct CachedDecision {
pub decision_type: DecisionType,
pub confidence: f64,
pub estimated_benefit: f64,
pub context_hash: u64,
pub creation_time: Instant,
}
#[derive(Debug, Clone)]
pub enum DecisionType {
IndexSelection(String),
JoinAlgorithm(String),
StreamingStrategy(String),
ParallelismDegree(usize),
MaterializeView(String),
}
#[derive(Debug, Default, Clone)]
pub struct CacheStatistics {
pub plan_hits: usize,
pub plan_misses: usize,
pub decision_hits: usize,
pub decision_misses: usize,
pub evictions: usize,
pub total_lookups: usize,
}
impl OptimizationCache {
pub fn new(config: CacheConfig) -> Self {
Self {
plan_cache: HashMap::new(),
decision_cache: HashMap::new(),
config,
statistics: CacheStatistics::default(),
}
}
pub fn cache_plan(&mut self, query_hash: u64, plan: Algebra, cost: f64) {
if self.plan_cache.len() >= self.config.max_plan_entries {
self.evict_least_recently_used_plan();
}
let cached_plan = CachedPlan {
optimized_plan: plan,
estimated_cost: cost,
creation_time: Instant::now(),
access_count: 0,
last_access: Instant::now(),
};
self.plan_cache.insert(query_hash, cached_plan);
}
pub fn get_cached_plan(&mut self, query_hash: u64) -> Option<Algebra> {
self.statistics.total_lookups += 1;
let should_remove = if let Some(cached) = self.plan_cache.get(&query_hash) {
self.is_expired(cached.creation_time)
} else {
false
};
if should_remove {
self.plan_cache.remove(&query_hash);
self.statistics.plan_misses += 1;
return None;
}
if let Some(cached) = self.plan_cache.get_mut(&query_hash) {
cached.access_count += 1;
cached.last_access = Instant::now();
self.statistics.plan_hits += 1;
Some(cached.optimized_plan.clone())
} else {
self.statistics.plan_misses += 1;
None
}
}
pub fn cache_decision(&mut self, context_hash: u64, decision: CachedDecision) {
if self.decision_cache.len() >= self.config.max_decision_entries {
self.evict_oldest_decision();
}
self.decision_cache.insert(context_hash, decision);
}
pub fn get_cached_decision(&mut self, context_hash: u64) -> Option<CachedDecision> {
self.statistics.total_lookups += 1;
let should_remove = if let Some(decision) = self.decision_cache.get(&context_hash) {
self.is_expired(decision.creation_time)
} else {
false
};
if should_remove {
self.decision_cache.remove(&context_hash);
self.statistics.decision_misses += 1;
return None;
}
if let Some(decision) = self.decision_cache.get(&context_hash) {
self.statistics.decision_hits += 1;
Some(decision.clone())
} else {
self.statistics.decision_misses += 1;
None
}
}
pub fn statistics(&self) -> &CacheStatistics {
&self.statistics
}
pub fn clear(&mut self) {
self.plan_cache.clear();
self.decision_cache.clear();
self.statistics = CacheStatistics::default();
}
pub fn plan_hit_ratio(&self) -> f64 {
let total = self.statistics.plan_hits + self.statistics.plan_misses;
if total == 0 {
0.0
} else {
self.statistics.plan_hits as f64 / total as f64
}
}
pub fn decision_hit_ratio(&self) -> f64 {
let total = self.statistics.decision_hits + self.statistics.decision_misses;
if total == 0 {
0.0
} else {
self.statistics.decision_hits as f64 / total as f64
}
}
pub fn hit_ratio(&self) -> f64 {
let total_hits = self.statistics.plan_hits + self.statistics.decision_hits;
let total_misses = self.statistics.plan_misses + self.statistics.decision_misses;
let total = total_hits + total_misses;
if total == 0 {
0.0
} else {
total_hits as f64 / total as f64
}
}
pub fn total_requests(&self) -> usize {
self.statistics.total_lookups
}
fn is_expired(&self, creation_time: Instant) -> bool {
creation_time.elapsed() > Duration::from_secs(self.config.ttl_seconds)
}
fn evict_least_recently_used_plan(&mut self) {
if let Some((&key, _)) = self
.plan_cache
.iter()
.min_by_key(|(_, cached)| cached.last_access)
{
self.plan_cache.remove(&key);
self.statistics.evictions += 1;
}
}
fn evict_oldest_decision(&mut self) {
if let Some((&key, _)) = self
.decision_cache
.iter()
.min_by_key(|(_, decision)| decision.creation_time)
{
self.decision_cache.remove(&key);
self.statistics.evictions += 1;
}
}
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
max_plan_entries: 1000,
max_decision_entries: 5000,
ttl_seconds: 3600, enable_statistics: true,
}
}
}