1use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
2use std::sync::Mutex;
3use std::time::{Duration, Instant};
4
5use crate::Plugin;
6use serde::Serialize;
7
8#[derive(Debug, Clone)]
14#[allow(dead_code)]
15enum CacheValue {
16 String(String),
17 Int(i64),
18 Float(f64),
19 List(VecDeque<String>),
20 Set(HashSet<String>),
21 Hash(HashMap<String, String>),
22 SortedSet(BTreeMap<String, f64>),
23}
24
25struct CacheEntry {
27 value: CacheValue,
28 expires_at: Option<Instant>,
29 #[allow(dead_code)]
30 created_at: Instant,
31 last_accessed: Instant,
32}
33
34impl CacheEntry {
35 fn new(value: CacheValue, ttl: Option<u64>) -> Self {
36 let now = Instant::now();
37 Self {
38 value,
39 expires_at: ttl.map(|s| now + Duration::from_secs(s)),
40 created_at: now,
41 last_accessed: now,
42 }
43 }
44
45 fn is_expired(&self) -> bool {
46 self.expires_at
47 .map(|exp| Instant::now() >= exp)
48 .unwrap_or(false)
49 }
50
51 fn touch(&mut self) {
52 self.last_accessed = Instant::now();
53 }
54}
55
56pub struct CachePlugin {
58 store: Mutex<HashMap<String, CacheEntry>>,
59 max_keys: usize,
60 stats: Mutex<CacheStats>,
61}
62
63#[derive(Debug, Clone, Default, Serialize)]
64pub struct CacheStats {
65 pub hits: u64,
66 pub misses: u64,
67 pub sets: u64,
68 pub deletes: u64,
69 pub evictions: u64,
70 pub expired: u64,
71}
72
73fn glob_match(pattern: &str, text: &str) -> bool {
78 let mut pi = 0;
79 let mut ti = 0;
80 let pb = pattern.as_bytes();
81 let tb = text.as_bytes();
82 let mut star_pi = usize::MAX;
83 let mut star_ti = 0;
84
85 while ti < tb.len() {
86 if pi < pb.len() && (pb[pi] == b'?' || pb[pi] == tb[ti]) {
87 pi += 1;
88 ti += 1;
89 } else if pi < pb.len() && pb[pi] == b'*' {
90 star_pi = pi;
91 star_ti = ti;
92 pi += 1;
93 } else if star_pi != usize::MAX {
94 pi = star_pi + 1;
95 star_ti += 1;
96 ti = star_ti;
97 } else {
98 return false;
99 }
100 }
101 while pi < pb.len() && pb[pi] == b'*' {
102 pi += 1;
103 }
104 pi == pb.len()
105}
106
107impl CachePlugin {
112 pub fn new(max_keys: usize) -> Self {
113 Self {
114 store: Mutex::new(HashMap::new()),
115 max_keys,
116 stats: Mutex::new(CacheStats::default()),
117 }
118 }
119
120 fn record_hit(&self) {
124 self.stats.lock().unwrap().hits += 1;
125 }
126
127 fn record_miss(&self) {
129 self.stats.lock().unwrap().misses += 1;
130 }
131
132 fn evict_lru(&self, store: &mut HashMap<String, CacheEntry>) {
135 if store.len() < self.max_keys {
136 return;
137 }
138
139 let victim = store
141 .iter()
142 .min_by_key(|(_, entry)| entry.last_accessed)
143 .map(|(k, _)| k.clone());
144
145 if let Some(key) = victim {
146 store.remove(&key);
147 self.stats.lock().unwrap().evictions += 1;
148 }
149 }
150
151 fn remove_if_expired(&self, store: &mut HashMap<String, CacheEntry>, key: &str) -> bool {
154 let expired = store.get(key).map(|e| e.is_expired()).unwrap_or(false);
155 if expired {
156 store.remove(key);
157 self.stats.lock().unwrap().expired += 1;
158 }
159 expired
160 }
161
162 pub fn set(&self, key: &str, value: &str, ttl: Option<u64>) {
168 let mut store = self.store.lock().unwrap();
169 self.evict_lru(&mut store);
170 store.insert(
171 key.to_string(),
172 CacheEntry::new(CacheValue::String(value.to_string()), ttl),
173 );
174 self.stats.lock().unwrap().sets += 1;
175 }
176
177 pub fn get(&self, key: &str) -> Option<String> {
179 let mut store = self.store.lock().unwrap();
180 if self.remove_if_expired(&mut store, key) {
181 self.record_miss();
182 return None;
183 }
184 match store.get_mut(key) {
185 Some(entry) => {
186 entry.touch();
187 let val = match &entry.value {
188 CacheValue::String(s) => Some(s.clone()),
189 CacheValue::Int(n) => Some(n.to_string()),
190 CacheValue::Float(f) => Some(f.to_string()),
191 _ => None,
192 };
193 if val.is_some() {
194 self.record_hit();
195 } else {
196 self.record_miss();
197 }
198 val
199 }
200 None => {
201 self.record_miss();
202 None
203 }
204 }
205 }
206
207 pub fn del(&self, key: &str) -> bool {
209 let mut store = self.store.lock().unwrap();
210 let existed = store.remove(key).is_some();
211 if existed {
212 self.stats.lock().unwrap().deletes += 1;
213 }
214 existed
215 }
216
217 pub fn exists(&self, key: &str) -> bool {
219 let mut store = self.store.lock().unwrap();
220 if self.remove_if_expired(&mut store, key) {
221 return false;
222 }
223 store.contains_key(key)
224 }
225
226 pub fn incr(&self, key: &str) -> Result<i64, String> {
228 self.incrby(key, 1)
229 }
230
231 pub fn decr(&self, key: &str) -> Result<i64, String> {
233 self.incrby(key, -1)
234 }
235
236 pub fn incrby(&self, key: &str, amount: i64) -> Result<i64, String> {
238 let mut store = self.store.lock().unwrap();
239 self.remove_if_expired(&mut store, key);
240
241 match store.get_mut(key) {
242 Some(entry) => {
243 entry.touch();
244 match &mut entry.value {
245 CacheValue::Int(n) => {
246 *n += amount;
247 Ok(*n)
248 }
249 CacheValue::String(s) => {
250 let n: i64 = s
251 .parse()
252 .map_err(|_| "value is not an integer".to_string())?;
253 let new_val = n + amount;
254 entry.value = CacheValue::Int(new_val);
255 Ok(new_val)
256 }
257 _ => Err("value is not an integer".to_string()),
258 }
259 }
260 None => {
261 self.evict_lru(&mut store);
262 store.insert(
263 key.to_string(),
264 CacheEntry::new(CacheValue::Int(amount), None),
265 );
266 Ok(amount)
267 }
268 }
269 }
270
271 pub fn setnx(&self, key: &str, value: &str, ttl: Option<u64>) -> bool {
273 let mut store = self.store.lock().unwrap();
274 self.remove_if_expired(&mut store, key);
275
276 if store.contains_key(key) {
277 return false;
278 }
279
280 self.evict_lru(&mut store);
281 store.insert(
282 key.to_string(),
283 CacheEntry::new(CacheValue::String(value.to_string()), ttl),
284 );
285 self.stats.lock().unwrap().sets += 1;
286 true
287 }
288
289 pub fn getset(&self, key: &str, value: &str) -> Option<String> {
291 let mut store = self.store.lock().unwrap();
292 self.remove_if_expired(&mut store, key);
293
294 let old = store.get(key).and_then(|entry| match &entry.value {
295 CacheValue::String(s) => Some(s.clone()),
296 CacheValue::Int(n) => Some(n.to_string()),
297 CacheValue::Float(f) => Some(f.to_string()),
298 _ => None,
299 });
300
301 self.evict_lru(&mut store);
302 store.insert(
303 key.to_string(),
304 CacheEntry::new(CacheValue::String(value.to_string()), None),
305 );
306 self.stats.lock().unwrap().sets += 1;
307 old
308 }
309
310 pub fn mget(&self, keys: &[&str]) -> Vec<Option<String>> {
312 let mut store = self.store.lock().unwrap();
313 keys.iter()
314 .map(|key| {
315 if self.remove_if_expired(&mut store, key) {
316 self.record_miss();
317 return None;
318 }
319 match store.get_mut(*key) {
320 Some(entry) => {
321 entry.touch();
322 match &entry.value {
323 CacheValue::String(s) => {
324 self.record_hit();
325 Some(s.clone())
326 }
327 CacheValue::Int(n) => {
328 self.record_hit();
329 Some(n.to_string())
330 }
331 CacheValue::Float(f) => {
332 self.record_hit();
333 Some(f.to_string())
334 }
335 _ => {
336 self.record_miss();
337 None
338 }
339 }
340 }
341 None => {
342 self.record_miss();
343 None
344 }
345 }
346 })
347 .collect()
348 }
349
350 pub fn mset(&self, pairs: &[(&str, &str)]) {
352 let mut store = self.store.lock().unwrap();
353 for (key, value) in pairs {
354 self.evict_lru(&mut store);
355 store.insert(
356 key.to_string(),
357 CacheEntry::new(CacheValue::String(value.to_string()), None),
358 );
359 self.stats.lock().unwrap().sets += 1;
360 }
361 }
362
363 pub fn ttl(&self, key: &str) -> i64 {
365 let mut store = self.store.lock().unwrap();
366 if self.remove_if_expired(&mut store, key) {
367 return -2;
368 }
369 match store.get(key) {
370 Some(entry) => match entry.expires_at {
371 Some(exp) => {
372 let now = Instant::now();
373 if exp > now {
374 (exp - now).as_secs() as i64
375 } else {
376 -2
377 }
378 }
379 None => -1,
380 },
381 None => -2,
382 }
383 }
384
385 pub fn expire(&self, key: &str, seconds: u64) -> bool {
387 let mut store = self.store.lock().unwrap();
388 if self.remove_if_expired(&mut store, key) {
389 return false;
390 }
391 match store.get_mut(key) {
392 Some(entry) => {
393 entry.expires_at = Some(Instant::now() + Duration::from_secs(seconds));
394 true
395 }
396 None => false,
397 }
398 }
399
400 pub fn persist(&self, key: &str) -> bool {
402 let mut store = self.store.lock().unwrap();
403 if self.remove_if_expired(&mut store, key) {
404 return false;
405 }
406 match store.get_mut(key) {
407 Some(entry) => {
408 let had_expiry = entry.expires_at.is_some();
409 entry.expires_at = None;
410 had_expiry
411 }
412 None => false,
413 }
414 }
415
416 pub fn keys(&self, pattern: &str) -> Vec<String> {
418 let mut store = self.store.lock().unwrap();
419
420 let expired: Vec<String> = store
422 .iter()
423 .filter(|(_, entry)| entry.is_expired())
424 .map(|(k, _)| k.clone())
425 .collect();
426 for k in &expired {
427 store.remove(k);
428 }
429 {
430 let mut stats = self.stats.lock().unwrap();
431 stats.expired += expired.len() as u64;
432 }
433
434 store
435 .keys()
436 .filter(|k| glob_match(pattern, k))
437 .cloned()
438 .collect()
439 }
440
441 pub fn lpush(&self, key: &str, value: &str) -> usize {
447 let mut store = self.store.lock().unwrap();
448 self.remove_if_expired(&mut store, key);
449
450 match store.get_mut(key) {
451 Some(entry) => {
452 entry.touch();
453 if let CacheValue::List(list) = &mut entry.value {
454 list.push_front(value.to_string());
455 list.len()
456 } else {
457 let mut list = VecDeque::new();
459 list.push_front(value.to_string());
460 let len = list.len();
461 entry.value = CacheValue::List(list);
462 len
463 }
464 }
465 None => {
466 self.evict_lru(&mut store);
467 let mut list = VecDeque::new();
468 list.push_front(value.to_string());
469 store.insert(
470 key.to_string(),
471 CacheEntry::new(CacheValue::List(list), None),
472 );
473 1
474 }
475 }
476 }
477
478 pub fn rpush(&self, key: &str, value: &str) -> usize {
480 let mut store = self.store.lock().unwrap();
481 self.remove_if_expired(&mut store, key);
482
483 match store.get_mut(key) {
484 Some(entry) => {
485 entry.touch();
486 if let CacheValue::List(list) = &mut entry.value {
487 list.push_back(value.to_string());
488 list.len()
489 } else {
490 let mut list = VecDeque::new();
491 list.push_back(value.to_string());
492 let len = list.len();
493 entry.value = CacheValue::List(list);
494 len
495 }
496 }
497 None => {
498 self.evict_lru(&mut store);
499 let mut list = VecDeque::new();
500 list.push_back(value.to_string());
501 store.insert(
502 key.to_string(),
503 CacheEntry::new(CacheValue::List(list), None),
504 );
505 1
506 }
507 }
508 }
509
510 pub fn lpop(&self, key: &str) -> Option<String> {
512 let mut store = self.store.lock().unwrap();
513 if self.remove_if_expired(&mut store, key) {
514 return None;
515 }
516 let entry = store.get_mut(key)?;
517 entry.touch();
518 if let CacheValue::List(list) = &mut entry.value {
519 list.pop_front()
520 } else {
521 None
522 }
523 }
524
525 pub fn rpop(&self, key: &str) -> Option<String> {
527 let mut store = self.store.lock().unwrap();
528 if self.remove_if_expired(&mut store, key) {
529 return None;
530 }
531 let entry = store.get_mut(key)?;
532 entry.touch();
533 if let CacheValue::List(list) = &mut entry.value {
534 list.pop_back()
535 } else {
536 None
537 }
538 }
539
540 pub fn lrange(&self, key: &str, start: i64, stop: i64) -> Vec<String> {
542 let mut store = self.store.lock().unwrap();
543 if self.remove_if_expired(&mut store, key) {
544 return vec![];
545 }
546 match store.get_mut(key) {
547 Some(entry) => {
548 entry.touch();
549 if let CacheValue::List(list) = &entry.value {
550 let len = list.len() as i64;
551 if len == 0 {
552 return vec![];
553 }
554
555 let s = if start < 0 {
557 (len + start).max(0) as usize
558 } else {
559 start.min(len - 1) as usize
560 };
561 let e = if stop < 0 {
562 (len + stop).max(0) as usize
563 } else {
564 stop.min(len - 1) as usize
565 };
566
567 if s > e {
568 return vec![];
569 }
570
571 list.iter().skip(s).take(e - s + 1).cloned().collect()
572 } else {
573 vec![]
574 }
575 }
576 None => vec![],
577 }
578 }
579
580 pub fn llen(&self, key: &str) -> usize {
582 let mut store = self.store.lock().unwrap();
583 if self.remove_if_expired(&mut store, key) {
584 return 0;
585 }
586 match store.get(key) {
587 Some(entry) => {
588 if let CacheValue::List(list) = &entry.value {
589 list.len()
590 } else {
591 0
592 }
593 }
594 None => 0,
595 }
596 }
597
598 pub fn sadd(&self, key: &str, member: &str) -> bool {
604 let mut store = self.store.lock().unwrap();
605 self.remove_if_expired(&mut store, key);
606
607 match store.get_mut(key) {
608 Some(entry) => {
609 entry.touch();
610 if let CacheValue::Set(set) = &mut entry.value {
611 set.insert(member.to_string())
612 } else {
613 let mut set = HashSet::new();
614 set.insert(member.to_string());
615 entry.value = CacheValue::Set(set);
616 true
617 }
618 }
619 None => {
620 self.evict_lru(&mut store);
621 let mut set = HashSet::new();
622 set.insert(member.to_string());
623 store.insert(key.to_string(), CacheEntry::new(CacheValue::Set(set), None));
624 true
625 }
626 }
627 }
628
629 pub fn srem(&self, key: &str, member: &str) -> bool {
631 let mut store = self.store.lock().unwrap();
632 if self.remove_if_expired(&mut store, key) {
633 return false;
634 }
635 match store.get_mut(key) {
636 Some(entry) => {
637 entry.touch();
638 if let CacheValue::Set(set) = &mut entry.value {
639 set.remove(member)
640 } else {
641 false
642 }
643 }
644 None => false,
645 }
646 }
647
648 pub fn smembers(&self, key: &str) -> Vec<String> {
650 let mut store = self.store.lock().unwrap();
651 if self.remove_if_expired(&mut store, key) {
652 return vec![];
653 }
654 match store.get_mut(key) {
655 Some(entry) => {
656 entry.touch();
657 if let CacheValue::Set(set) = &entry.value {
658 set.iter().cloned().collect()
659 } else {
660 vec![]
661 }
662 }
663 None => vec![],
664 }
665 }
666
667 pub fn sismember(&self, key: &str, member: &str) -> bool {
669 let mut store = self.store.lock().unwrap();
670 if self.remove_if_expired(&mut store, key) {
671 return false;
672 }
673 match store.get(key) {
674 Some(entry) => {
675 if let CacheValue::Set(set) = &entry.value {
676 set.contains(member)
677 } else {
678 false
679 }
680 }
681 None => false,
682 }
683 }
684
685 pub fn scard(&self, key: &str) -> usize {
687 let mut store = self.store.lock().unwrap();
688 if self.remove_if_expired(&mut store, key) {
689 return 0;
690 }
691 match store.get(key) {
692 Some(entry) => {
693 if let CacheValue::Set(set) = &entry.value {
694 set.len()
695 } else {
696 0
697 }
698 }
699 None => 0,
700 }
701 }
702
703 pub fn sinter(&self, key1: &str, key2: &str) -> Vec<String> {
705 let mut store = self.store.lock().unwrap();
706 self.remove_if_expired(&mut store, key1);
707 self.remove_if_expired(&mut store, key2);
708
709 let set1 = match store.get(key1) {
710 Some(entry) => match &entry.value {
711 CacheValue::Set(s) => s.clone(),
712 _ => return vec![],
713 },
714 None => return vec![],
715 };
716 let set2 = match store.get(key2) {
717 Some(entry) => match &entry.value {
718 CacheValue::Set(s) => s,
719 _ => return vec![],
720 },
721 None => return vec![],
722 };
723
724 set1.intersection(set2).cloned().collect()
725 }
726
727 pub fn sunion(&self, key1: &str, key2: &str) -> Vec<String> {
729 let mut store = self.store.lock().unwrap();
730 self.remove_if_expired(&mut store, key1);
731 self.remove_if_expired(&mut store, key2);
732
733 let set1 = match store.get(key1) {
734 Some(entry) => match &entry.value {
735 CacheValue::Set(s) => s.clone(),
736 _ => HashSet::new(),
737 },
738 None => HashSet::new(),
739 };
740 let set2 = match store.get(key2) {
741 Some(entry) => match &entry.value {
742 CacheValue::Set(s) => s,
743 _ => return set1.into_iter().collect(),
744 },
745 None => return set1.into_iter().collect(),
746 };
747
748 set1.union(set2).cloned().collect()
749 }
750
751 pub fn hset(&self, key: &str, field: &str, value: &str) {
757 let mut store = self.store.lock().unwrap();
758 self.remove_if_expired(&mut store, key);
759
760 match store.get_mut(key) {
761 Some(entry) => {
762 entry.touch();
763 if let CacheValue::Hash(hash) = &mut entry.value {
764 hash.insert(field.to_string(), value.to_string());
765 } else {
766 let mut hash = HashMap::new();
767 hash.insert(field.to_string(), value.to_string());
768 entry.value = CacheValue::Hash(hash);
769 }
770 }
771 None => {
772 self.evict_lru(&mut store);
773 let mut hash = HashMap::new();
774 hash.insert(field.to_string(), value.to_string());
775 store.insert(
776 key.to_string(),
777 CacheEntry::new(CacheValue::Hash(hash), None),
778 );
779 }
780 }
781 }
782
783 pub fn hget(&self, key: &str, field: &str) -> Option<String> {
785 let mut store = self.store.lock().unwrap();
786 if self.remove_if_expired(&mut store, key) {
787 return None;
788 }
789 let entry = store.get_mut(key)?;
790 entry.touch();
791 if let CacheValue::Hash(hash) = &entry.value {
792 hash.get(field).cloned()
793 } else {
794 None
795 }
796 }
797
798 pub fn hdel(&self, key: &str, field: &str) -> bool {
800 let mut store = self.store.lock().unwrap();
801 if self.remove_if_expired(&mut store, key) {
802 return false;
803 }
804 match store.get_mut(key) {
805 Some(entry) => {
806 entry.touch();
807 if let CacheValue::Hash(hash) = &mut entry.value {
808 hash.remove(field).is_some()
809 } else {
810 false
811 }
812 }
813 None => false,
814 }
815 }
816
817 pub fn hgetall(&self, key: &str) -> HashMap<String, String> {
819 let mut store = self.store.lock().unwrap();
820 if self.remove_if_expired(&mut store, key) {
821 return HashMap::new();
822 }
823 match store.get_mut(key) {
824 Some(entry) => {
825 entry.touch();
826 if let CacheValue::Hash(hash) = &entry.value {
827 hash.clone()
828 } else {
829 HashMap::new()
830 }
831 }
832 None => HashMap::new(),
833 }
834 }
835
836 pub fn hexists(&self, key: &str, field: &str) -> bool {
838 let mut store = self.store.lock().unwrap();
839 if self.remove_if_expired(&mut store, key) {
840 return false;
841 }
842 match store.get(key) {
843 Some(entry) => {
844 if let CacheValue::Hash(hash) = &entry.value {
845 hash.contains_key(field)
846 } else {
847 false
848 }
849 }
850 None => false,
851 }
852 }
853
854 pub fn hlen(&self, key: &str) -> usize {
856 let mut store = self.store.lock().unwrap();
857 if self.remove_if_expired(&mut store, key) {
858 return 0;
859 }
860 match store.get(key) {
861 Some(entry) => {
862 if let CacheValue::Hash(hash) = &entry.value {
863 hash.len()
864 } else {
865 0
866 }
867 }
868 None => 0,
869 }
870 }
871
872 pub fn hkeys(&self, key: &str) -> Vec<String> {
874 let mut store = self.store.lock().unwrap();
875 if self.remove_if_expired(&mut store, key) {
876 return vec![];
877 }
878 match store.get_mut(key) {
879 Some(entry) => {
880 entry.touch();
881 if let CacheValue::Hash(hash) = &entry.value {
882 hash.keys().cloned().collect()
883 } else {
884 vec![]
885 }
886 }
887 None => vec![],
888 }
889 }
890
891 pub fn hincrby(&self, key: &str, field: &str, amount: i64) -> Result<i64, String> {
893 let mut store = self.store.lock().unwrap();
894 self.remove_if_expired(&mut store, key);
895
896 match store.get_mut(key) {
897 Some(entry) => {
898 entry.touch();
899 if let CacheValue::Hash(hash) = &mut entry.value {
900 let current: i64 = match hash.get(field) {
901 Some(v) => v
902 .parse()
903 .map_err(|_| "hash value is not an integer".to_string())?,
904 None => 0,
905 };
906 let new_val = current + amount;
907 hash.insert(field.to_string(), new_val.to_string());
908 Ok(new_val)
909 } else {
910 Err("key is not a hash".to_string())
911 }
912 }
913 None => {
914 self.evict_lru(&mut store);
915 let mut hash = HashMap::new();
916 hash.insert(field.to_string(), amount.to_string());
917 store.insert(
918 key.to_string(),
919 CacheEntry::new(CacheValue::Hash(hash), None),
920 );
921 Ok(amount)
922 }
923 }
924 }
925
926 pub fn zadd(&self, key: &str, score: f64, member: &str) {
932 let mut store = self.store.lock().unwrap();
933 self.remove_if_expired(&mut store, key);
934
935 match store.get_mut(key) {
936 Some(entry) => {
937 entry.touch();
938 if let CacheValue::SortedSet(zset) = &mut entry.value {
939 zset.insert(member.to_string(), score);
940 } else {
941 let mut zset = BTreeMap::new();
942 zset.insert(member.to_string(), score);
943 entry.value = CacheValue::SortedSet(zset);
944 }
945 }
946 None => {
947 self.evict_lru(&mut store);
948 let mut zset = BTreeMap::new();
949 zset.insert(member.to_string(), score);
950 store.insert(
951 key.to_string(),
952 CacheEntry::new(CacheValue::SortedSet(zset), None),
953 );
954 }
955 }
956 }
957
958 pub fn zrem(&self, key: &str, member: &str) -> bool {
960 let mut store = self.store.lock().unwrap();
961 if self.remove_if_expired(&mut store, key) {
962 return false;
963 }
964 match store.get_mut(key) {
965 Some(entry) => {
966 entry.touch();
967 if let CacheValue::SortedSet(zset) = &mut entry.value {
968 zset.remove(member).is_some()
969 } else {
970 false
971 }
972 }
973 None => false,
974 }
975 }
976
977 pub fn zscore(&self, key: &str, member: &str) -> Option<f64> {
979 let mut store = self.store.lock().unwrap();
980 if self.remove_if_expired(&mut store, key) {
981 return None;
982 }
983 let entry = store.get_mut(key)?;
984 entry.touch();
985 if let CacheValue::SortedSet(zset) = &entry.value {
986 zset.get(member).copied()
987 } else {
988 None
989 }
990 }
991
992 pub fn zrank(&self, key: &str, member: &str) -> Option<usize> {
994 let mut store = self.store.lock().unwrap();
995 if self.remove_if_expired(&mut store, key) {
996 return None;
997 }
998 let entry = store.get_mut(key)?;
999 entry.touch();
1000 if let CacheValue::SortedSet(zset) = &entry.value {
1001 let target_score = zset.get(member)?;
1002 let mut members: Vec<(&String, &f64)> = zset.iter().collect();
1004 members.sort_by(|a, b| {
1005 a.1.partial_cmp(b.1)
1006 .unwrap_or(std::cmp::Ordering::Equal)
1007 .then_with(|| a.0.cmp(b.0))
1008 });
1009 members
1010 .iter()
1011 .position(|(m, s)| *m == member && *s == target_score)
1012 } else {
1013 None
1014 }
1015 }
1016
1017 pub fn zrange(&self, key: &str, start: usize, stop: usize) -> Vec<(String, f64)> {
1019 let mut store = self.store.lock().unwrap();
1020 if self.remove_if_expired(&mut store, key) {
1021 return vec![];
1022 }
1023 match store.get_mut(key) {
1024 Some(entry) => {
1025 entry.touch();
1026 if let CacheValue::SortedSet(zset) = &entry.value {
1027 let mut members: Vec<(String, f64)> =
1028 zset.iter().map(|(m, s)| (m.clone(), *s)).collect();
1029 members.sort_by(|a, b| {
1030 a.1.partial_cmp(&b.1)
1031 .unwrap_or(std::cmp::Ordering::Equal)
1032 .then_with(|| a.0.cmp(&b.0))
1033 });
1034 let end = stop.min(members.len().saturating_sub(1));
1035 if start > end {
1036 return vec![];
1037 }
1038 members[start..=end].to_vec()
1039 } else {
1040 vec![]
1041 }
1042 }
1043 None => vec![],
1044 }
1045 }
1046
1047 pub fn zcard(&self, key: &str) -> usize {
1049 let mut store = self.store.lock().unwrap();
1050 if self.remove_if_expired(&mut store, key) {
1051 return 0;
1052 }
1053 match store.get(key) {
1054 Some(entry) => {
1055 if let CacheValue::SortedSet(zset) = &entry.value {
1056 zset.len()
1057 } else {
1058 0
1059 }
1060 }
1061 None => 0,
1062 }
1063 }
1064
1065 pub fn dbsize(&self) -> usize {
1071 let store = self.store.lock().unwrap();
1072 store.values().filter(|e| !e.is_expired()).count()
1073 }
1074
1075 pub fn flushall(&self) {
1077 let mut store = self.store.lock().unwrap();
1078 store.clear();
1079 let mut stats = self.stats.lock().unwrap();
1080 *stats = CacheStats::default();
1081 }
1082
1083 pub fn info(&self) -> CacheStats {
1085 self.stats.lock().unwrap().clone()
1086 }
1087
1088 pub fn key_type(&self, key: &str) -> Option<&'static str> {
1090 let mut store = self.store.lock().unwrap();
1091 if self.remove_if_expired(&mut store, key) {
1092 return None;
1093 }
1094 store.get(key).map(|entry| match &entry.value {
1095 CacheValue::String(_) => "string",
1096 CacheValue::Int(_) => "string",
1097 CacheValue::Float(_) => "string",
1098 CacheValue::List(_) => "list",
1099 CacheValue::Set(_) => "set",
1100 CacheValue::Hash(_) => "hash",
1101 CacheValue::SortedSet(_) => "zset",
1102 })
1103 }
1104
1105 pub fn cleanup_expired(&self) -> usize {
1107 let mut store = self.store.lock().unwrap();
1108 let expired: Vec<String> = store
1109 .iter()
1110 .filter(|(_, entry)| entry.is_expired())
1111 .map(|(k, _)| k.clone())
1112 .collect();
1113 let count = expired.len();
1114 for k in &expired {
1115 store.remove(k);
1116 }
1117 self.stats.lock().unwrap().expired += count as u64;
1118 count
1119 }
1120}
1121
1122impl Plugin for CachePlugin {
1127 fn name(&self) -> &str {
1128 "cache"
1129 }
1130}
1131
1132#[cfg(test)]
1137mod tests {
1138 use super::*;
1139 use std::thread;
1140 use std::time::Duration;
1141
1142 fn cache() -> CachePlugin {
1143 CachePlugin::new(1000)
1144 }
1145
1146 #[test]
1149 fn set_and_get() {
1150 let c = cache();
1151 c.set("hello", "world", None);
1152 assert_eq!(c.get("hello"), Some("world".to_string()));
1153 }
1154
1155 #[test]
1156 fn get_missing_key_returns_none() {
1157 let c = cache();
1158 assert_eq!(c.get("nonexistent"), None);
1159 }
1160
1161 #[test]
1162 fn set_with_ttl_and_get_before_expiry() {
1163 let c = cache();
1164 c.set("k", "v", Some(10));
1165 assert_eq!(c.get("k"), Some("v".to_string()));
1166 }
1167
1168 #[test]
1169 fn get_expired_key_returns_none() {
1170 let c = cache();
1171 c.set("k", "v", Some(0));
1172 thread::sleep(Duration::from_millis(5));
1174 assert_eq!(c.get("k"), None);
1175 }
1176
1177 #[test]
1178 fn incr_creates_key() {
1179 let c = cache();
1180 assert_eq!(c.incr("counter"), Ok(1));
1181 assert_eq!(c.incr("counter"), Ok(2));
1182 }
1183
1184 #[test]
1185 fn decr_key() {
1186 let c = cache();
1187 c.set("x", "10", None);
1188 assert_eq!(c.decr("x"), Ok(9));
1189 assert_eq!(c.decr("x"), Ok(8));
1190 }
1191
1192 #[test]
1193 fn incrby_amount() {
1194 let c = cache();
1195 assert_eq!(c.incrby("n", 5), Ok(5));
1196 assert_eq!(c.incrby("n", 3), Ok(8));
1197 assert_eq!(c.incrby("n", -2), Ok(6));
1198 }
1199
1200 #[test]
1201 fn incr_non_integer_errors() {
1202 let c = cache();
1203 c.set("s", "not_a_number", None);
1204 assert!(c.incr("s").is_err());
1205 }
1206
1207 #[test]
1208 fn setnx_only_sets_if_missing() {
1209 let c = cache();
1210 assert!(c.setnx("k", "first", None));
1211 assert!(!c.setnx("k", "second", None));
1212 assert_eq!(c.get("k"), Some("first".to_string()));
1213 }
1214
1215 #[test]
1216 fn getset_swaps_value() {
1217 let c = cache();
1218 c.set("k", "old", None);
1219 let old = c.getset("k", "new");
1220 assert_eq!(old, Some("old".to_string()));
1221 assert_eq!(c.get("k"), Some("new".to_string()));
1222 }
1223
1224 #[test]
1225 fn getset_on_missing_key() {
1226 let c = cache();
1227 let old = c.getset("k", "val");
1228 assert_eq!(old, None);
1229 assert_eq!(c.get("k"), Some("val".to_string()));
1230 }
1231
1232 #[test]
1233 fn mget_and_mset() {
1234 let c = cache();
1235 c.mset(&[("a", "1"), ("b", "2"), ("c", "3")]);
1236 let vals = c.mget(&["a", "b", "missing", "c"]);
1237 assert_eq!(
1238 vals,
1239 vec![
1240 Some("1".to_string()),
1241 Some("2".to_string()),
1242 None,
1243 Some("3".to_string()),
1244 ]
1245 );
1246 }
1247
1248 #[test]
1251 fn ttl_no_expiry() {
1252 let c = cache();
1253 c.set("k", "v", None);
1254 assert_eq!(c.ttl("k"), -1);
1255 }
1256
1257 #[test]
1258 fn ttl_missing_key() {
1259 let c = cache();
1260 assert_eq!(c.ttl("nope"), -2);
1261 }
1262
1263 #[test]
1264 fn expire_and_persist() {
1265 let c = cache();
1266 c.set("k", "v", None);
1267 assert!(c.expire("k", 100));
1268 assert!(c.ttl("k") > 0);
1269 assert!(c.persist("k"));
1270 assert_eq!(c.ttl("k"), -1);
1271 }
1272
1273 #[test]
1274 fn expire_on_missing_key() {
1275 let c = cache();
1276 assert!(!c.expire("nope", 10));
1277 }
1278
1279 #[test]
1282 fn del_existing_key() {
1283 let c = cache();
1284 c.set("k", "v", None);
1285 assert!(c.del("k"));
1286 assert!(!c.exists("k"));
1287 }
1288
1289 #[test]
1290 fn del_missing_key() {
1291 let c = cache();
1292 assert!(!c.del("nope"));
1293 }
1294
1295 #[test]
1296 fn exists_key() {
1297 let c = cache();
1298 assert!(!c.exists("k"));
1299 c.set("k", "v", None);
1300 assert!(c.exists("k"));
1301 }
1302
1303 #[test]
1306 fn lpush_and_rpush() {
1307 let c = cache();
1308 c.lpush("list", "b");
1309 c.lpush("list", "a");
1310 c.rpush("list", "c");
1311 assert_eq!(
1312 c.lrange("list", 0, -1),
1313 vec!["a".to_string(), "b".to_string(), "c".to_string()]
1314 );
1315 }
1316
1317 #[test]
1318 fn lpop_and_rpop() {
1319 let c = cache();
1320 c.rpush("list", "a");
1321 c.rpush("list", "b");
1322 c.rpush("list", "c");
1323 assert_eq!(c.lpop("list"), Some("a".to_string()));
1324 assert_eq!(c.rpop("list"), Some("c".to_string()));
1325 assert_eq!(c.llen("list"), 1);
1326 }
1327
1328 #[test]
1329 fn lrange_with_negative_indices() {
1330 let c = cache();
1331 for v in &["a", "b", "c", "d", "e"] {
1332 c.rpush("list", v);
1333 }
1334 assert_eq!(
1336 c.lrange("list", -2, -1),
1337 vec!["d".to_string(), "e".to_string()]
1338 );
1339 }
1340
1341 #[test]
1342 fn llen_empty_and_missing() {
1343 let c = cache();
1344 assert_eq!(c.llen("nope"), 0);
1345 c.rpush("list", "x");
1346 assert_eq!(c.llen("list"), 1);
1347 }
1348
1349 #[test]
1350 fn lpop_empty_list() {
1351 let c = cache();
1352 assert_eq!(c.lpop("nope"), None);
1353 }
1354
1355 #[test]
1358 fn sadd_and_sismember() {
1359 let c = cache();
1360 assert!(c.sadd("s", "a"));
1361 assert!(!c.sadd("s", "a")); assert!(c.sismember("s", "a"));
1363 assert!(!c.sismember("s", "b"));
1364 }
1365
1366 #[test]
1367 fn srem_member() {
1368 let c = cache();
1369 c.sadd("s", "a");
1370 c.sadd("s", "b");
1371 assert!(c.srem("s", "a"));
1372 assert!(!c.sismember("s", "a"));
1373 assert_eq!(c.scard("s"), 1);
1374 }
1375
1376 #[test]
1377 fn smembers_returns_all() {
1378 let c = cache();
1379 c.sadd("s", "x");
1380 c.sadd("s", "y");
1381 let mut members = c.smembers("s");
1382 members.sort();
1383 assert_eq!(members, vec!["x".to_string(), "y".to_string()]);
1384 }
1385
1386 #[test]
1387 fn scard_and_empty() {
1388 let c = cache();
1389 assert_eq!(c.scard("nope"), 0);
1390 c.sadd("s", "a");
1391 assert_eq!(c.scard("s"), 1);
1392 }
1393
1394 #[test]
1395 fn sinter_two_sets() {
1396 let c = cache();
1397 c.sadd("s1", "a");
1398 c.sadd("s1", "b");
1399 c.sadd("s1", "c");
1400 c.sadd("s2", "b");
1401 c.sadd("s2", "c");
1402 c.sadd("s2", "d");
1403 let mut inter = c.sinter("s1", "s2");
1404 inter.sort();
1405 assert_eq!(inter, vec!["b".to_string(), "c".to_string()]);
1406 }
1407
1408 #[test]
1409 fn sunion_two_sets() {
1410 let c = cache();
1411 c.sadd("s1", "a");
1412 c.sadd("s1", "b");
1413 c.sadd("s2", "b");
1414 c.sadd("s2", "c");
1415 let mut union = c.sunion("s1", "s2");
1416 union.sort();
1417 assert_eq!(
1418 union,
1419 vec!["a".to_string(), "b".to_string(), "c".to_string()]
1420 );
1421 }
1422
1423 #[test]
1426 fn hset_and_hget() {
1427 let c = cache();
1428 c.hset("h", "name", "alice");
1429 assert_eq!(c.hget("h", "name"), Some("alice".to_string()));
1430 assert_eq!(c.hget("h", "missing"), None);
1431 }
1432
1433 #[test]
1434 fn hdel_field() {
1435 let c = cache();
1436 c.hset("h", "a", "1");
1437 c.hset("h", "b", "2");
1438 assert!(c.hdel("h", "a"));
1439 assert!(!c.hexists("h", "a"));
1440 assert_eq!(c.hlen("h"), 1);
1441 }
1442
1443 #[test]
1444 fn hgetall_returns_map() {
1445 let c = cache();
1446 c.hset("h", "x", "1");
1447 c.hset("h", "y", "2");
1448 let all = c.hgetall("h");
1449 assert_eq!(all.len(), 2);
1450 assert_eq!(all.get("x"), Some(&"1".to_string()));
1451 assert_eq!(all.get("y"), Some(&"2".to_string()));
1452 }
1453
1454 #[test]
1455 fn hexists_and_hlen() {
1456 let c = cache();
1457 assert!(!c.hexists("h", "f"));
1458 assert_eq!(c.hlen("h"), 0);
1459 c.hset("h", "f", "v");
1460 assert!(c.hexists("h", "f"));
1461 assert_eq!(c.hlen("h"), 1);
1462 }
1463
1464 #[test]
1465 fn hkeys_returns_field_names() {
1466 let c = cache();
1467 c.hset("h", "a", "1");
1468 c.hset("h", "b", "2");
1469 let mut keys = c.hkeys("h");
1470 keys.sort();
1471 assert_eq!(keys, vec!["a".to_string(), "b".to_string()]);
1472 }
1473
1474 #[test]
1475 fn hincrby_creates_and_increments() {
1476 let c = cache();
1477 assert_eq!(c.hincrby("h", "count", 5), Ok(5));
1478 assert_eq!(c.hincrby("h", "count", 3), Ok(8));
1479 }
1480
1481 #[test]
1482 fn hincrby_non_integer_errors() {
1483 let c = cache();
1484 c.hset("h", "name", "alice");
1485 assert!(c.hincrby("h", "name", 1).is_err());
1486 }
1487
1488 #[test]
1491 fn zadd_and_zscore() {
1492 let c = cache();
1493 c.zadd("z", 1.5, "a");
1494 c.zadd("z", 2.5, "b");
1495 assert_eq!(c.zscore("z", "a"), Some(1.5));
1496 assert_eq!(c.zscore("z", "b"), Some(2.5));
1497 assert_eq!(c.zscore("z", "c"), None);
1498 }
1499
1500 #[test]
1501 fn zrem_member() {
1502 let c = cache();
1503 c.zadd("z", 1.0, "a");
1504 c.zadd("z", 2.0, "b");
1505 assert!(c.zrem("z", "a"));
1506 assert!(!c.zrem("z", "a"));
1507 assert_eq!(c.zcard("z"), 1);
1508 }
1509
1510 #[test]
1511 fn zrank_by_score() {
1512 let c = cache();
1513 c.zadd("z", 3.0, "c");
1514 c.zadd("z", 1.0, "a");
1515 c.zadd("z", 2.0, "b");
1516 assert_eq!(c.zrank("z", "a"), Some(0));
1517 assert_eq!(c.zrank("z", "b"), Some(1));
1518 assert_eq!(c.zrank("z", "c"), Some(2));
1519 }
1520
1521 #[test]
1522 fn zrange_returns_ordered_slice() {
1523 let c = cache();
1524 c.zadd("z", 3.0, "c");
1525 c.zadd("z", 1.0, "a");
1526 c.zadd("z", 2.0, "b");
1527 let range = c.zrange("z", 0, 1);
1528 assert_eq!(range, vec![("a".to_string(), 1.0), ("b".to_string(), 2.0),]);
1529 }
1530
1531 #[test]
1532 fn zcard_empty_and_filled() {
1533 let c = cache();
1534 assert_eq!(c.zcard("z"), 0);
1535 c.zadd("z", 1.0, "x");
1536 assert_eq!(c.zcard("z"), 1);
1537 }
1538
1539 #[test]
1542 fn keys_star_pattern() {
1543 let c = cache();
1544 c.set("user:1", "a", None);
1545 c.set("user:2", "b", None);
1546 c.set("post:1", "c", None);
1547 let mut matched = c.keys("user:*");
1548 matched.sort();
1549 assert_eq!(matched, vec!["user:1".to_string(), "user:2".to_string()]);
1550 }
1551
1552 #[test]
1553 fn keys_question_mark_pattern() {
1554 let c = cache();
1555 c.set("a1", "v", None);
1556 c.set("a2", "v", None);
1557 c.set("ab", "v", None);
1558 let mut matched = c.keys("a?");
1559 matched.sort();
1560 assert_eq!(
1561 matched,
1562 vec!["a1".to_string(), "a2".to_string(), "ab".to_string()]
1563 );
1564 }
1565
1566 #[test]
1567 fn keys_all_pattern() {
1568 let c = cache();
1569 c.set("x", "1", None);
1570 c.set("y", "2", None);
1571 assert_eq!(c.keys("*").len(), 2);
1572 }
1573
1574 #[test]
1577 fn key_type_detection() {
1578 let c = cache();
1579 c.set("s", "val", None);
1580 c.rpush("l", "item");
1581 c.sadd("set", "m");
1582 c.hset("h", "f", "v");
1583 c.zadd("z", 1.0, "m");
1584
1585 assert_eq!(c.key_type("s"), Some("string"));
1586 assert_eq!(c.key_type("l"), Some("list"));
1587 assert_eq!(c.key_type("set"), Some("set"));
1588 assert_eq!(c.key_type("h"), Some("hash"));
1589 assert_eq!(c.key_type("z"), Some("zset"));
1590 assert_eq!(c.key_type("nope"), None);
1591 }
1592
1593 #[test]
1596 fn lru_eviction_when_over_max_keys() {
1597 let c = CachePlugin::new(3);
1598 c.set("a", "1", None);
1599 c.set("b", "2", None);
1600 c.set("c", "3", None);
1601
1602 c.get("a");
1604
1605 c.set("d", "4", None);
1611
1612 assert_eq!(c.dbsize(), 3);
1613 assert!(c.exists("a")); assert!(!c.exists("b")); assert!(c.exists("c"));
1616 assert!(c.exists("d"));
1617
1618 let stats = c.info();
1619 assert!(stats.evictions >= 1);
1620 }
1621
1622 #[test]
1625 fn stats_hit_miss_tracking() {
1626 let c = cache();
1627 c.set("k", "v", None);
1628 c.get("k"); c.get("k"); c.get("missing"); let stats = c.info();
1633 assert_eq!(stats.hits, 2);
1634 assert_eq!(stats.misses, 1);
1635 assert_eq!(stats.sets, 1);
1636 }
1637
1638 #[test]
1641 fn cleanup_expired_keys() {
1642 let c = cache();
1643 c.set("keep", "yes", None);
1644 c.set("expire1", "no", Some(0));
1645 c.set("expire2", "no", Some(0));
1646 thread::sleep(Duration::from_millis(5));
1647
1648 let removed = c.cleanup_expired();
1649 assert_eq!(removed, 2);
1650 assert!(c.exists("keep"));
1651 assert!(!c.exists("expire1"));
1652 assert!(!c.exists("expire2"));
1653 }
1654
1655 #[test]
1658 fn dbsize_counts_non_expired() {
1659 let c = cache();
1660 c.set("a", "1", None);
1661 c.set("b", "2", None);
1662 c.set("c", "3", Some(0));
1663 thread::sleep(Duration::from_millis(5));
1664 assert_eq!(c.dbsize(), 2);
1666 }
1667
1668 #[test]
1669 fn flushall_clears_everything() {
1670 let c = cache();
1671 c.set("a", "1", None);
1672 c.set("b", "2", None);
1673 c.rpush("list", "x");
1674 c.flushall();
1675 assert_eq!(c.dbsize(), 0);
1676 let stats = c.info();
1677 assert_eq!(stats.sets, 0);
1678 }
1679
1680 #[test]
1683 fn plugin_name() {
1684 let c = cache();
1685 assert_eq!(Plugin::name(&c), "cache");
1686 }
1687
1688 #[test]
1691 fn glob_match_exact() {
1692 assert!(glob_match("hello", "hello"));
1693 assert!(!glob_match("hello", "world"));
1694 }
1695
1696 #[test]
1697 fn glob_match_star() {
1698 assert!(glob_match("h*o", "hello"));
1699 assert!(glob_match("*", "anything"));
1700 assert!(glob_match("pre*", "prefix"));
1701 assert!(glob_match("*fix", "suffix"));
1702 }
1703
1704 #[test]
1705 fn glob_match_question() {
1706 assert!(glob_match("h?llo", "hello"));
1707 assert!(!glob_match("h?llo", "hllo"));
1708 }
1709}