use std::num::NonZeroUsize;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use lru::LruCache;
use parking_lot::Mutex;
use crate::lexical::query::Query;
#[derive(Debug)]
pub struct ParsedQueryCache {
inner: Option<Mutex<LruCache<String, Arc<dyn Query>>>>,
hits: AtomicU64,
misses: AtomicU64,
}
impl ParsedQueryCache {
pub fn new(capacity: usize) -> Self {
let inner = NonZeroUsize::new(capacity).map(|c| Mutex::new(LruCache::new(c)));
ParsedQueryCache {
inner,
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
}
}
pub fn get(&self, dsl: &str) -> Option<Arc<dyn Query>> {
let hit = self
.inner
.as_ref()
.and_then(|inner| inner.lock().get(dsl).cloned());
if hit.is_some() {
self.hits.fetch_add(1, Ordering::Relaxed);
} else {
self.misses.fetch_add(1, Ordering::Relaxed);
}
hit
}
pub fn put(&self, dsl: String, query: Arc<dyn Query>) {
if let Some(inner) = self.inner.as_ref() {
inner.lock().put(dsl, query);
}
}
pub fn is_enabled(&self) -> bool {
self.inner.is_some()
}
pub fn stats(&self) -> ParsedQueryCacheStats {
ParsedQueryCacheStats {
hits: self.hits.load(Ordering::Relaxed),
misses: self.misses.load(Ordering::Relaxed),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParsedQueryCacheStats {
pub hits: u64,
pub misses: u64,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lexical::query::term::TermQuery;
fn term(field: &str, t: &str) -> Arc<dyn Query> {
Arc::new(TermQuery::new(field, t))
}
#[test]
fn put_then_get_returns_cached_query() {
let cache = ParsedQueryCache::new(4);
cache.put("title:rust".to_string(), term("title", "rust"));
let got = cache.get("title:rust").expect("entry should be present");
assert_eq!(got.description(), "title:rust");
assert_eq!(cache.stats().hits, 1);
assert_eq!(cache.stats().misses, 0);
}
#[test]
fn miss_increments_miss_counter() {
let cache = ParsedQueryCache::new(4);
assert!(cache.get("absent:x").is_none());
assert_eq!(cache.stats().misses, 1);
assert_eq!(cache.stats().hits, 0);
}
#[test]
fn capacity_zero_disables_cache() {
let cache = ParsedQueryCache::new(0);
assert!(!cache.is_enabled());
cache.put("title:rust".to_string(), term("title", "rust"));
assert!(cache.get("title:rust").is_none());
}
#[test]
fn lru_evicts_least_recently_used() {
let cache = ParsedQueryCache::new(2);
cache.put("a:1".to_string(), term("a", "1"));
cache.put("b:1".to_string(), term("b", "1"));
assert!(cache.get("a:1").is_some());
cache.put("c:1".to_string(), term("c", "1"));
assert!(cache.get("a:1").is_some(), "recently used");
assert!(cache.get("c:1").is_some(), "just inserted");
assert!(cache.get("b:1").is_none(), "should have been evicted");
}
}