term_guard/optimizer/
stats_cache.rs

1//! Statistics caching for query optimization.
2
3use std::collections::HashMap;
4use std::time::{Duration, Instant};
5
6/// Cache entry with timestamp.
7#[derive(Debug, Clone)]
8struct CacheEntry {
9    value: f64,
10    timestamp: Instant,
11}
12
13/// Caches statistics to avoid redundant computations.
14#[derive(Debug)]
15pub struct StatsCache {
16    /// The cache storage
17    cache: HashMap<String, CacheEntry>,
18    /// Time-to-live for cache entries
19    ttl: Duration,
20    /// Maximum number of entries
21    max_entries: usize,
22}
23
24impl StatsCache {
25    /// Creates a new statistics cache.
26    pub fn new() -> Self {
27        Self {
28            cache: HashMap::new(),
29            ttl: Duration::from_secs(300), // 5 minutes default
30            max_entries: 1000,
31        }
32    }
33
34    /// Creates a cache with custom configuration.
35    pub fn with_config(ttl: Duration, max_entries: usize) -> Self {
36        Self {
37            cache: HashMap::new(),
38            ttl,
39            max_entries,
40        }
41    }
42
43    /// Gets a value from the cache.
44    pub fn get(&self, key: &str) -> Option<f64> {
45        self.cache.get(key).and_then(|entry| {
46            if entry.timestamp.elapsed() < self.ttl {
47                Some(entry.value)
48            } else {
49                None
50            }
51        })
52    }
53
54    /// Sets a value in the cache.
55    pub fn set(&mut self, key: String, value: f64) {
56        // Evict oldest entries if at capacity
57        if self.cache.len() >= self.max_entries {
58            self.evict_oldest();
59        }
60
61        self.cache.insert(
62            key,
63            CacheEntry {
64                value,
65                timestamp: Instant::now(),
66            },
67        );
68    }
69
70    /// Clears the entire cache.
71    pub fn clear(&mut self) {
72        self.cache.clear();
73    }
74
75    /// Removes expired entries.
76    pub fn remove_expired(&mut self) {
77        let now = Instant::now();
78        self.cache
79            .retain(|_, entry| now.duration_since(entry.timestamp) < self.ttl);
80    }
81
82    /// Gets the current size of the cache.
83    pub fn size(&self) -> usize {
84        self.cache.len()
85    }
86
87    /// Evicts the oldest entry.
88    fn evict_oldest(&mut self) {
89        if let Some((oldest_key, _)) = self
90            .cache
91            .iter()
92            .min_by_key(|(_, entry)| entry.timestamp)
93            .map(|(k, v)| (k.clone(), v.clone()))
94        {
95            self.cache.remove(&oldest_key);
96        }
97    }
98
99    /// Gets cache statistics.
100    pub fn stats(&self) -> CacheStats {
101        let total_entries = self.cache.len();
102        let expired_entries = self
103            .cache
104            .values()
105            .filter(|entry| entry.timestamp.elapsed() >= self.ttl)
106            .count();
107
108        CacheStats {
109            total_entries,
110            expired_entries,
111            active_entries: total_entries - expired_entries,
112        }
113    }
114}
115
116/// Statistics about the cache.
117#[derive(Debug)]
118pub struct CacheStats {
119    /// Total number of entries
120    pub total_entries: usize,
121    /// Number of expired entries
122    pub expired_entries: usize,
123    /// Number of active (non-expired) entries
124    pub active_entries: usize,
125}
126
127impl Default for StatsCache {
128    fn default() -> Self {
129        Self::new()
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_cache_basic_operations() {
139        let mut cache = StatsCache::new();
140
141        // Test set and get
142        cache.set("test_key".to_string(), 42.0);
143        assert_eq!(cache.get("test_key"), Some(42.0));
144
145        // Test non-existent key
146        assert_eq!(cache.get("missing_key"), None);
147    }
148
149    #[test]
150    fn test_cache_expiration() {
151        let mut cache = StatsCache::with_config(Duration::from_millis(100), 10);
152
153        cache.set("test_key".to_string(), 42.0);
154        assert_eq!(cache.get("test_key"), Some(42.0));
155
156        // Wait for expiration
157        std::thread::sleep(Duration::from_millis(150));
158        assert_eq!(cache.get("test_key"), None);
159    }
160
161    #[test]
162    fn test_cache_eviction() {
163        let mut cache = StatsCache::with_config(Duration::from_secs(60), 2);
164
165        cache.set("key1".to_string(), 1.0);
166        cache.set("key2".to_string(), 2.0);
167
168        // This should evict the oldest entry
169        cache.set("key3".to_string(), 3.0);
170
171        assert_eq!(cache.size(), 2);
172    }
173
174    #[test]
175    fn test_cache_stats() {
176        let mut cache = StatsCache::new();
177
178        cache.set("key1".to_string(), 1.0);
179        cache.set("key2".to_string(), 2.0);
180
181        let stats = cache.stats();
182        assert_eq!(stats.total_entries, 2);
183        assert_eq!(stats.active_entries, 2);
184        assert_eq!(stats.expired_entries, 0);
185    }
186}