use std::collections::{HashMap, VecDeque};
use std::sync::Mutex;
use crate::cypher::ast::Statement;
use crate::cypher::parser;
use crate::types::Result;
const DEFAULT_CAPACITY: usize = 128;
pub(crate) struct ParseCache {
inner: Mutex<Inner>,
}
struct Inner {
map: HashMap<String, Statement>,
order: VecDeque<String>,
capacity: usize,
}
impl ParseCache {
pub(crate) fn new() -> Self {
Self::with_capacity(DEFAULT_CAPACITY)
}
pub(crate) fn with_capacity(capacity: usize) -> Self {
Self {
inner: Mutex::new(Inner {
map: HashMap::with_capacity(capacity),
order: VecDeque::with_capacity(capacity),
capacity,
}),
}
}
pub(crate) fn get_or_parse(&self, cypher: &str) -> Result<Statement> {
if let Ok(guard) = self.inner.lock() {
if let Some(stmt) = guard.map.get(cypher) {
return Ok(stmt.clone());
}
}
let stmt = parser::parse(cypher)?;
if let Ok(mut guard) = self.inner.lock() {
if !guard.map.contains_key(cypher) {
if guard.map.len() >= guard.capacity {
if let Some(oldest) = guard.order.pop_front() {
guard.map.remove(&oldest);
}
}
guard.order.push_back(cypher.to_string());
guard.map.insert(cypher.to_string(), stmt.clone());
}
}
Ok(stmt)
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.inner.lock().map(|g| g.map.len()).unwrap_or(0)
}
}
impl Default for ParseCache {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hit_returns_equal_ast() {
let cache = ParseCache::with_capacity(8);
let q = "MATCH (n:Person) RETURN n";
let a = cache.get_or_parse(q).unwrap();
let b = cache.get_or_parse(q).unwrap();
assert_eq!(a, b);
assert_eq!(cache.len(), 1);
}
#[test]
fn miss_parses_and_inserts() {
let cache = ParseCache::with_capacity(8);
cache.get_or_parse("MATCH (n) RETURN n").unwrap();
cache.get_or_parse("MATCH (n:Person) RETURN n").unwrap();
assert_eq!(cache.len(), 2);
}
#[test]
fn evicts_oldest_at_capacity() {
let cache = ParseCache::with_capacity(2);
cache.get_or_parse("MATCH (n) RETURN n").unwrap();
cache.get_or_parse("MATCH (n:A) RETURN n").unwrap();
cache.get_or_parse("MATCH (n:B) RETURN n").unwrap();
assert_eq!(cache.len(), 2);
}
#[test]
fn parse_error_is_not_cached() {
let cache = ParseCache::with_capacity(8);
assert!(cache.get_or_parse("NOT VALID CYPHER").is_err());
assert_eq!(cache.len(), 0);
}
}