use std::fmt;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use smallvec::SmallVec;
use super::LockFreeLruCache;
use crate::velesql::QueryPlan;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct PlanKey {
pub query_hash: u64,
pub schema_version: u64,
pub collection_generations: SmallVec<[u64; 4]>,
}
#[derive(Debug)]
pub struct CompiledPlan {
pub plan: QueryPlan,
pub referenced_collections: Vec<String>,
pub compiled_at: std::time::Instant,
pub reuse_count: AtomicU64,
}
#[derive(Debug, Default)]
pub struct PlanCacheMetrics {
pub hits: AtomicU64,
pub misses: AtomicU64,
}
impl PlanCacheMetrics {
pub fn record_hit(&self) {
self.hits.fetch_add(1, Ordering::Relaxed);
}
pub fn record_miss(&self) {
self.misses.fetch_add(1, Ordering::Relaxed);
}
#[must_use]
pub fn hits(&self) -> u64 {
self.hits.load(Ordering::Relaxed)
}
#[must_use]
pub fn misses(&self) -> u64 {
self.misses.load(Ordering::Relaxed)
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn hit_rate(&self) -> f64 {
let h = self.hits();
let m = self.misses();
let total = h + m;
if total == 0 {
0.0
} else {
h as f64 / total as f64
}
}
}
pub struct CompiledPlanCache {
cache: LockFreeLruCache<PlanKey, Arc<CompiledPlan>>,
metrics: PlanCacheMetrics,
}
impl fmt::Debug for CompiledPlanCache {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let stats = self.cache.stats();
f.debug_struct("CompiledPlanCache")
.field("l1_size", &stats.l1_size)
.field("l2_size", &stats.l2_size)
.field("hits", &self.metrics.hits())
.field("misses", &self.metrics.misses())
.finish()
}
}
impl CompiledPlanCache {
#[must_use]
pub fn new(l1_capacity: usize, l2_capacity: usize) -> Self {
Self {
cache: LockFreeLruCache::new(l1_capacity, l2_capacity),
metrics: PlanCacheMetrics::default(),
}
}
#[must_use]
pub fn contains(&self, key: &PlanKey) -> bool {
self.cache.peek_l1(key).is_some() || self.cache.peek_l2(key).is_some()
}
#[must_use]
pub fn get(&self, key: &PlanKey) -> Option<Arc<CompiledPlan>> {
if let Some(plan) = self.cache.get(key) {
self.metrics.record_hit();
plan.reuse_count.fetch_add(1, Ordering::Relaxed);
Some(plan)
} else {
self.metrics.record_miss();
None
}
}
pub fn insert(&self, key: PlanKey, plan: Arc<CompiledPlan>) {
self.cache.insert(key, plan);
}
#[must_use]
pub fn stats(&self) -> super::LockFreeCacheStats {
self.cache.stats()
}
#[must_use]
pub fn metrics(&self) -> &PlanCacheMetrics {
&self.metrics
}
pub fn clear(&self) {
self.cache.clear();
}
}