1use dashmap::DashMap;
32use parking_lot::Mutex;
33use std::collections::hash_map::DefaultHasher;
34use std::collections::VecDeque;
35use std::hash::{Hash, Hasher};
36use std::sync::atomic::{AtomicU64, Ordering};
37
38use super::Query;
39
40type CacheKey = (String, u64);
42
43#[derive(Clone, Debug)]
45struct CacheEntry {
46 merkle_root: Vec<u8>,
48 keys: Vec<String>,
50}
51
52pub struct SearchCache {
54 cache: DashMap<CacheKey, CacheEntry>,
56 order: Mutex<VecDeque<CacheKey>>,
58 max_entries: usize,
60 hits: AtomicU64,
62 misses: AtomicU64,
64 stale: AtomicU64,
66}
67
68#[derive(Debug, Clone)]
70pub struct SearchCacheStats {
71 pub hits: u64,
73 pub misses: u64,
75 pub stale: u64,
77 pub entry_count: usize,
79 pub hit_rate: f64,
81}
82
83impl SearchCache {
84 pub fn new(max_entries: usize) -> Self {
86 Self {
87 cache: DashMap::new(),
88 order: Mutex::new(VecDeque::new()),
89 max_entries,
90 hits: AtomicU64::new(0),
91 misses: AtomicU64::new(0),
92 stale: AtomicU64::new(0),
93 }
94 }
95
96 pub fn get(&self, prefix: &str, query: &Query, current_merkle_root: &[u8]) -> Option<Vec<String>> {
101 let key = self.cache_key(prefix, query);
102
103 if let Some(entry) = self.cache.get(&key) {
104 if entry.merkle_root == current_merkle_root {
105 self.hits.fetch_add(1, Ordering::Relaxed);
106 return Some(entry.keys.clone());
107 }
108 self.stale.fetch_add(1, Ordering::Relaxed);
110 drop(entry); self.cache.remove(&key);
112 }
113
114 self.misses.fetch_add(1, Ordering::Relaxed);
115 None
116 }
117
118 pub fn insert(&self, prefix: &str, query: &Query, merkle_root: Vec<u8>, keys: Vec<String>) {
120 let key = self.cache_key(prefix, query);
121
122 if self.cache.len() >= self.max_entries {
124 let mut order = self.order.lock();
125 while self.cache.len() >= self.max_entries {
126 if let Some(old_key) = order.pop_front() {
127 self.cache.remove(&old_key);
128 } else {
129 break;
130 }
131 }
132 }
133
134 let is_new = !self.cache.contains_key(&key);
136 self.cache.insert(key.clone(), CacheEntry { merkle_root, keys });
137
138 if is_new {
139 let mut order = self.order.lock();
140 order.push_back(key);
141 }
142 }
143
144 pub fn stats(&self) -> SearchCacheStats {
146 let hits = self.hits.load(Ordering::Relaxed);
147 let misses = self.misses.load(Ordering::Relaxed);
148 let total = hits + misses;
149
150 SearchCacheStats {
151 hits,
152 misses,
153 stale: self.stale.load(Ordering::Relaxed),
154 entry_count: self.cache.len(),
155 hit_rate: if total > 0 {
156 hits as f64 / total as f64
157 } else {
158 0.0
159 },
160 }
161 }
162
163 pub fn clear(&self) {
165 self.cache.clear();
166 self.order.lock().clear();
167 }
168
169 fn cache_key(&self, prefix: &str, query: &Query) -> CacheKey {
170 (prefix.to_string(), Self::hash_query(query))
171 }
172
173 fn hash_query(query: &Query) -> u64 {
174 let mut hasher = DefaultHasher::new();
177 format!("{:?}", query.root).hash(&mut hasher);
178 hasher.finish()
179 }
180}
181
182impl Default for SearchCache {
183 fn default() -> Self {
184 Self::new(1000)
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn test_cache_hit() {
195 let cache = SearchCache::new(100);
196 let query = Query::field_eq("name", "Alice");
197 let merkle_root = vec![1, 2, 3, 4];
198 let keys = vec!["crdt:users:1".to_string(), "crdt:users:2".to_string()];
199
200 cache.insert("crdt:users:", &query, merkle_root.clone(), keys.clone());
202
203 let result = cache.get("crdt:users:", &query, &merkle_root);
205 assert_eq!(result, Some(keys));
206
207 let stats = cache.stats();
208 assert_eq!(stats.hits, 1);
209 assert_eq!(stats.misses, 0);
210 }
211
212 #[test]
213 fn test_cache_miss() {
214 let cache = SearchCache::new(100);
215 let query = Query::field_eq("name", "Alice");
216 let merkle_root = vec![1, 2, 3, 4];
217
218 let result = cache.get("crdt:users:", &query, &merkle_root);
220 assert_eq!(result, None);
221
222 let stats = cache.stats();
223 assert_eq!(stats.hits, 0);
224 assert_eq!(stats.misses, 1);
225 }
226
227 #[test]
228 fn test_cache_stale_merkle() {
229 let cache = SearchCache::new(100);
230 let query = Query::field_eq("name", "Alice");
231 let old_root = vec![1, 2, 3, 4];
232 let new_root = vec![5, 6, 7, 8];
233 let keys = vec!["crdt:users:1".to_string()];
234
235 cache.insert("crdt:users:", &query, old_root, keys);
237
238 let result = cache.get("crdt:users:", &query, &new_root);
240 assert_eq!(result, None);
241
242 let stats = cache.stats();
243 assert_eq!(stats.hits, 0);
244 assert_eq!(stats.misses, 1);
245 assert_eq!(stats.stale, 1);
246 }
247
248 #[test]
249 fn test_different_queries() {
250 let cache = SearchCache::new(100);
251 let query1 = Query::field_eq("name", "Alice");
252 let query2 = Query::field_eq("name", "Bob");
253 let merkle_root = vec![1, 2, 3, 4];
254
255 cache.insert(
256 "crdt:users:",
257 &query1,
258 merkle_root.clone(),
259 vec!["alice".to_string()],
260 );
261 cache.insert(
262 "crdt:users:",
263 &query2,
264 merkle_root.clone(),
265 vec!["bob".to_string()],
266 );
267
268 assert_eq!(
270 cache.get("crdt:users:", &query1, &merkle_root),
271 Some(vec!["alice".to_string()])
272 );
273 assert_eq!(
274 cache.get("crdt:users:", &query2, &merkle_root),
275 Some(vec!["bob".to_string()])
276 );
277 }
278
279 #[test]
280 fn test_different_prefixes() {
281 let cache = SearchCache::new(100);
282 let query = Query::field_eq("name", "Alice");
283 let root1 = vec![1, 2, 3];
284 let root2 = vec![4, 5, 6];
285
286 cache.insert("crdt:users:", &query, root1.clone(), vec!["u1".to_string()]);
287 cache.insert("crdt:posts:", &query, root2.clone(), vec!["p1".to_string()]);
288
289 assert_eq!(
291 cache.get("crdt:users:", &query, &root1),
292 Some(vec!["u1".to_string()])
293 );
294 assert_eq!(
295 cache.get("crdt:posts:", &query, &root2),
296 Some(vec!["p1".to_string()])
297 );
298 }
299
300 #[test]
301 fn test_hit_rate() {
302 let cache = SearchCache::new(100);
303 let query = Query::field_eq("name", "Alice");
304 let merkle_root = vec![1, 2, 3, 4];
305 let keys = vec!["crdt:users:1".to_string()];
306
307 cache.insert("crdt:users:", &query, merkle_root.clone(), keys);
308
309 cache.get("crdt:users:", &query, &merkle_root);
311 cache.get("crdt:users:", &query, &merkle_root);
312 cache.get("crdt:users:", &query, &merkle_root);
313 cache.get("crdt:users:", &Query::field_eq("x", "y"), &merkle_root);
314
315 let stats = cache.stats();
316 assert_eq!(stats.hits, 3);
317 assert_eq!(stats.misses, 1);
318 assert!((stats.hit_rate - 0.75).abs() < 0.01);
319 }
320
321 #[test]
322 fn test_eviction_oldest() {
323 let cache = SearchCache::new(3); let root = vec![1, 2, 3];
325
326 cache.insert("p:", &Query::field_eq("n", "1"), root.clone(), vec!["k1".into()]);
328 cache.insert("p:", &Query::field_eq("n", "2"), root.clone(), vec!["k2".into()]);
329 cache.insert("p:", &Query::field_eq("n", "3"), root.clone(), vec!["k3".into()]);
330
331 assert_eq!(cache.stats().entry_count, 3);
332
333 cache.insert("p:", &Query::field_eq("n", "4"), root.clone(), vec!["k4".into()]);
335
336 assert_eq!(cache.stats().entry_count, 3);
337
338 assert!(cache.get("p:", &Query::field_eq("n", "1"), &root).is_none());
340 assert!(cache.get("p:", &Query::field_eq("n", "4"), &root).is_some());
342 }
343}