1use std::collections::HashMap;
7use std::sync::Arc;
8use std::time::{Duration, Instant};
9use tokio::sync::RwLock;
10
11#[derive(Debug, Clone)]
13struct CacheEntry<V> {
14 value: V,
16 expires_at: Instant,
18}
19
20#[derive(Clone)]
46pub struct QueryCache<K, V>
47where
48 K: Clone + Eq + std::hash::Hash + Send + Sync + 'static,
49 V: Clone + Send + Sync + 'static,
50{
51 max_size: usize,
53 ttl: Duration,
55 inner: Arc<RwLock<CacheInner<K, V>>>,
57}
58
59#[derive(Debug)]
61struct CacheInner<K, V>
62where
63 K: Clone + Eq + std::hash::Hash,
64 V: Clone,
65{
66 entries: HashMap<K, CacheEntry<V>>,
68 keys: Vec<K>,
70}
71
72impl<K, V> QueryCache<K, V>
73where
74 K: Clone + Eq + std::hash::Hash + Send + Sync + 'static,
75 V: Clone + Send + Sync + 'static,
76{
77 pub fn new(max_size: usize, ttl: Duration) -> Self {
84 Self {
85 max_size,
86 ttl,
87 inner: Arc::new(RwLock::new(CacheInner {
88 entries: HashMap::new(),
89 keys: Vec::new(),
90 })),
91 }
92 }
93
94 pub async fn get(&self, key: &K) -> Option<V> {
104 let mut inner = self.inner.write().await;
105 let now = Instant::now();
106
107 let key_clone = key.clone();
109 let value_opt = inner.entries.get(&key_clone).cloned();
110
111 if let Some(entry) = value_opt {
112 if now < entry.expires_at {
113 if let Some(pos) = inner.keys.iter().position(|k| k == &key_clone) {
115 inner.keys.remove(pos);
116 inner.keys.push(key_clone);
117 }
118 return Some(entry.value);
119 } else {
120 inner.entries.remove(&key_clone);
122 if let Some(pos) = inner.keys.iter().position(|k| k == &key_clone) {
123 inner.keys.remove(pos);
124 }
125 }
126 }
127 None
128 }
129
130 pub async fn insert(&self, key: K, value: V) {
139 let mut inner = self.inner.write().await;
140
141 if self.max_size == 0 {
143 return;
144 }
145
146 while inner.keys.len() >= self.max_size && !inner.keys.is_empty() {
148 if let Some(old_key) = inner.keys.first() {
150 let old_key = old_key.clone();
151 inner.keys.remove(0);
152 inner.entries.remove(&old_key);
153 }
154 }
155
156 let expires_at = Instant::now() + self.ttl;
157
158 if !inner.keys.contains(&key) {
160 inner.keys.push(key.clone());
161 }
162 inner.entries.insert(key, CacheEntry { value, expires_at });
163 }
164
165 pub async fn invalidate(&self, key: &K) {
171 let mut inner = self.inner.write().await;
172 inner.entries.remove(key);
173 if let Some(pos) = inner.keys.iter().position(|k| k == key) {
174 inner.keys.remove(pos);
175 }
176 }
177
178 pub async fn clear(&self) {
180 let mut inner = self.inner.write().await;
181 inner.entries.clear();
182 inner.keys.clear();
183 }
184
185 pub async fn len(&self) -> usize {
187 let inner = self.inner.read().await;
188 inner.entries.len()
189 }
190
191 pub async fn is_empty(&self) -> bool {
193 let inner = self.inner.read().await;
194 inner.entries.is_empty()
195 }
196}
197
198impl<K, V> std::fmt::Debug for QueryCache<K, V>
199where
200 K: Clone + Eq + std::hash::Hash + Send + Sync + 'static,
201 V: Clone + Send + Sync + 'static,
202{
203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204 f.debug_struct("QueryCache")
205 .field("max_size", &self.max_size)
206 .field("ttl", &self.ttl)
207 .field("inner", &"<cache>")
208 .finish()
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215 use std::time::Duration;
216
217 #[tokio::test]
218 async fn test_cache_insert_get() {
219 let cache = QueryCache::new(10, Duration::from_secs(60));
220
221 cache.insert("key1".to_string(), "value1".to_string()).await;
222 let value = cache.get(&"key1".to_string()).await;
223
224 assert_eq!(value, Some("value1".to_string()));
225 }
226
227 #[tokio::test]
228 async fn test_cache_miss() {
229 let cache = QueryCache::new(10, Duration::from_secs(60));
230
231 let value: Option<String> = cache.get(&"nonexistent".to_string()).await;
232 assert!(value.is_none());
233 }
234
235 #[tokio::test]
236 async fn test_cache_expiration() {
237 let cache = QueryCache::new(10, Duration::from_millis(50));
238
239 cache.insert("key".to_string(), "value".to_string()).await;
240
241 tokio::time::sleep(Duration::from_millis(100)).await;
243
244 let value = cache.get(&"key".to_string()).await;
245 assert!(value.is_none());
246 }
247
248 #[tokio::test]
249 async fn test_cache_eviction() {
250 let cache = QueryCache::new(2, Duration::from_secs(60));
251
252 cache.insert("key1".to_string(), "value1".to_string()).await;
253 cache.insert("key2".to_string(), "value2".to_string()).await;
254 cache.insert("key3".to_string(), "value3".to_string()).await;
255
256 assert_eq!(cache.len().await, 2);
258 assert!(cache.get(&"key1".to_string()).await.is_none());
259 assert_eq!(
260 cache.get(&"key2".to_string()).await,
261 Some("value2".to_string())
262 );
263 assert_eq!(
264 cache.get(&"key3".to_string()).await,
265 Some("value3".to_string())
266 );
267 }
268
269 #[tokio::test]
270 async fn test_cache_invalidate() {
271 let cache = QueryCache::new(10, Duration::from_secs(60));
272
273 cache.insert("key".to_string(), "value".to_string()).await;
274 cache.invalidate(&"key".to_string()).await;
275
276 assert!(cache.get(&"key".to_string()).await.is_none());
277 assert_eq!(cache.len().await, 0);
278 }
279
280 #[tokio::test]
281 async fn test_cache_clear() {
282 let cache = QueryCache::new(10, Duration::from_secs(60));
283
284 cache.insert("key1".to_string(), "value1".to_string()).await;
285 cache.insert("key2".to_string(), "value2".to_string()).await;
286 cache.clear().await;
287
288 assert!(cache.is_empty().await);
289 }
290
291 #[tokio::test]
292 async fn test_cache_lru_touch() {
293 let cache = QueryCache::new(3, Duration::from_secs(60));
294
295 cache.insert("key1".to_string(), "value1".to_string()).await;
297 cache.insert("key2".to_string(), "value2".to_string()).await;
298 cache.insert("key3".to_string(), "value3".to_string()).await;
299
300 let _ = cache.get(&"key1".to_string()).await;
302
303 cache.insert("key4".to_string(), "value4".to_string()).await;
305
306 assert_eq!(cache.len().await, 3);
308 assert!(cache.get(&"key2".to_string()).await.is_none());
309 assert_eq!(
310 cache.get(&"key1".to_string()).await,
311 Some("value1".to_string())
312 );
313 assert_eq!(
314 cache.get(&"key3".to_string()).await,
315 Some("value3".to_string())
316 );
317 assert_eq!(
318 cache.get(&"key4".to_string()).await,
319 Some("value4".to_string())
320 );
321 }
322
323 #[tokio::test]
324 async fn test_cache_update_existing() {
325 let cache = QueryCache::new(10, Duration::from_millis(100));
326
327 cache.insert("key".to_string(), "valueA".to_string()).await;
329
330 tokio::time::sleep(Duration::from_millis(50)).await;
332
333 cache.insert("key".to_string(), "valueB".to_string()).await;
335
336 assert_eq!(
338 cache.get(&"key".to_string()).await,
339 Some("valueB".to_string())
340 );
341
342 tokio::time::sleep(Duration::from_millis(60)).await;
344
345 assert_eq!(
347 cache.get(&"key".to_string()).await,
348 Some("valueB".to_string())
349 );
350 }
351
352 #[tokio::test]
353 async fn test_cache_concurrent_access() {
354 let cache = QueryCache::new(20, Duration::from_secs(60));
355 let mut handles = vec![];
356
357 for i in 0..10 {
359 let cache_clone = cache.clone();
360 handles.push(tokio::spawn(async move {
361 cache_clone
362 .insert(format!("key{}", i), format!("value{}", i))
363 .await;
364 }));
365 }
366
367 for handle in handles {
369 handle.await.unwrap();
370 }
371
372 assert_eq!(cache.len().await, 10);
374 for i in 0..10 {
375 assert_eq!(
376 cache.get(&format!("key{}", i)).await,
377 Some(format!("value{}", i))
378 );
379 }
380 }
381
382 #[tokio::test]
383 async fn test_cache_stress_eviction() {
384 let cache = QueryCache::new(5, Duration::from_secs(60));
385
386 for i in 0..100 {
388 cache
389 .insert(format!("key{}", i), format!("value{}", i))
390 .await;
391 }
392
393 assert_eq!(cache.len().await, 5);
395
396 assert!(cache.get(&"key0".to_string()).await.is_none());
398 assert!(cache.get(&"key94".to_string()).await.is_none());
399 assert_eq!(
400 cache.get(&"key95".to_string()).await,
401 Some("value95".to_string())
402 );
403 assert_eq!(
404 cache.get(&"key96".to_string()).await,
405 Some("value96".to_string())
406 );
407 assert_eq!(
408 cache.get(&"key97".to_string()).await,
409 Some("value97".to_string())
410 );
411 assert_eq!(
412 cache.get(&"key98".to_string()).await,
413 Some("value98".to_string())
414 );
415 assert_eq!(
416 cache.get(&"key99".to_string()).await,
417 Some("value99".to_string())
418 );
419 }
420
421 #[tokio::test]
422 async fn test_cache_zero_max_size() {
423 let cache = QueryCache::new(0, Duration::from_secs(60));
424
425 cache.insert("key1".to_string(), "value1".to_string()).await;
427 cache.insert("key2".to_string(), "value2".to_string()).await;
428
429 assert_eq!(cache.len().await, 0);
431 assert!(cache.is_empty().await);
432 assert!(cache.get(&"key1".to_string()).await.is_none());
433 assert!(cache.get(&"key2".to_string()).await.is_none());
434 }
435
436 #[tokio::test]
437 async fn test_cache_ttl_expiration() {
438 let cache = QueryCache::new(10, Duration::from_millis(100));
439
440 cache.insert("key".to_string(), "value".to_string()).await;
442 assert_eq!(cache.len().await, 1);
443
444 tokio::time::sleep(Duration::from_millis(150)).await;
446
447 assert!(cache.get(&"key".to_string()).await.is_none());
449
450 assert_eq!(cache.len().await, 0);
452 }
453}