1use std::collections::HashMap;
8use std::hash::{Hash, Hasher};
9use std::sync::{Arc, RwLock};
10
11use crate::backend::BackendDirection;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct BfsCacheKey {
16 pub start: i64,
17 pub depth: u32,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct KHopCacheKey {
23 pub start: i64,
24 pub depth: u32,
25 pub direction: BackendDirection,
26}
27
28#[derive(Debug, Clone)]
30pub struct KHopFilteredCacheKey {
31 pub start: i64,
32 pub depth: u32,
33 pub direction: BackendDirection,
34 pub allowed_edge_types: Vec<String>,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct ShortestPathCacheKey {
40 pub start: i64,
41 pub end: i64,
42}
43
44#[derive(Debug, Clone)]
46pub enum QueryCacheKey {
47 Bfs(BfsCacheKey),
48 KHop(KHopCacheKey),
49 KHopFiltered(KHopFilteredCacheKey),
50 ShortestPath(ShortestPathCacheKey),
51}
52
53#[derive(Debug, Clone)]
55pub struct QueryCacheEntry {
56 pub result: QueryResult,
57 }
59
60#[derive(Debug, Clone)]
62pub enum QueryResult {
63 Bfs(Vec<i64>),
64 KHop(Vec<i64>),
65 ShortestPath(Option<Vec<i64>>),
66}
67
68impl QueryCacheKey {
69 pub fn hash(&self) -> u64 {
71 let mut hasher = ahash::AHasher::default();
72 match self {
73 QueryCacheKey::Bfs(key) => {
74 0u8.hash(&mut hasher);
75 key.start.hash(&mut hasher);
76 key.depth.hash(&mut hasher);
77 }
78 QueryCacheKey::KHop(key) => {
79 1u8.hash(&mut hasher);
80 key.start.hash(&mut hasher);
81 key.depth.hash(&mut hasher);
82 (match key.direction {
83 BackendDirection::Outgoing => 0u8,
84 BackendDirection::Incoming => 1u8,
85 })
86 .hash(&mut hasher);
87 }
88 QueryCacheKey::KHopFiltered(key) => {
89 2u8.hash(&mut hasher);
90 key.start.hash(&mut hasher);
91 key.depth.hash(&mut hasher);
92 (match key.direction {
93 BackendDirection::Outgoing => 0u8,
94 BackendDirection::Incoming => 1u8,
95 })
96 .hash(&mut hasher);
97 key.allowed_edge_types.len().hash(&mut hasher);
98 for edge_type in &key.allowed_edge_types {
99 edge_type.hash(&mut hasher);
100 }
101 }
102 QueryCacheKey::ShortestPath(key) => {
103 3u8.hash(&mut hasher);
104 key.start.hash(&mut hasher);
105 key.end.hash(&mut hasher);
106 }
107 }
108 hasher.finish()
109 }
110}
111
112impl PartialEq for QueryCacheKey {
113 fn eq(&self, other: &Self) -> bool {
114 match (self, other) {
115 (QueryCacheKey::Bfs(a), QueryCacheKey::Bfs(b)) => a == b,
116 (QueryCacheKey::KHop(a), QueryCacheKey::KHop(b)) => a == b,
117 (QueryCacheKey::KHopFiltered(a), QueryCacheKey::KHopFiltered(b)) => {
118 a.start == b.start
119 && a.depth == b.depth
120 && a.direction == b.direction
121 && a.allowed_edge_types == b.allowed_edge_types
122 }
123 (QueryCacheKey::ShortestPath(a), QueryCacheKey::ShortestPath(b)) => a == b,
124 _ => false,
125 }
126 }
127}
128
129impl Eq for QueryCacheKey {}
130
131impl Hash for QueryCacheKey {
132 fn hash<H: Hasher>(&self, state: &mut H) {
133 self.hash().hash(state);
134 }
135}
136
137impl PartialEq for KHopFilteredCacheKey {
138 fn eq(&self, other: &Self) -> bool {
139 self.start == other.start
140 && self.depth == other.depth
141 && self.direction == other.direction
142 && self.allowed_edge_types == other.allowed_edge_types
143 }
144}
145
146impl Eq for KHopFilteredCacheKey {}
147
148impl Hash for KHopFilteredCacheKey {
149 fn hash<H: Hasher>(&self, state: &mut H) {
150 self.start.hash(state);
151 self.depth.hash(state);
152 (match self.direction {
153 BackendDirection::Outgoing => 0u8,
154 BackendDirection::Incoming => 1u8,
155 })
156 .hash(state);
157 self.allowed_edge_types.len().hash(state);
158 for edge_type in &self.allowed_edge_types {
159 edge_type.hash(state);
160 }
161 }
162}
163
164#[derive(Debug)]
166pub struct QueryCache {
167 cache: Arc<RwLock<HashMap<QueryCacheKey, QueryCacheEntry>>>,
168}
169
170impl QueryCache {
171 pub fn new() -> Self {
173 Self {
174 cache: Arc::new(RwLock::new(HashMap::new())),
175 }
176 }
177
178 pub fn get_bfs(&self, start: i64, depth: u32) -> Option<Vec<i64>> {
180 let key = QueryCacheKey::Bfs(BfsCacheKey { start, depth });
181
182 let cache = match self.cache.read() {
184 Ok(cache) => cache,
185 Err(poisoned) => {
186 eprintln!(
188 "WARNING: Query cache read lock poisoned in get_bfs operation (start={}, depth={}). Treating as cache miss.",
189 start, depth
190 );
191 poisoned.into_inner()
193 }
194 };
195
196 cache.get(&key).and_then(|entry| match &entry.result {
197 QueryResult::Bfs(result) => Some(result.clone()),
198 _ => None,
199 })
200 }
201
202 pub fn put_bfs(&self, start: i64, depth: u32, result: Vec<i64>) {
204 let key = QueryCacheKey::Bfs(BfsCacheKey { start, depth });
205 let entry = QueryCacheEntry {
206 result: QueryResult::Bfs(result),
207 };
208
209 match self.cache.write() {
211 Ok(mut cache) => {
212 cache.insert(key, entry);
213 }
214 Err(poisoned) => {
215 eprintln!(
217 "WARNING: Query cache write lock poisoned in put_bfs operation (start={}, depth={}). Recovering and continuing.",
218 start, depth
219 );
220 let mut cache = poisoned.into_inner();
221 cache.insert(key, entry);
222 }
223 }
224 }
225
226 pub fn get_k_hop(
228 &self,
229 start: i64,
230 depth: u32,
231 direction: BackendDirection,
232 ) -> Option<Vec<i64>> {
233 let key = QueryCacheKey::KHop(KHopCacheKey {
234 start,
235 depth,
236 direction,
237 });
238
239 let cache = match self.cache.read() {
241 Ok(cache) => cache,
242 Err(poisoned) => {
243 eprintln!(
245 "WARNING: Query cache read lock poisoned in get_k_hop operation (start={}, depth={}, direction={:?}). Treating as cache miss.",
246 start, depth, direction
247 );
248 poisoned.into_inner()
249 }
250 };
251
252 cache.get(&key).and_then(|entry| match &entry.result {
253 QueryResult::KHop(result) => Some(result.clone()),
254 _ => None,
255 })
256 }
257
258 pub fn put_k_hop(&self, start: i64, depth: u32, direction: BackendDirection, result: Vec<i64>) {
260 let key = QueryCacheKey::KHop(KHopCacheKey {
261 start,
262 depth,
263 direction,
264 });
265 let entry = QueryCacheEntry {
266 result: QueryResult::KHop(result),
267 };
268
269 match self.cache.write() {
271 Ok(mut cache) => {
272 cache.insert(key, entry);
273 }
274 Err(poisoned) => {
275 eprintln!(
277 "WARNING: Query cache write lock poisoned in put_k_hop operation (start={}, depth={}, direction={:?}). Recovering and continuing.",
278 start, depth, direction
279 );
280 let mut cache = poisoned.into_inner();
281 cache.insert(key, entry);
282 }
283 }
284 }
285
286 pub fn get_k_hop_filtered(
288 &self,
289 start: i64,
290 depth: u32,
291 direction: BackendDirection,
292 allowed_edge_types: &[&str],
293 ) -> Option<Vec<i64>> {
294 let edge_types = allowed_edge_types.iter().map(|s| s.to_string()).collect();
295 let key = QueryCacheKey::KHopFiltered(KHopFilteredCacheKey {
296 start,
297 depth,
298 direction,
299 allowed_edge_types: edge_types,
300 });
301
302 let cache = match self.cache.read() {
304 Ok(cache) => cache,
305 Err(poisoned) => {
306 eprintln!(
308 "WARNING: Query cache read lock poisoned in get_k_hop_filtered operation (start={}, depth={}, direction={:?}, edge_types={:?}). Treating as cache miss.",
309 start, depth, direction, allowed_edge_types
310 );
311 poisoned.into_inner()
312 }
313 };
314
315 cache.get(&key).and_then(|entry| match &entry.result {
316 QueryResult::KHop(result) => Some(result.clone()),
317 _ => None,
318 })
319 }
320
321 pub fn put_k_hop_filtered(
323 &self,
324 start: i64,
325 depth: u32,
326 direction: BackendDirection,
327 allowed_edge_types: &[&str],
328 result: Vec<i64>,
329 ) {
330 let edge_types = allowed_edge_types.iter().map(|s| s.to_string()).collect();
331 let key = QueryCacheKey::KHopFiltered(KHopFilteredCacheKey {
332 start,
333 depth,
334 direction,
335 allowed_edge_types: edge_types,
336 });
337 let entry = QueryCacheEntry {
338 result: QueryResult::KHop(result),
339 };
340
341 match self.cache.write() {
343 Ok(mut cache) => {
344 cache.insert(key, entry);
345 }
346 Err(poisoned) => {
347 eprintln!(
349 "WARNING: Query cache write lock poisoned in put_k_hop_filtered operation (start={}, depth={}, direction={:?}, edge_types={:?}). Recovering and continuing.",
350 start, depth, direction, allowed_edge_types
351 );
352 let mut cache = poisoned.into_inner();
353 cache.insert(key, entry);
354 }
355 }
356 }
357
358 pub fn get_shortest_path(&self, start: i64, end: i64) -> Option<Option<Vec<i64>>> {
360 let key = QueryCacheKey::ShortestPath(ShortestPathCacheKey { start, end });
361
362 let cache = match self.cache.read() {
364 Ok(cache) => cache,
365 Err(poisoned) => {
366 eprintln!(
368 "WARNING: Query cache read lock poisoned in get_shortest_path operation (start={}, end={}). Treating as cache miss.",
369 start, end
370 );
371 poisoned.into_inner()
372 }
373 };
374
375 cache.get(&key).and_then(|entry| match &entry.result {
376 QueryResult::ShortestPath(result) => Some(result.clone()),
377 _ => None,
378 })
379 }
380
381 pub fn put_shortest_path(&self, start: i64, end: i64, result: Option<Vec<i64>>) {
383 let key = QueryCacheKey::ShortestPath(ShortestPathCacheKey { start, end });
384 let entry = QueryCacheEntry {
385 result: QueryResult::ShortestPath(result),
386 };
387
388 match self.cache.write() {
390 Ok(mut cache) => {
391 cache.insert(key, entry);
392 }
393 Err(poisoned) => {
394 eprintln!(
396 "WARNING: Query cache write lock poisoned in put_shortest_path operation (start={}, end={}). Recovering and continuing.",
397 start, end
398 );
399 let mut cache = poisoned.into_inner();
400 cache.insert(key, entry);
401 }
402 }
403 }
404
405 pub fn invalidate_all(&self) {
407 match self.cache.write() {
409 Ok(mut cache) => {
410 cache.clear();
411 }
412 Err(poisoned) => {
413 eprintln!(
415 "WARNING: Query cache write lock poisoned in invalidate_all operation. Recovering and continuing."
416 );
417 let mut cache = poisoned.into_inner();
418 cache.clear();
419 }
420 }
421 }
422
423 pub fn size(&self) -> usize {
425 match self.cache.read() {
427 Ok(cache) => cache.len(),
428 Err(poisoned) => {
429 eprintln!(
431 "WARNING: Query cache read lock poisoned in size operation. Treating as empty cache."
432 );
433 poisoned.into_inner().len()
434 }
435 }
436 }
437
438 pub fn is_empty(&self) -> bool {
440 match self.cache.read() {
442 Ok(cache) => cache.is_empty(),
443 Err(poisoned) => {
444 eprintln!(
446 "WARNING: Query cache read lock poisoned in is_empty operation. Treating as empty cache."
447 );
448 poisoned.into_inner().is_empty()
449 }
450 }
451 }
452}
453
454impl Default for QueryCache {
455 fn default() -> Self {
456 Self::new()
457 }
458}
459
460impl Clone for QueryCache {
461 fn clone(&self) -> Self {
462 Self {
463 cache: Arc::clone(&self.cache),
464 }
465 }
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471
472 #[test]
473 fn test_cache_key_hashing() {
474 let key1 = QueryCacheKey::Bfs(BfsCacheKey {
476 start: 42,
477 depth: 3,
478 });
479 let key2 = QueryCacheKey::Bfs(BfsCacheKey {
480 start: 42,
481 depth: 3,
482 });
483 assert_eq!(key1.hash(), key2.hash());
484
485 let key3 = QueryCacheKey::Bfs(BfsCacheKey {
487 start: 42,
488 depth: 4,
489 });
490 assert_ne!(key1.hash(), key3.hash());
491 }
492
493 #[test]
494 fn test_cache_basic_operations() {
495 let cache = QueryCache::new();
496
497 assert_eq!(cache.get_bfs(1, 2), None);
499
500 cache.put_bfs(1, 2, vec![3, 4, 5]);
502 assert_eq!(cache.get_bfs(1, 2), Some(vec![3, 4, 5]));
503
504 assert_eq!(cache.size(), 1);
506 assert!(!cache.is_empty());
507
508 cache.invalidate_all();
510 assert_eq!(cache.get_bfs(1, 2), None);
511 assert_eq!(cache.size(), 0);
512 assert!(cache.is_empty());
513 }
514
515 #[test]
516 fn test_k_hop_filtered_cache() {
517 let cache = QueryCache::new();
518 let edge_types = vec!["friend", "colleague"];
519
520 assert_eq!(
522 cache.get_k_hop_filtered(1, 2, BackendDirection::Outgoing, &edge_types),
523 None
524 );
525
526 cache.put_k_hop_filtered(1, 2, BackendDirection::Outgoing, &edge_types, vec![3, 4]);
528 assert_eq!(
529 cache.get_k_hop_filtered(1, 2, BackendDirection::Outgoing, &edge_types),
530 Some(vec![3, 4])
531 );
532
533 assert_eq!(
535 cache.get_k_hop_filtered(1, 2, BackendDirection::Outgoing, &["enemy"]),
536 None
537 );
538 }
539
540 #[test]
541 fn test_shortest_path_cache() {
542 let cache = QueryCache::new();
543
544 cache.put_shortest_path(1, 5, None);
546 assert_eq!(cache.get_shortest_path(1, 5), Some(None));
547
548 cache.put_shortest_path(1, 3, Some(vec![1, 2, 3]));
550 assert_eq!(cache.get_shortest_path(1, 3), Some(Some(vec![1, 2, 3])));
551
552 assert_eq!(cache.size(), 2);
554 }
555}