1use crate::query::QueryResult;
7use lru::LruCache;
8use std::hash::Hash;
9use std::num::NonZeroUsize;
10use std::sync::{Arc, Mutex};
11
12#[derive(Debug, Clone, Eq, PartialEq, Hash)]
14pub struct QueryCacheKey {
15 query: String,
17}
18
19impl QueryCacheKey {
20 pub fn new(query: impl Into<String>) -> Self {
22 let query = query.into();
23 let normalized = query.trim().to_lowercase();
25 Self {
26 query: normalized,
27 }
28 }
29}
30
31pub struct QueryCache {
33 cache: Arc<Mutex<LruCache<QueryCacheKey, QueryResult>>>,
35
36 hits: Arc<Mutex<usize>>,
38
39 misses: Arc<Mutex<usize>>,
41}
42
43impl QueryCache {
44 pub fn new(capacity: usize) -> Self {
49 let capacity = NonZeroUsize::new(capacity).unwrap_or(NonZeroUsize::new(100).unwrap());
50
51 Self {
52 cache: Arc::new(Mutex::new(LruCache::new(capacity))),
53 hits: Arc::new(Mutex::new(0)),
54 misses: Arc::new(Mutex::new(0)),
55 }
56 }
57
58 pub fn get(&self, key: &QueryCacheKey) -> Option<QueryResult> {
66 let mut cache = self.cache.lock().unwrap();
67 if let Some(result) = cache.get(key) {
68 *self.hits.lock().unwrap() += 1;
70 Some(result.clone())
71 } else {
72 *self.misses.lock().unwrap() += 1;
74 None
75 }
76 }
77
78 pub fn put(&self, key: QueryCacheKey, result: QueryResult) {
84 let mut cache = self.cache.lock().unwrap();
85 cache.put(key, result);
86 }
87
88 pub fn clear(&self) {
90 let mut cache = self.cache.lock().unwrap();
91 cache.clear();
92 *self.hits.lock().unwrap() = 0;
93 *self.misses.lock().unwrap() = 0;
94 }
95
96 pub fn len(&self) -> usize {
98 let cache = self.cache.lock().unwrap();
99 cache.len()
100 }
101
102 pub fn is_empty(&self) -> bool {
104 self.len() == 0
105 }
106
107 pub fn stats(&self) -> CacheStats {
109 let hits = *self.hits.lock().unwrap();
110 let misses = *self.misses.lock().unwrap();
111 let total = hits + misses;
112 let hit_rate = if total > 0 {
113 (hits as f64 / total as f64) * 100.0
114 } else {
115 0.0
116 };
117
118 CacheStats {
119 hits,
120 misses,
121 total_requests: total,
122 hit_rate,
123 entries: self.len(),
124 }
125 }
126}
127
128#[derive(Debug, Clone, PartialEq)]
130pub struct CacheStats {
131 pub hits: usize,
133
134 pub misses: usize,
136
137 pub total_requests: usize,
139
140 pub hit_rate: f64,
142
143 pub entries: usize,
145}
146
147impl std::fmt::Display for CacheStats {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 write!(
150 f,
151 "Cache Stats: {} hits, {} misses, {:.2}% hit rate, {} entries",
152 self.hits, self.misses, self.hit_rate, self.entries
153 )
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 fn create_dummy_result() -> QueryResult {
162 QueryResult::new_for_testing(Vec::new(), 0)
163 }
164
165 #[test]
166 fn test_cache_key_normalization() {
167 let key1 = QueryCacheKey::new("SELECT * FROM cube");
168 let key2 = QueryCacheKey::new(" select * from cube ");
169 assert_eq!(key1, key2);
170 }
171
172 #[test]
173 fn test_cache_put_get() {
174 let cache = QueryCache::new(10);
175 let key = QueryCacheKey::new("SELECT * FROM cube");
176 let result = create_dummy_result();
177
178 cache.put(key.clone(), result.clone());
179
180 let cached = cache.get(&key);
181 assert!(cached.is_some());
182 assert_eq!(cached.unwrap().row_count(), result.row_count());
183 }
184
185 #[test]
186 fn test_cache_miss() {
187 let cache = QueryCache::new(10);
188 let key = QueryCacheKey::new("SELECT * FROM cube");
189
190 let cached = cache.get(&key);
191 assert!(cached.is_none());
192 }
193
194 #[test]
195 fn test_cache_eviction() {
196 let cache = QueryCache::new(2);
197
198 cache.put(QueryCacheKey::new("query1"), create_dummy_result());
199 cache.put(QueryCacheKey::new("query2"), create_dummy_result());
200 cache.put(QueryCacheKey::new("query3"), create_dummy_result());
201
202 assert_eq!(cache.len(), 2);
204 }
205
206 #[test]
207 fn test_cache_clear() {
208 let cache = QueryCache::new(10);
209 cache.put(QueryCacheKey::new("query1"), create_dummy_result());
210 cache.put(QueryCacheKey::new("query2"), create_dummy_result());
211
212 assert_eq!(cache.len(), 2);
213
214 cache.clear();
215 assert_eq!(cache.len(), 0);
216 assert!(cache.is_empty());
217 }
218
219 #[test]
220 fn test_cache_stats() {
221 let cache = QueryCache::new(10);
222 let key = QueryCacheKey::new("SELECT * FROM cube");
223
224 cache.put(key.clone(), create_dummy_result());
225
226 cache.get(&key); cache.get(&QueryCacheKey::new("nonexistent")); let stats = cache.stats();
230 assert_eq!(stats.hits, 1);
231 assert_eq!(stats.misses, 1);
232 assert_eq!(stats.total_requests, 2);
233 assert_eq!(stats.hit_rate, 50.0);
234 }
235}