1use crate::cache::Cache;
50use crate::error::CacheError;
51use crate::value::Value;
52use std::collections::HashMap;
53use std::future::Future;
54use std::pin::Pin;
55use std::sync::{Arc, RwLock};
56use std::time::Duration;
57use tokio::time::Instant;
62
63#[derive(Debug, Clone)]
69pub enum InvalidationMessage {
70 InvalidateKey(String),
72 InvalidateTable(String),
74 InvalidateAll,
76}
77
78pub trait InvalidationBus: Send + Sync {
83 fn publish(&self, message: InvalidationMessage);
85 fn subscribe(&self) -> Box<dyn Iterator<Item = InvalidationMessage> + Send>;
87}
88
89pub struct LocalInvalidationBus {
94 tx: tokio::sync::broadcast::Sender<InvalidationMessage>,
95}
96
97impl LocalInvalidationBus {
98 pub fn new(capacity: usize) -> Self {
100 let (tx, _rx) = tokio::sync::broadcast::channel(capacity.max(1));
101 Self { tx }
102 }
103}
104
105impl Default for LocalInvalidationBus {
106 fn default() -> Self {
107 Self::new(256)
108 }
109}
110
111impl InvalidationBus for LocalInvalidationBus {
112 fn publish(&self, message: InvalidationMessage) {
113 let _ = self.tx.send(message);
115 }
116
117 fn subscribe(&self) -> Box<dyn Iterator<Item = InvalidationMessage> + Send> {
118 let mut rx = self.tx.subscribe();
119 Box::new(std::iter::from_fn(move || loop {
120 match rx.try_recv() {
121 Ok(msg) => return Some(msg),
122 Err(tokio::sync::broadcast::error::TryRecvError::Empty)
124 | Err(tokio::sync::broadcast::error::TryRecvError::Closed) => return None,
125 Err(tokio::sync::broadcast::error::TryRecvError::Lagged(_)) => continue,
127 }
128 }))
129 }
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Hash)]
143pub struct CacheKey {
144 pub table: String,
146 pub kind: CacheKeyKind,
148 pub identifier: String,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Hash)]
154pub enum CacheKeyKind {
155 ByPk,
157 ByQuery,
159 ByRelation,
161}
162
163impl CacheKey {
164 pub fn by_pk(table: impl Into<String>, pk: impl std::fmt::Display) -> Self {
166 Self {
167 table: table.into(),
168 kind: CacheKeyKind::ByPk,
169 identifier: pk.to_string(),
170 }
171 }
172
173 pub fn by_query(table: impl Into<String>, query_hash: impl std::fmt::Display) -> Self {
175 Self {
176 table: table.into(),
177 kind: CacheKeyKind::ByQuery,
178 identifier: query_hash.to_string(),
179 }
180 }
181
182 pub fn by_relation(table: impl Into<String>, relation: impl std::fmt::Display) -> Self {
184 Self {
185 table: table.into(),
186 kind: CacheKeyKind::ByRelation,
187 identifier: relation.to_string(),
188 }
189 }
190
191 pub fn to_string_key(&self) -> String {
193 let kind_str = match self.kind {
194 CacheKeyKind::ByPk => "pk",
195 CacheKeyKind::ByQuery => "q",
196 CacheKeyKind::ByRelation => "rel",
197 };
198 format!("l2:{}:{}:{}", self.table, kind_str, self.identifier)
199 }
200}
201
202impl std::fmt::Display for CacheKey {
203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204 write!(f, "{}", self.to_string_key())
205 }
206}
207
208#[derive(Debug, Clone, Default)]
214pub struct L2CacheStats {
215 pub hits: u64,
217 pub misses: u64,
219 pub sets: u64,
221 pub evictions: u64,
223 pub size: usize,
225}
226
227#[derive(Debug, Clone, Default)]
232pub struct PerTableStats {
233 pub hits: u64,
235 pub misses: u64,
237 pub sets: u64,
239 pub evictions: u64,
241}
242
243impl PerTableStats {
244 pub fn total_lookups(&self) -> u64 {
246 self.hits + self.misses
247 }
248
249 pub fn hit_rate(&self) -> f64 {
251 let total = self.total_lookups();
252 if total == 0 {
253 0.0
254 } else {
255 self.hits as f64 / total as f64
256 }
257 }
258}
259
260impl L2CacheStats {
261 pub fn total_lookups(&self) -> u64 {
263 self.hits + self.misses
264 }
265
266 pub fn hit_rate(&self) -> f64 {
268 let total = self.total_lookups();
269 if total == 0 {
270 0.0
271 } else {
272 self.hits as f64 / total as f64
273 }
274 }
275
276 pub fn miss_rate(&self) -> f64 {
278 1.0 - self.hit_rate()
279 }
280
281 pub fn merge(&mut self, other: &L2CacheStats) {
283 self.hits += other.hits;
284 self.misses += other.misses;
285 self.sets += other.sets;
286 self.evictions += other.evictions;
287 self.size += other.size;
288 }
289}
290
291#[derive(Debug, Clone)]
297struct CacheEntry {
298 value: Value,
300 expires_at: Option<Instant>,
302}
303
304impl CacheEntry {
305 fn new(value: Value, ttl: Option<Duration>) -> Self {
306 let expires_at = ttl.and_then(|d| {
309 if d == Duration::MAX {
310 None
311 } else {
312 Some(Instant::now() + d)
313 }
314 });
315 Self { value, expires_at }
316 }
317
318 fn is_expired(&self) -> bool {
319 self.expires_at
320 .map(|t| t <= Instant::now())
321 .unwrap_or(false)
322 }
323}
324
325struct LruOrder {
340 nodes: Vec<LruNode>,
342 free_list: Vec<usize>,
344 index: HashMap<String, usize>,
346 head: Option<usize>,
348 tail: Option<usize>,
350}
351
352struct LruNode {
354 key: String,
355 prev: Option<usize>,
356 next: Option<usize>,
357}
358
359impl LruOrder {
360 fn new() -> Self {
361 Self {
362 nodes: Vec::new(),
363 free_list: Vec::new(),
364 index: HashMap::new(),
365 head: None,
366 tail: None,
367 }
368 }
369
370 fn touch(&mut self, key: &str) {
372 if let Some(&idx) = self.index.get(key) {
373 self.unlink(idx);
374 self.link_tail(idx);
375 } else {
376 let idx = self.alloc_node(key.to_string());
377 self.link_tail(idx);
378 self.index.insert(key.to_string(), idx);
379 }
380 }
381
382 fn remove(&mut self, key: &str) {
384 if let Some(idx) = self.index.remove(key) {
385 self.unlink(idx);
386 self.free_node(idx);
387 }
388 }
389
390 fn lru_key(&self) -> Option<&str> {
392 self.head.map(|idx| self.nodes[idx].key.as_str())
393 }
394
395 fn iter_keys(&self) -> impl Iterator<Item = &str> {
397 LruIter {
398 nodes: &self.nodes,
399 current: self.head,
400 }
401 }
402
403 fn clear(&mut self) {
405 self.nodes.clear();
406 self.free_list.clear();
407 self.index.clear();
408 self.head = None;
409 self.tail = None;
410 }
411
412 #[allow(dead_code)]
414 fn len(&self) -> usize {
415 self.index.len()
416 }
417
418 fn alloc_node(&mut self, key: String) -> usize {
420 if let Some(idx) = self.free_list.pop() {
421 self.nodes[idx] = LruNode {
422 key,
423 prev: None,
424 next: None,
425 };
426 idx
427 } else {
428 self.nodes.push(LruNode {
429 key,
430 prev: None,
431 next: None,
432 });
433 self.nodes.len() - 1
434 }
435 }
436
437 fn free_node(&mut self, idx: usize) {
439 self.free_list.push(idx);
440 }
441
442 fn unlink(&mut self, idx: usize) {
444 let prev = self.nodes[idx].prev;
445 let next = self.nodes[idx].next;
446 match prev {
447 Some(p) => self.nodes[p].next = next,
448 None => self.head = next,
449 }
450 match next {
451 Some(n) => self.nodes[n].prev = prev,
452 None => self.tail = prev,
453 }
454 self.nodes[idx].prev = None;
455 self.nodes[idx].next = None;
456 }
457
458 fn link_tail(&mut self, idx: usize) {
460 match self.tail {
461 Some(t) => {
462 self.nodes[t].next = Some(idx);
463 self.nodes[idx].prev = Some(t);
464 }
465 None => self.head = Some(idx),
466 }
467 self.nodes[idx].next = None;
468 self.tail = Some(idx);
469 }
470}
471
472struct LruIter<'a> {
474 nodes: &'a [LruNode],
475 current: Option<usize>,
476}
477
478impl<'a> Iterator for LruIter<'a> {
479 type Item = &'a str;
480
481 fn next(&mut self) -> Option<Self::Item> {
482 let idx = self.current?;
483 let node = &self.nodes[idx];
484 self.current = node.next;
485 Some(node.key.as_str())
486 }
487}
488
489pub struct L2Cache {
518 data: RwLock<HashMap<String, CacheEntry>>,
520 table_index: RwLock<HashMap<String, Vec<String>>>,
522 access_order: RwLock<LruOrder>,
529 stats: RwLock<L2CacheStats>,
531 table_stats: RwLock<HashMap<String, PerTableStats>>,
533 default_ttl: Option<Duration>,
535 max_size: usize,
537 invalidation_bus: Option<Arc<dyn InvalidationBus>>,
539}
540
541impl Default for L2Cache {
542 fn default() -> Self {
543 Self::new()
544 }
545}
546
547impl L2Cache {
548 pub fn new() -> Self {
550 Self {
551 data: RwLock::new(HashMap::new()),
552 table_index: RwLock::new(HashMap::new()),
553 access_order: RwLock::new(LruOrder::new()),
554 stats: RwLock::new(L2CacheStats::default()),
555 table_stats: RwLock::new(HashMap::new()),
556 default_ttl: None,
557 max_size: 10_000,
558 invalidation_bus: None,
559 }
560 }
561
562 pub fn with_default_ttl(mut self, ttl: Duration) -> Self {
564 self.default_ttl = Some(ttl);
565 self
566 }
567
568 pub fn with_max_size(mut self, max_size: usize) -> Self {
570 self.max_size = max_size;
571 self
572 }
573
574 pub fn with_invalidation_bus(mut self, bus: Arc<dyn InvalidationBus>) -> Self {
576 self.invalidation_bus = Some(bus);
577 self
578 }
579
580 pub fn put(&self, key: &CacheKey, value: Value, ttl: Option<Duration>) {
588 let actual_ttl = ttl.or(self.default_ttl);
589 let entry = CacheEntry::new(value, actual_ttl);
590 let key_str = key.to_string_key();
591
592 {
594 let mut data = match self.data.write() {
596 Ok(d) => d,
597 Err(_) => return,
598 };
599 let exists = data.contains_key(&key_str);
600 if !exists && data.len() >= self.max_size {
601 let victim = {
603 match self.access_order.read() {
606 Ok(order) => {
607 let expired = order
610 .iter_keys()
611 .find(|k| data.get(*k).map(|e| e.is_expired()).unwrap_or(false))
612 .map(|s| s.to_string());
613 let lru = order.lru_key().map(|s| s.to_string());
614 expired.or(lru)
615 }
616 Err(_) => None,
617 }
618 };
619 if let Some(victim) = victim {
620 data.remove(&victim);
621 if let Ok(mut order) = self.access_order.write() {
624 order.remove(&victim);
625 }
626 }
627 }
628 data.insert(key_str.clone(), entry);
629 };
630
631 if let Ok(mut order) = self.access_order.write() {
634 order.touch(&key_str);
635 }
636
637 if let Ok(mut idx) = self.table_index.write() {
640 let keys = idx.entry(key.table.clone()).or_default();
641 if !keys.contains(&key_str) {
642 keys.push(key_str);
643 }
644 }
645
646 if let Ok(mut stats) = self.stats.write() {
649 stats.sets += 1;
650 }
651 {
653 if let Ok(mut tbl_stats) = self.table_stats.write() {
654 tbl_stats.entry(key.table.clone()).or_default().sets += 1;
655 }
656 }
657 }
658
659 pub fn get(&self, key: &CacheKey) -> Option<Value> {
663 let key_str = key.to_string_key();
664 let table_name = key.table.clone();
665 let result = {
666 let data = self.data.read().ok()?;
667 if let Some(entry) = data.get(&key_str) {
668 if entry.is_expired() {
669 None
670 } else {
671 Some(entry.value.clone())
672 }
673 } else {
674 None
675 }
676 };
677
678 if result.is_some() {
681 if let Ok(mut order) = self.access_order.write() {
682 order.touch(&key_str);
683 }
684 }
685
686 if let Ok(mut stats) = self.stats.write() {
688 if result.is_some() {
689 stats.hits += 1;
690 } else {
691 stats.misses += 1;
692 }
693 }
694 if let Ok(mut tbl_stats) = self.table_stats.write() {
696 let entry = tbl_stats.entry(table_name).or_default();
697 if result.is_some() {
698 entry.hits += 1;
699 } else {
700 entry.misses += 1;
701 }
702 }
703
704 result
705 }
706
707 pub fn invalidate(&self, key: &CacheKey) {
709 let key_str = key.to_string_key();
710 let table_name = key.table.clone();
711 let removed = {
712 let mut data = match self.data.write() {
714 Ok(d) => d,
715 Err(_) => return,
716 };
717 data.remove(&key_str).is_some()
718 };
719 if removed {
720 if let Ok(mut order) = self.access_order.write() {
722 order.remove(&key_str);
723 }
724 }
725 if removed {
726 if let Ok(mut stats) = self.stats.write() {
728 stats.evictions += 1;
729 }
730 if let Ok(mut tbl_stats) = self.table_stats.write() {
731 tbl_stats.entry(table_name).or_default().evictions += 1;
732 }
733 }
734 }
735
736 pub fn invalidate_table(&self, table: &str) {
741 let keys_to_remove: Vec<String> = {
742 let idx = match self.table_index.read() {
743 Ok(i) => i,
744 Err(_) => return,
745 };
746 idx.get(table).cloned().unwrap_or_default()
747 };
748
749 let mut actually_removed: usize = 0;
750 {
751 let mut data = match self.data.write() {
753 Ok(d) => d,
754 Err(_) => return,
755 };
756 for k in &keys_to_remove {
757 if data.remove(k).is_some() {
758 actually_removed += 1;
759 }
760 }
761 }
762
763 if actually_removed > 0 {
766 if let Ok(mut order) = self.access_order.write() {
767 for k in &keys_to_remove {
768 order.remove(k);
769 }
770 }
771 }
772
773 if let Ok(mut idx) = self.table_index.write() {
774 idx.remove(table);
775 }
776 if actually_removed > 0 {
777 if let Ok(mut stats) = self.stats.write() {
779 stats.evictions += actually_removed as u64;
780 }
781 if let Ok(mut tbl_stats) = self.table_stats.write() {
782 tbl_stats.entry(table.to_string()).or_default().evictions +=
783 actually_removed as u64;
784 }
785 }
786
787 if let Some(bus) = &self.invalidation_bus {
789 bus.publish(InvalidationMessage::InvalidateTable(table.to_string()));
790 }
791 }
792
793 pub fn clear(&self) {
795 let removed = {
796 let mut data = match self.data.write() {
798 Ok(d) => d,
799 Err(_) => return,
800 };
801 let n = data.len();
802 data.clear();
803 n
804 };
805 if let Ok(mut order) = self.access_order.write() {
806 order.clear();
807 }
808 if let Ok(mut idx) = self.table_index.write() {
809 idx.clear();
810 }
811 if let Ok(mut tbl_stats) = self.table_stats.write() {
812 tbl_stats.clear();
813 }
814 if removed > 0 {
815 if let Ok(mut stats) = self.stats.write() {
817 stats.evictions += removed as u64;
818 stats.size = 0;
819 }
820 }
821 }
822
823 pub fn size(&self) -> usize {
825 self.data.read().map(|d| d.len()).unwrap_or(0)
826 }
827
828 pub fn stats(&self) -> L2CacheStats {
830 let mut s = self.stats.read().map(|s| s.clone()).unwrap_or_default();
831 s.size = self.size();
833 s
834 }
835
836 pub fn reset_stats(&self) {
838 if let Ok(mut stats) = self.stats.write() {
839 *stats = L2CacheStats::default();
840 }
841 if let Ok(mut tbl_stats) = self.table_stats.write() {
842 tbl_stats.clear();
843 }
844 }
845
846 pub async fn get_or_load_query<F, Fut>(
879 &self,
880 table: &str,
881 sql: &str,
882 params: &[crate::value::Value],
883 ttl: Duration,
884 loader: F,
885 ) -> Result<crate::pool::QueryRows, crate::DbError>
886 where
887 F: FnOnce() -> Fut,
888 Fut: std::future::Future<Output = Result<crate::pool::QueryRows, crate::DbError>>,
889 {
890 use std::collections::hash_map::DefaultHasher;
892 use std::hash::{Hash, Hasher};
893
894 let mut hasher = DefaultHasher::new();
895 sql.hash(&mut hasher);
896 for param in params {
897 param.to_string().hash(&mut hasher);
898 }
899 let query_hash = hasher.finish();
900 let cache_key = CacheKey::by_query(table, query_hash);
901
902 if let Some(cached) = self.get(&cache_key) {
904 if let Value::Json(json_str) = cached {
906 if let Ok(rows) = serde_json::from_str::<crate::pool::QueryRows>(&json_str) {
907 return Ok(rows);
908 }
909 }
910 }
911
912 let rows = loader().await?;
914
915 let cache_ttl = if rows.is_empty() {
917 std::cmp::max(ttl / 10, Duration::from_secs(1))
919 } else {
920 ttl
921 };
922
923 if let Ok(json_str) = serde_json::to_string(&rows) {
925 self.put(&cache_key, Value::Json(json_str), Some(cache_ttl));
926 }
927
928 Ok(rows)
929 }
930
931 pub fn invalidate_query(&self, table: &str, sql: &str, params: &[crate::value::Value]) {
935 use std::collections::hash_map::DefaultHasher;
936 use std::hash::{Hash, Hasher};
937
938 let mut hasher = DefaultHasher::new();
939 sql.hash(&mut hasher);
940 for param in params {
941 param.to_string().hash(&mut hasher);
942 }
943 let query_hash = hasher.finish();
944 let cache_key = CacheKey::by_query(table, query_hash);
945 self.invalidate(&cache_key);
946 }
947
948 pub fn table_stats(&self, table: &str) -> Option<PerTableStats> {
950 self.table_stats
951 .read()
952 .ok()
953 .and_then(|s| s.get(table).cloned())
954 }
955
956 pub fn all_table_stats(&self) -> HashMap<String, PerTableStats> {
958 self.table_stats
959 .read()
960 .map(|s| s.clone())
961 .unwrap_or_default()
962 }
963
964 pub fn contains(&self, key: &CacheKey) -> bool {
966 let key_str = key.to_string_key();
967 self.data
968 .read()
969 .map(|d| d.get(&key_str).map(|e| !e.is_expired()).unwrap_or(false))
970 .unwrap_or(false)
971 }
972
973 pub fn evict_expired(&self) -> usize {
975 let expired_keys: Vec<String> = {
976 let data = match self.data.read() {
978 Ok(d) => d,
979 Err(_) => return 0,
980 };
981 data.iter()
982 .filter(|(_, e)| e.is_expired())
983 .map(|(k, _)| k.clone())
984 .collect()
985 };
986
987 let key_to_table: HashMap<String, String> = match self.table_index.read() {
990 Ok(idx) => {
991 let mut map = HashMap::new();
992 for (table, keys) in idx.iter() {
993 for k in keys {
994 map.insert(k.clone(), table.clone());
995 }
996 }
997 map
998 }
999 Err(_) => HashMap::new(),
1000 };
1001
1002 let mut removed = 0;
1003 if !expired_keys.is_empty() {
1004 let mut data = match self.data.write() {
1006 Ok(d) => d,
1007 Err(_) => return 0,
1008 };
1009 for k in &expired_keys {
1010 if data.remove(k).is_some() {
1011 removed += 1;
1012 }
1013 }
1014 }
1015
1016 if removed > 0 {
1017 if let Ok(mut order) = self.access_order.write() {
1020 for k in &expired_keys {
1021 order.remove(k);
1022 }
1023 }
1024 {
1025 if let Ok(mut stats) = self.stats.write() {
1027 stats.evictions += removed as u64;
1028 }
1029 }
1030 if let Ok(mut tbl_stats) = self.table_stats.write() {
1032 for k in &expired_keys {
1033 if let Some(table) = key_to_table.get(k) {
1034 tbl_stats.entry(table.clone()).or_default().evictions += 1;
1035 }
1036 }
1037 }
1038 }
1039 removed
1040 }
1041
1042 pub fn update_ttl(&self, key: &CacheKey, ttl: Duration) -> bool {
1046 let key_str = key.to_string_key();
1047 let mut data = match self.data.write() {
1048 Ok(d) => d,
1049 Err(_) => return false,
1050 };
1051 if let Some(entry) = data.get_mut(&key_str) {
1052 entry.expires_at = Some(Instant::now() + ttl);
1053 true
1054 } else {
1055 false
1056 }
1057 }
1058
1059 pub fn remaining_ttl(&self, key: &CacheKey) -> Option<Option<Duration>> {
1068 let key_str = key.to_string_key();
1069 let data = self.data.read().ok()?;
1070 let entry = data.get(&key_str)?;
1071 match entry.expires_at {
1072 Some(expires_at) => {
1073 let now = Instant::now();
1074 if expires_at <= now {
1075 None
1076 } else {
1077 Some(Some(expires_at.duration_since(now)))
1078 }
1079 }
1080 None => Some(None),
1081 }
1082 }
1083}
1084
1085impl Cache for L2Cache {
1097 fn get(&self, key: &str) -> Result<Option<Vec<u8>>, CacheError> {
1098 let cache_key = CacheKey::by_pk("__cache__", key);
1099 match L2Cache::get(self, &cache_key) {
1100 Some(Value::Bytes(bytes)) => Ok(Some(bytes)),
1101 Some(other) => {
1102 let json = serde_json::to_vec(&other)
1103 .map_err(|e| CacheError::SerializationError(e.to_string()))?;
1104 Ok(Some(json))
1105 }
1106 None => Ok(None),
1107 }
1108 }
1109
1110 fn set(&self, key: &str, value: Vec<u8>, ttl: Option<Duration>) -> Result<(), CacheError> {
1111 let cache_key = CacheKey::by_pk("__cache__", key);
1112 self.put(&cache_key, Value::Bytes(value), ttl);
1113 Ok(())
1114 }
1115
1116 fn delete(&self, key: &str) -> Result<(), CacheError> {
1117 let cache_key = CacheKey::by_pk("__cache__", key);
1118 self.invalidate(&cache_key);
1119 Ok(())
1120 }
1121
1122 fn clear(&self) -> Result<(), CacheError> {
1123 self.invalidate_table("__cache__");
1126 Ok(())
1127 }
1128
1129 fn exists(&self, key: &str) -> Result<bool, CacheError> {
1130 let cache_key = CacheKey::by_pk("__cache__", key);
1131 Ok(self.contains(&cache_key))
1132 }
1133
1134 fn expire(&self, key: &str, ttl: Duration) -> Result<(), CacheError> {
1135 let cache_key = CacheKey::by_pk("__cache__", key);
1136 if self.update_ttl(&cache_key, ttl) {
1137 Ok(())
1138 } else {
1139 Err(CacheError::NotFound(key.to_string()))
1140 }
1141 }
1142
1143 fn ttl(&self, key: &str) -> Result<Option<Duration>, CacheError> {
1144 let cache_key = CacheKey::by_pk("__cache__", key);
1145 match self.remaining_ttl(&cache_key) {
1146 None => Err(CacheError::NotFound(key.to_string())),
1147 Some(None) => Ok(None),
1148 Some(Some(d)) => Ok(Some(d)),
1149 }
1150 }
1151}
1152
1153pub type L2CacheFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T, CacheError>> + Send + 'a>>;
1162
1163pub trait L2CacheBackend: Send + Sync {
1179 fn get<'a>(&'a self, key: &'a str) -> L2CacheFuture<'a, Option<Vec<u8>>>;
1181
1182 fn set<'a>(
1184 &'a self,
1185 key: &'a str,
1186 value: &'a [u8],
1187 ttl: Option<Duration>,
1188 ) -> L2CacheFuture<'a, ()>;
1189
1190 fn delete<'a>(&'a self, key: &'a str) -> L2CacheFuture<'a, ()>;
1192
1193 fn invalidate_prefix<'a>(&'a self, prefix: &'a str) -> L2CacheFuture<'a, ()>;
1195}
1196
1197pub struct InMemoryBackend {
1211 data: RwLock<InMemoryCacheData>,
1214}
1215
1216type InMemoryCacheData = HashMap<String, (Vec<u8>, Option<Instant>)>;
1218
1219impl Default for InMemoryBackend {
1220 fn default() -> Self {
1221 Self::new()
1222 }
1223}
1224
1225impl InMemoryBackend {
1226 pub fn new() -> Self {
1228 Self {
1229 data: RwLock::new(HashMap::new()),
1230 }
1231 }
1232}
1233
1234impl L2CacheBackend for InMemoryBackend {
1235 fn get<'a>(&'a self, key: &'a str) -> L2CacheFuture<'a, Option<Vec<u8>>> {
1236 let result = {
1238 let data = match self.data.read() {
1239 Ok(d) => d,
1240 Err(e) => {
1241 let err = CacheError::from(e);
1242 return Box::pin(async move { Err(err) });
1243 }
1244 };
1245 match data.get(key) {
1246 Some((value, expiry)) => {
1247 if expiry.map(|t| t <= Instant::now()).unwrap_or(false) {
1249 Ok(None)
1250 } else {
1251 Ok(Some(value.clone()))
1252 }
1253 }
1254 None => Ok(None),
1255 }
1256 };
1257 Box::pin(async move { result })
1258 }
1259
1260 fn set<'a>(
1261 &'a self,
1262 key: &'a str,
1263 value: &'a [u8],
1264 ttl: Option<Duration>,
1265 ) -> L2CacheFuture<'a, ()> {
1266 let result = {
1267 let mut data = match self.data.write() {
1268 Ok(d) => d,
1269 Err(e) => {
1270 let err = CacheError::from(e);
1271 return Box::pin(async move { Err(err) });
1272 }
1273 };
1274 let expiry = ttl.map(|d| Instant::now() + d);
1275 data.insert(key.to_string(), (value.to_vec(), expiry));
1276 Ok(())
1277 };
1278 Box::pin(async move { result })
1279 }
1280
1281 fn delete<'a>(&'a self, key: &'a str) -> L2CacheFuture<'a, ()> {
1282 let result = {
1283 let mut data = match self.data.write() {
1284 Ok(d) => d,
1285 Err(e) => {
1286 let err = CacheError::from(e);
1287 return Box::pin(async move { Err(err) });
1288 }
1289 };
1290 data.remove(key);
1291 Ok(())
1292 };
1293 Box::pin(async move { result })
1294 }
1295
1296 fn invalidate_prefix<'a>(&'a self, prefix: &'a str) -> L2CacheFuture<'a, ()> {
1297 let result = {
1298 let mut data = match self.data.write() {
1299 Ok(d) => d,
1300 Err(e) => {
1301 let err = CacheError::from(e);
1302 return Box::pin(async move { Err(err) });
1303 }
1304 };
1305 let keys_to_remove: Vec<String> = data
1307 .keys()
1308 .filter(|k| k.starts_with(prefix))
1309 .cloned()
1310 .collect();
1311 for k in keys_to_remove {
1312 data.remove(&k);
1313 }
1314 Ok(())
1315 };
1316 Box::pin(async move { result })
1317 }
1318}
1319
1320#[cfg(feature = "redis")]
1363pub struct RedisBackend {
1364 manager: redis::aio::ConnectionManager,
1366}
1367
1368#[cfg(feature = "redis")]
1369impl RedisBackend {
1370 pub async fn new(url: impl Into<String>) -> Result<Self, CacheError> {
1380 let url = url.into();
1381 let client = redis::Client::open(url.as_str())
1382 .map_err(|e| CacheError::Internal(format!("Redis client create failed: {}", e)))?;
1383 let manager = redis::aio::ConnectionManager::new(client)
1384 .await
1385 .map_err(|e| CacheError::Internal(format!("Redis connect failed: {}", e)))?;
1386 Ok(Self { manager })
1387 }
1388
1389 pub fn from_manager(manager: redis::aio::ConnectionManager) -> Self {
1391 Self { manager }
1392 }
1393
1394 async fn invalidate_prefix_inner(&self, prefix: &str) -> Result<(), CacheError> {
1408 let pattern = format!("{}*", prefix);
1409 let mut cursor: u64 = 0;
1410 loop {
1411 let mut conn = self.manager.clone();
1414 let scan_result: redis::RedisResult<(u64, Vec<String>)> = redis::cmd("SCAN")
1415 .arg(cursor)
1416 .arg("MATCH")
1417 .arg(&pattern)
1418 .arg("COUNT")
1419 .arg(100usize)
1420 .query_async(&mut conn)
1421 .await;
1422 let (next_cursor, keys): (u64, Vec<String>) = scan_result
1423 .map_err(|e| CacheError::Internal(format!("Redis SCAN failed: {}", e)))?;
1424
1425 if !keys.is_empty() {
1426 let mut pipe = redis::pipe();
1428 for k in &keys {
1429 pipe.del(k);
1430 }
1431 let del_result: redis::RedisResult<()> = pipe.query_async(&mut conn).await;
1433 del_result.map_err(|e| {
1434 CacheError::Internal(format!("Redis DEL pipeline failed: {}", e))
1435 })?;
1436 }
1437
1438 if next_cursor == 0 {
1440 break;
1441 }
1442 cursor = next_cursor;
1443 }
1444 Ok(())
1445 }
1446}
1447
1448#[cfg(feature = "redis")]
1449impl L2CacheBackend for RedisBackend {
1450 fn get<'a>(&'a self, key: &'a str) -> L2CacheFuture<'a, Option<Vec<u8>>> {
1451 Box::pin(async move {
1452 use redis::AsyncCommands;
1453 let mut conn = self.manager.clone();
1454 let value: Option<Vec<u8>> = conn
1455 .get(key)
1456 .await
1457 .map_err(|e| CacheError::Internal(format!("Redis GET failed: {}", e)))?;
1458 Ok(value)
1459 })
1460 }
1461
1462 fn set<'a>(
1463 &'a self,
1464 key: &'a str,
1465 value: &'a [u8],
1466 ttl: Option<Duration>,
1467 ) -> L2CacheFuture<'a, ()> {
1468 Box::pin(async move {
1469 use redis::AsyncCommands;
1470 let mut conn = self.manager.clone();
1471 match ttl {
1474 Some(d) => {
1475 let secs = d.as_secs();
1476 if secs > 0 {
1477 let _: () = conn.set_ex(key, value, secs).await.map_err(|e| {
1478 CacheError::Internal(format!("Redis SET EX failed: {}", e))
1479 })?;
1480 } else {
1481 let _: () = conn.set(key, value).await.map_err(|e| {
1483 CacheError::Internal(format!("Redis SET failed: {}", e))
1484 })?;
1485 let ms: i64 = d.as_millis().min(i64::MAX as u128) as i64;
1487 let _: () = conn.pexpire(key, ms).await.map_err(|e| {
1488 CacheError::Internal(format!("Redis PEXPIRE failed: {}", e))
1489 })?;
1490 }
1491 }
1492 None => {
1493 let _: () = conn
1494 .set(key, value)
1495 .await
1496 .map_err(|e| CacheError::Internal(format!("Redis SET failed: {}", e)))?;
1497 }
1498 }
1499 Ok(())
1500 })
1501 }
1502
1503 fn delete<'a>(&'a self, key: &'a str) -> L2CacheFuture<'a, ()> {
1504 Box::pin(async move {
1505 use redis::AsyncCommands;
1506 let mut conn = self.manager.clone();
1507 let _: () = conn
1508 .del(key)
1509 .await
1510 .map_err(|e| CacheError::Internal(format!("Redis DEL failed: {}", e)))?;
1511 Ok(())
1512 })
1513 }
1514
1515 fn invalidate_prefix<'a>(&'a self, prefix: &'a str) -> L2CacheFuture<'a, ()> {
1516 Box::pin(async move { self.invalidate_prefix_inner(prefix).await })
1517 }
1518}
1519
1520#[cfg(not(feature = "redis"))]
1525pub struct RedisBackend {
1526 url: String,
1528}
1529
1530#[cfg(not(feature = "redis"))]
1531impl RedisBackend {
1532 pub fn new(_url: impl Into<String>) -> Self {
1537 Self { url: _url.into() }
1538 }
1539}
1540
1541#[cfg(not(feature = "redis"))]
1542impl L2CacheBackend for RedisBackend {
1543 fn get<'a>(&'a self, _key: &'a str) -> L2CacheFuture<'a, Option<Vec<u8>>> {
1544 let url = self.url.clone();
1545 Box::pin(async move {
1546 Err(CacheError::Internal(format!(
1547 "RedisBackend not compiled: enable 'redis' feature in sz-orm-core. URL: {}",
1548 url
1549 )))
1550 })
1551 }
1552
1553 fn set<'a>(
1554 &'a self,
1555 _key: &'a str,
1556 _value: &'a [u8],
1557 _ttl: Option<Duration>,
1558 ) -> L2CacheFuture<'a, ()> {
1559 let url = self.url.clone();
1560 Box::pin(async move {
1561 Err(CacheError::Internal(format!(
1562 "RedisBackend not compiled: enable 'redis' feature in sz-orm-core. URL: {}",
1563 url
1564 )))
1565 })
1566 }
1567
1568 fn delete<'a>(&'a self, _key: &'a str) -> L2CacheFuture<'a, ()> {
1569 let url = self.url.clone();
1570 Box::pin(async move {
1571 Err(CacheError::Internal(format!(
1572 "RedisBackend not compiled: enable 'redis' feature in sz-orm-core. URL: {}",
1573 url
1574 )))
1575 })
1576 }
1577
1578 fn invalidate_prefix<'a>(&'a self, _prefix: &'a str) -> L2CacheFuture<'a, ()> {
1579 let url = self.url.clone();
1580 Box::pin(async move {
1581 Err(CacheError::Internal(format!(
1582 "RedisBackend not compiled: enable 'redis' feature in sz-orm-core. URL: {}",
1583 url
1584 )))
1585 })
1586 }
1587}
1588
1589#[derive(Debug, Clone)]
1616pub enum WriteOp {
1617 Set {
1619 key: String,
1621 value: Vec<u8>,
1623 ttl: Option<Duration>,
1625 },
1626 Delete {
1628 key: String,
1630 },
1631}
1632
1633pub type FlushCallback = Arc<
1638 dyn Fn(Vec<WriteOp>) -> Pin<Box<dyn Future<Output = Result<(), CacheError>> + Send>>
1639 + Send
1640 + Sync,
1641>;
1642
1643pub type ErrorCallback = Arc<dyn Fn(Vec<WriteOp>, CacheError) + Send + Sync>;
1645
1646pub struct WriteBehindWriter {
1685 backend: Arc<dyn L2CacheBackend>,
1687 queue: tokio::sync::Mutex<Vec<WriteOp>>,
1689 on_flush: FlushCallback,
1691 on_error: Option<ErrorCallback>,
1693}
1694
1695impl WriteBehindWriter {
1696 pub fn new(backend: Arc<dyn L2CacheBackend>, on_flush: FlushCallback) -> Self {
1702 Self {
1703 backend,
1704 queue: tokio::sync::Mutex::new(Vec::new()),
1705 on_flush,
1706 on_error: None,
1707 }
1708 }
1709
1710 pub fn with_error_callback(mut self, on_error: ErrorCallback) -> Self {
1715 self.on_error = Some(on_error);
1716 self
1717 }
1718
1719 pub async fn write(
1726 &self,
1727 key: &[u8],
1728 value: &[u8],
1729 ttl: Option<Duration>,
1730 ) -> Result<(), CacheError> {
1731 let key_str = String::from_utf8_lossy(key).into_owned();
1732 self.backend.set(&key_str, value, ttl).await?;
1734 let op = WriteOp::Set {
1736 key: key_str,
1737 value: value.to_vec(),
1738 ttl,
1739 };
1740 self.queue.lock().await.push(op);
1741 Ok(())
1742 }
1743
1744 pub async fn delete(&self, key: &[u8]) -> Result<(), CacheError> {
1746 let key_str = String::from_utf8_lossy(key).into_owned();
1747 self.backend.delete(&key_str).await?;
1749 let op = WriteOp::Delete { key: key_str };
1751 self.queue.lock().await.push(op);
1752 Ok(())
1753 }
1754
1755 pub async fn flush(&self) -> Result<(), CacheError> {
1760 let ops: Vec<WriteOp> = {
1762 let mut guard = self.queue.lock().await;
1763 std::mem::take(&mut *guard)
1764 };
1765 if ops.is_empty() {
1766 return Ok(());
1767 }
1768 match (self.on_flush)(ops.clone()).await {
1770 Ok(()) => Ok(()),
1771 Err(e) => {
1772 let mut guard = self.queue.lock().await;
1774 guard.extend(ops.clone());
1775 if let Some(ref on_error) = self.on_error {
1777 on_error(ops, e.clone());
1778 }
1779 Err(e)
1780 }
1781 }
1782 }
1783
1784 pub async fn pending_count(&self) -> usize {
1786 self.queue.lock().await.len()
1787 }
1788
1789 pub fn spawn_auto_flush(self: Arc<Self>, interval: Duration) -> tokio::task::JoinHandle<()> {
1799 tokio::spawn(async move {
1800 let mut ticker = tokio::time::interval(interval);
1801 ticker.tick().await;
1803 loop {
1804 ticker.tick().await;
1805 if let Err(e) = self.flush().await {
1807 eprintln!("[WriteBehind] auto flush failed: {}", e);
1808 }
1809 }
1810 })
1811 }
1812}
1813
1814#[cfg(test)]
1819mod tests {
1820 use super::*;
1821 use crate::Value;
1822 use std::thread;
1823 use std::time::Duration;
1824
1825 #[test]
1828 fn test_cache_key_by_pk() {
1829 let key = CacheKey::by_pk("users", 1);
1830 assert_eq!(key.table, "users");
1831 assert_eq!(key.kind, CacheKeyKind::ByPk);
1832 assert_eq!(key.identifier, "1");
1833 assert_eq!(key.to_string_key(), "l2:users:pk:1");
1834 }
1835
1836 #[test]
1837 fn test_cache_key_by_query() {
1838 let key = CacheKey::by_query("orders", "abc123");
1839 assert_eq!(key.kind, CacheKeyKind::ByQuery);
1840 assert_eq!(key.to_string_key(), "l2:orders:q:abc123");
1841 }
1842
1843 #[test]
1844 fn test_cache_key_by_relation() {
1845 let key = CacheKey::by_relation("users", "posts:1");
1846 assert_eq!(key.kind, CacheKeyKind::ByRelation);
1847 assert_eq!(key.to_string_key(), "l2:users:rel:posts:1");
1848 }
1849
1850 #[test]
1851 fn test_cache_key_equality() {
1852 let k1 = CacheKey::by_pk("users", 1);
1853 let k2 = CacheKey::by_pk("users", 1);
1854 let k3 = CacheKey::by_pk("users", 2);
1855 assert_eq!(k1, k2);
1856 assert_ne!(k1, k3);
1857 }
1858
1859 #[test]
1860 fn test_cache_key_display() {
1861 let key = CacheKey::by_pk("users", 42);
1862 assert_eq!(format!("{}", key), "l2:users:pk:42");
1863 }
1864
1865 #[test]
1868 fn test_stats_hit_rate_empty() {
1869 let stats = L2CacheStats::default();
1870 assert_eq!(stats.hit_rate(), 0.0);
1871 assert_eq!(stats.total_lookups(), 0);
1872 }
1873
1874 #[test]
1875 fn test_stats_hit_rate_calculation() {
1876 let stats = L2CacheStats {
1877 hits: 80,
1878 misses: 20,
1879 ..Default::default()
1880 };
1881 assert_eq!(stats.total_lookups(), 100);
1882 assert!((stats.hit_rate() - 0.8).abs() < 0.001);
1883 assert!((stats.miss_rate() - 0.2).abs() < 0.001);
1884 }
1885
1886 #[test]
1887 fn test_stats_merge() {
1888 let mut s1 = L2CacheStats {
1889 hits: 10,
1890 misses: 5,
1891 sets: 15,
1892 evictions: 2,
1893 size: 100,
1894 };
1895 let s2 = L2CacheStats {
1896 hits: 20,
1897 misses: 10,
1898 sets: 30,
1899 evictions: 5,
1900 size: 200,
1901 };
1902 s1.merge(&s2);
1903 assert_eq!(s1.hits, 30);
1904 assert_eq!(s1.misses, 15);
1905 assert_eq!(s1.sets, 45);
1906 assert_eq!(s1.evictions, 7);
1907 assert_eq!(s1.size, 300);
1908 }
1909
1910 #[test]
1913 fn test_put_and_get() {
1914 let cache = L2Cache::new();
1915 let key = CacheKey::by_pk("users", 1);
1916
1917 cache.put(&key, Value::String("Alice".to_string()), None);
1918 let val = cache.get(&key);
1919 assert_eq!(val, Some(Value::String("Alice".to_string())));
1920 }
1921
1922 #[test]
1923 fn test_get_missing_returns_none() {
1924 let cache = L2Cache::new();
1925 let key = CacheKey::by_pk("users", 999);
1926 assert_eq!(cache.get(&key), None);
1927 }
1928
1929 #[test]
1930 fn test_overwrite_existing_key() {
1931 let cache = L2Cache::new();
1932 let key = CacheKey::by_pk("users", 1);
1933
1934 cache.put(&key, Value::String("Alice".to_string()), None);
1935 cache.put(&key, Value::String("Bob".to_string()), None);
1936 assert_eq!(cache.get(&key), Some(Value::String("Bob".to_string())));
1937 }
1938
1939 #[test]
1940 fn test_invalidate_single_key() {
1941 let cache = L2Cache::new();
1942 let key = CacheKey::by_pk("users", 1);
1943
1944 cache.put(&key, Value::I64(42), None);
1945 assert!(cache.get(&key).is_some());
1946
1947 cache.invalidate(&key);
1948 assert!(cache.get(&key).is_none());
1949 }
1950
1951 #[test]
1954 fn test_invalidate_table_removes_all_entries_for_table() {
1955 let cache = L2Cache::new();
1956
1957 let k1 = CacheKey::by_pk("users", 1);
1958 let k2 = CacheKey::by_pk("users", 2);
1959 let k3 = CacheKey::by_query("users", "hash1");
1960 let k4 = CacheKey::by_pk("orders", 1); cache.put(&k1, Value::I64(1), None);
1963 cache.put(&k2, Value::I64(2), None);
1964 cache.put(&k3, Value::I64(3), None);
1965 cache.put(&k4, Value::I64(4), None);
1966
1967 cache.invalidate_table("users");
1968
1969 assert!(cache.get(&k1).is_none());
1971 assert!(cache.get(&k2).is_none());
1972 assert!(cache.get(&k3).is_none());
1973 assert!(cache.get(&k4).is_some());
1975 }
1976
1977 #[test]
1978 fn test_invalidate_table_no_op_for_unknown_table() {
1979 let cache = L2Cache::new();
1980 let k1 = CacheKey::by_pk("users", 1);
1981 cache.put(&k1, Value::I64(1), None);
1982
1983 cache.invalidate_table("nonexistent");
1984 assert!(cache.get(&k1).is_some());
1985 }
1986
1987 #[test]
1990 fn test_ttl_expiration() {
1991 let cache = L2Cache::new();
1992 let key = CacheKey::by_pk("users", 1);
1993
1994 cache.put(&key, Value::I64(42), Some(Duration::from_millis(50)));
1995 assert!(cache.get(&key).is_some());
1996
1997 thread::sleep(Duration::from_millis(100));
1999 assert!(cache.get(&key).is_none());
2000 }
2001
2002 #[test]
2003 fn test_default_ttl_applied_when_no_explicit_ttl() {
2004 let cache = L2Cache::new().with_default_ttl(Duration::from_millis(50));
2005 let key = CacheKey::by_pk("users", 1);
2006
2007 cache.put(&key, Value::I64(42), None); assert!(cache.get(&key).is_some());
2009
2010 thread::sleep(Duration::from_millis(100));
2011 assert!(cache.get(&key).is_none());
2012 }
2013
2014 #[test]
2015 fn test_explicit_ttl_overrides_default() {
2016 let cache = L2Cache::new().with_default_ttl(Duration::from_millis(50));
2018 let key = CacheKey::by_pk("users", 1);
2019
2020 cache.put(&key, Value::I64(42), Some(Duration::MAX));
2022
2023 thread::sleep(Duration::from_millis(100));
2025 assert!(cache.get(&key).is_some());
2027 }
2028
2029 #[test]
2030 fn test_none_ttl_uses_default_ttl() {
2031 let cache = L2Cache::new().with_default_ttl(Duration::from_millis(50));
2033 let key = CacheKey::by_pk("users", 1);
2034
2035 cache.put(&key, Value::I64(42), None);
2036 assert!(cache.get(&key).is_some());
2037
2038 thread::sleep(Duration::from_millis(100));
2039 assert!(cache.get(&key).is_none());
2041 }
2042
2043 #[test]
2046 fn test_stats_tracks_hits_and_misses() {
2047 let cache = L2Cache::new();
2048
2049 let k1 = CacheKey::by_pk("users", 1);
2050 let k2 = CacheKey::by_pk("users", 2);
2051
2052 cache.put(&k1, Value::I64(1), None);
2053
2054 cache.get(&k1);
2056 cache.get(&k2);
2058 cache.get(&k2);
2059
2060 let stats = cache.stats();
2061 assert_eq!(stats.hits, 1);
2062 assert_eq!(stats.misses, 2);
2063 assert_eq!(stats.sets, 1);
2064 }
2065
2066 #[test]
2067 fn test_stats_tracks_evictions() {
2068 let cache = L2Cache::new();
2069 let k1 = CacheKey::by_pk("users", 1);
2070 let k2 = CacheKey::by_pk("users", 2);
2071
2072 cache.put(&k1, Value::I64(1), None);
2073 cache.put(&k2, Value::I64(2), None);
2074
2075 cache.invalidate(&k1); cache.invalidate_table("users"); let stats = cache.stats();
2079 assert_eq!(stats.evictions, 2);
2081 }
2082
2083 #[test]
2084 fn test_stats_reset() {
2085 let cache = L2Cache::new();
2086 let k1 = CacheKey::by_pk("users", 1);
2087
2088 cache.put(&k1, Value::I64(1), None);
2089 cache.get(&k1);
2090 cache.get(&k1);
2091
2092 let stats_before = cache.stats();
2093 assert!(stats_before.hits > 0);
2094
2095 cache.reset_stats();
2096 let stats_after = cache.stats();
2097 assert_eq!(stats_after.hits, 0);
2098 assert_eq!(stats_after.misses, 0);
2099 assert_eq!(stats_after.sets, 0);
2100 }
2101
2102 #[test]
2105 fn test_max_size_eviction() {
2106 let cache = L2Cache::new().with_max_size(3);
2107
2108 for i in 0..5 {
2109 let k = CacheKey::by_pk("users", i);
2110 cache.put(&k, Value::I64(i), None);
2111 }
2112
2113 let size = cache.size();
2115 assert_eq!(
2116 size, 3,
2117 "size should be exactly max_size after LRU eviction, got {}",
2118 size
2119 );
2120 }
2121
2122 #[test]
2123 fn test_lru_eviction_order() {
2124 let cache = L2Cache::new().with_max_size(3);
2126
2127 let k0 = CacheKey::by_pk("users", 0);
2128 let k1 = CacheKey::by_pk("users", 1);
2129 let k2 = CacheKey::by_pk("users", 2);
2130 let k3 = CacheKey::by_pk("users", 3);
2131
2132 cache.put(&k0, Value::I64(0), None);
2133 cache.put(&k1, Value::I64(1), None);
2134 cache.put(&k2, Value::I64(2), None);
2135
2136 let _ = cache.get(&k0);
2138
2139 cache.put(&k3, Value::I64(3), None);
2141
2142 assert!(
2143 cache.get(&k0).is_some(),
2144 "k0 should survive (recently accessed)"
2145 );
2146 assert!(
2147 cache.get(&k1).is_none(),
2148 "k1 should be evicted (LRU victim)"
2149 );
2150 assert!(cache.get(&k2).is_some(), "k2 should survive");
2151 assert!(
2152 cache.get(&k3).is_some(),
2153 "k3 should survive (just inserted)"
2154 );
2155 }
2156
2157 #[test]
2158 fn test_clear_all() {
2159 let cache = L2Cache::new();
2160 cache.put(&CacheKey::by_pk("users", 1), Value::I64(1), None);
2161 cache.put(&CacheKey::by_pk("users", 2), Value::I64(2), None);
2162 cache.put(&CacheKey::by_pk("orders", 1), Value::I64(3), None);
2163
2164 assert_eq!(cache.size(), 3);
2165 cache.clear();
2166 assert_eq!(cache.size(), 0);
2167 }
2168
2169 #[test]
2172 fn test_contains_does_not_update_stats() {
2173 let cache = L2Cache::new();
2174 let k1 = CacheKey::by_pk("users", 1);
2175 cache.put(&k1, Value::I64(1), None);
2176
2177 let exists = cache.contains(&k1);
2178 assert!(exists);
2179
2180 let stats = cache.stats();
2181 assert_eq!(stats.hits, 0);
2182 assert_eq!(stats.misses, 0);
2183 }
2184
2185 #[test]
2186 fn test_contains_returns_false_for_missing() {
2187 let cache = L2Cache::new();
2188 let k = CacheKey::by_pk("users", 999);
2189 assert!(!cache.contains(&k));
2190 }
2191
2192 #[test]
2193 fn test_contains_returns_false_for_expired() {
2194 let cache = L2Cache::new();
2195 let k = CacheKey::by_pk("users", 1);
2196 cache.put(&k, Value::I64(1), Some(Duration::from_millis(10)));
2197
2198 thread::sleep(Duration::from_millis(50));
2199 assert!(!cache.contains(&k));
2200 }
2201
2202 #[test]
2205 fn test_evict_expired_removes_only_expired_entries() {
2206 let cache = L2Cache::new();
2207
2208 let k1 = CacheKey::by_pk("users", 1);
2209 let k2 = CacheKey::by_pk("users", 2);
2210
2211 cache.put(&k1, Value::I64(1), Some(Duration::from_millis(10)));
2212 cache.put(&k2, Value::I64(2), None); thread::sleep(Duration::from_millis(50));
2215 let removed = cache.evict_expired();
2216
2217 assert_eq!(removed, 1);
2218 assert!(cache.get(&k1).is_none());
2219 assert!(cache.get(&k2).is_some());
2220 }
2221
2222 #[test]
2223 fn test_evict_expired_returns_zero_if_no_expired() {
2224 let cache = L2Cache::new();
2225 let k1 = CacheKey::by_pk("users", 1);
2226 cache.put(&k1, Value::I64(1), None);
2227
2228 let removed = cache.evict_expired();
2229 assert_eq!(removed, 0);
2230 }
2231
2232 #[test]
2235 fn test_concurrent_access() {
2236 let cache = std::sync::Arc::new(L2Cache::new());
2237 let mut handles = Vec::new();
2238
2239 for i in 0..4 {
2241 let c = cache.clone();
2242 handles.push(thread::spawn(move || {
2243 for j in 0..10 {
2244 let k = CacheKey::by_pk("users", i * 10 + j);
2245 c.put(&k, Value::I64(i * 10 + j), None);
2246 }
2247 }));
2248 }
2249 for h in handles {
2250 h.join().unwrap();
2251 }
2252
2253 assert_eq!(cache.size(), 40);
2254
2255 let mut handles = Vec::new();
2257 for i in 0..4 {
2258 let c = cache.clone();
2259 handles.push(thread::spawn(move || {
2260 for j in 0..10 {
2261 let k = CacheKey::by_pk("users", i * 10 + j);
2262 let v = c.get(&k);
2263 assert!(v.is_some());
2264 }
2265 }));
2266 }
2267 for h in handles {
2268 h.join().unwrap();
2269 }
2270
2271 let stats = cache.stats();
2272 assert_eq!(stats.hits, 40);
2273 }
2274
2275 #[test]
2278 fn test_default() {
2279 let cache = L2Cache::default();
2280 assert_eq!(cache.size(), 0);
2281 }
2282
2283 #[test]
2286 fn test_realistic_scenario() {
2287 let cache = L2Cache::new();
2288
2289 for i in 1..=5 {
2291 cache.put(
2292 &CacheKey::by_pk("users", i),
2293 Value::String(format!("user_{}", i)),
2294 None,
2295 );
2296 }
2297
2298 cache.put(
2300 &CacheKey::by_query("users", "active_users_hash"),
2301 Value::I64(5),
2302 None,
2303 );
2304
2305 for i in 1..=10 {
2307 let _ = cache.get(&CacheKey::by_pk("users", i));
2308 }
2309
2310 let stats = cache.stats();
2311 assert_eq!(stats.hits, 5); assert_eq!(stats.misses, 5); assert_eq!(stats.sets, 6); cache.invalidate_table("users");
2317
2318 cache.reset_stats();
2320 for i in 1..=5 {
2321 let _ = cache.get(&CacheKey::by_pk("users", i));
2322 }
2323 let stats2 = cache.stats();
2324 assert_eq!(stats2.hits, 0);
2325 assert_eq!(stats2.misses, 5);
2326 }
2327
2328 #[tokio::test]
2331 async fn test_write_behind_basic_write_and_flush() {
2332 use std::sync::atomic::{AtomicUsize, Ordering};
2333 let counter = Arc::new(AtomicUsize::new(0));
2335 let counter_clone = counter.clone();
2336 let on_flush: FlushCallback = Arc::new(move |ops: Vec<WriteOp>| {
2337 let c = counter_clone.clone();
2338 Box::pin(async move {
2339 c.fetch_add(ops.len(), Ordering::SeqCst);
2340 Ok(())
2341 })
2342 });
2343 let backend = Arc::new(InMemoryBackend::new());
2344 let writer = WriteBehindWriter::new(backend.clone(), on_flush);
2345
2346 writer.write(b"k1", b"v1", None).await.unwrap();
2348 writer.write(b"k2", b"v2", None).await.unwrap();
2349 writer.write(b"k3", b"v3", None).await.unwrap();
2350
2351 let v1 = backend.get("k1").await.unwrap();
2353 assert_eq!(v1, Some(b"v1".to_vec()));
2354
2355 assert_eq!(writer.pending_count().await, 3);
2357
2358 writer.flush().await.unwrap();
2360 assert_eq!(counter.load(Ordering::SeqCst), 3);
2361 assert_eq!(writer.pending_count().await, 0);
2362 }
2363
2364 #[tokio::test]
2365 async fn test_write_behind_delete() {
2366 let on_flush: FlushCallback =
2367 Arc::new(|_ops: Vec<WriteOp>| Box::pin(async move { Ok(()) }));
2368 let backend = Arc::new(InMemoryBackend::new());
2369 let writer = WriteBehindWriter::new(backend.clone(), on_flush);
2370
2371 writer.write(b"k1", b"v1", None).await.unwrap();
2373 assert!(backend.get("k1").await.unwrap().is_some());
2374 writer.delete(b"k1").await.unwrap();
2375 assert!(backend.get("k1").await.unwrap().is_none());
2377
2378 writer.flush().await.unwrap();
2380 assert_eq!(writer.pending_count().await, 0);
2381 }
2382
2383 #[tokio::test]
2384 async fn test_write_behind_flush_failure_retries() {
2385 let on_flush: FlushCallback = Arc::new(|_ops: Vec<WriteOp>| {
2387 Box::pin(async move { Err(CacheError::Internal("backend down".to_string())) })
2388 });
2389 let backend = Arc::new(InMemoryBackend::new());
2390 let writer = WriteBehindWriter::new(backend.clone(), on_flush);
2391
2392 writer.write(b"k1", b"v1", None).await.unwrap();
2393 let result = writer.flush().await;
2395 assert!(result.is_err());
2396 assert_eq!(writer.pending_count().await, 1);
2397 }
2398
2399 #[tokio::test]
2400 async fn test_write_behind_empty_flush_noop() {
2401 let on_flush: FlushCallback =
2402 Arc::new(|_ops: Vec<WriteOp>| Box::pin(async move { Ok(()) }));
2403 let backend = Arc::new(InMemoryBackend::new());
2404 let writer = WriteBehindWriter::new(backend, on_flush);
2405 writer.flush().await.unwrap();
2407 assert_eq!(writer.pending_count().await, 0);
2408 }
2409
2410 #[tokio::test]
2411 async fn test_write_behind_error_callback_invoked() {
2412 use std::sync::atomic::{AtomicUsize, Ordering};
2413 let error_counter = Arc::new(AtomicUsize::new(0));
2414 let ec = error_counter.clone();
2415 let on_error: ErrorCallback = Arc::new(move |_ops, _err| {
2416 ec.fetch_add(1, Ordering::SeqCst);
2417 });
2418 let on_flush: FlushCallback = Arc::new(|_ops: Vec<WriteOp>| {
2419 Box::pin(async move { Err(CacheError::Internal("fail".to_string())) })
2420 });
2421 let backend = Arc::new(InMemoryBackend::new());
2422 let writer = WriteBehindWriter::new(backend, on_flush).with_error_callback(on_error);
2423
2424 writer.write(b"k1", b"v1", None).await.unwrap();
2425 let _ = writer.flush().await;
2426 assert_eq!(error_counter.load(Ordering::SeqCst), 1);
2427 }
2428
2429 #[tokio::test]
2432 async fn test_query_cache_hit() {
2433 use std::collections::HashMap;
2434
2435 let cache = L2Cache::new();
2436 let mut call_count = 0;
2437
2438 let rows1 = cache
2440 .get_or_load_query(
2441 "users",
2442 "SELECT * FROM users WHERE status = ?",
2443 &[crate::value::Value::I64(1)],
2444 std::time::Duration::from_secs(300),
2445 || {
2446 call_count += 1;
2447 async {
2448 let mut row = HashMap::new();
2449 row.insert("id".to_string(), crate::value::Value::I64(1));
2450 row.insert(
2451 "name".to_string(),
2452 crate::value::Value::String("Alice".to_string()),
2453 );
2454 Ok(vec![row])
2455 }
2456 },
2457 )
2458 .await
2459 .unwrap();
2460
2461 assert_eq!(call_count, 1, "第一次查询应调用 loader");
2462 assert_eq!(rows1.len(), 1, "应返回 1 行");
2463
2464 let rows2 = cache
2466 .get_or_load_query(
2467 "users",
2468 "SELECT * FROM users WHERE status = ?",
2469 &[crate::value::Value::I64(1)],
2470 std::time::Duration::from_secs(300),
2471 || {
2472 call_count += 1;
2473 async { Ok(vec![]) }
2474 },
2475 )
2476 .await
2477 .unwrap();
2478
2479 assert_eq!(call_count, 1, "第二次查询不应调用 loader(缓存命中)");
2480 assert_eq!(rows2.len(), 1, "应返回缓存的 1 行");
2481 }
2482
2483 #[tokio::test]
2484 async fn test_query_cache_empty_result_cached() {
2485 let cache = L2Cache::new();
2486 let mut call_count = 0;
2487
2488 let rows1 = cache
2490 .get_or_load_query(
2491 "users",
2492 "SELECT * FROM users WHERE status = ?",
2493 &[crate::value::Value::I64(999)],
2494 std::time::Duration::from_secs(300),
2495 || {
2496 call_count += 1;
2497 async { Ok(vec![]) }
2498 },
2499 )
2500 .await
2501 .unwrap();
2502
2503 assert_eq!(call_count, 1, "第一次查询应调用 loader");
2504 assert_eq!(rows1.len(), 0, "应返回空结果");
2505
2506 let rows2 = cache
2508 .get_or_load_query(
2509 "users",
2510 "SELECT * FROM users WHERE status = ?",
2511 &[crate::value::Value::I64(999)],
2512 std::time::Duration::from_secs(300),
2513 || {
2514 call_count += 1;
2515 async { Ok(vec![]) }
2516 },
2517 )
2518 .await
2519 .unwrap();
2520
2521 assert_eq!(call_count, 1, "第二次查询不应调用 loader(空结果缓存命中)");
2522 assert_eq!(rows2.len(), 0, "应返回缓存的空结果");
2523 }
2524
2525 #[tokio::test]
2526 async fn test_query_cache_different_params() {
2527 use std::collections::HashMap;
2528
2529 let cache = L2Cache::new();
2530 let mut call_count = 0;
2531
2532 let _ = cache
2534 .get_or_load_query(
2535 "users",
2536 "SELECT * FROM users WHERE status = ?",
2537 &[crate::value::Value::I64(1)],
2538 std::time::Duration::from_secs(300),
2539 || {
2540 call_count += 1;
2541 async {
2542 let mut row = HashMap::new();
2543 row.insert("id".to_string(), crate::value::Value::I64(1));
2544 Ok(vec![row])
2545 }
2546 },
2547 )
2548 .await
2549 .unwrap();
2550
2551 let rows2 = cache
2553 .get_or_load_query(
2554 "users",
2555 "SELECT * FROM users WHERE status = ?",
2556 &[crate::value::Value::I64(2)],
2557 std::time::Duration::from_secs(300),
2558 || {
2559 call_count += 1;
2560 async {
2561 let mut row = HashMap::new();
2562 row.insert("id".to_string(), crate::value::Value::I64(2));
2563 row.insert(
2564 "name".to_string(),
2565 crate::value::Value::String("Bob".to_string()),
2566 );
2567 Ok(vec![row])
2568 }
2569 },
2570 )
2571 .await
2572 .unwrap();
2573
2574 assert_eq!(call_count, 2, "不同参数应调用 loader 两次");
2575 assert_eq!(rows2.len(), 1, "应返回 1 行");
2576 }
2577
2578 #[tokio::test]
2579 async fn test_query_cache_invalidate() {
2580 use std::collections::HashMap;
2581
2582 let cache = L2Cache::new();
2583 let mut call_count = 0;
2584
2585 let _ = cache
2587 .get_or_load_query(
2588 "users",
2589 "SELECT * FROM users WHERE status = ?",
2590 &[crate::value::Value::I64(1)],
2591 std::time::Duration::from_secs(300),
2592 || {
2593 call_count += 1;
2594 async {
2595 let mut row = HashMap::new();
2596 row.insert("id".to_string(), crate::value::Value::I64(1));
2597 Ok(vec![row])
2598 }
2599 },
2600 )
2601 .await
2602 .unwrap();
2603
2604 assert_eq!(call_count, 1, "第一次查询应调用 loader");
2605
2606 cache.invalidate_query(
2608 "users",
2609 "SELECT * FROM users WHERE status = ?",
2610 &[crate::value::Value::I64(1)],
2611 );
2612
2613 let _ = cache
2615 .get_or_load_query(
2616 "users",
2617 "SELECT * FROM users WHERE status = ?",
2618 &[crate::value::Value::I64(1)],
2619 std::time::Duration::from_secs(300),
2620 || {
2621 call_count += 1;
2622 async { Ok(vec![]) }
2623 },
2624 )
2625 .await
2626 .unwrap();
2627
2628 assert_eq!(call_count, 2, "失效后应重新调用 loader");
2629 }
2630
2631 #[tokio::test]
2632 async fn test_query_cache_hit_rate() {
2633 use std::collections::HashMap;
2634
2635 let cache = L2Cache::new();
2636 let mut call_count = 0;
2637
2638 for _ in 0..10 {
2640 let _ = cache
2641 .get_or_load_query(
2642 "users",
2643 "SELECT * FROM users WHERE status = ?",
2644 &[crate::value::Value::I64(1)],
2645 std::time::Duration::from_secs(300),
2646 || {
2647 call_count += 1;
2648 async {
2649 let mut row = HashMap::new();
2650 row.insert("id".to_string(), crate::value::Value::I64(1));
2651 Ok(vec![row])
2652 }
2653 },
2654 )
2655 .await
2656 .unwrap();
2657 }
2658
2659 assert_eq!(call_count, 1, "10 次查询中只有 1 次调用 loader");
2661
2662 let stats = cache.stats();
2663 assert_eq!(stats.hits, 9, "应命中 9 次");
2664 assert_eq!(stats.misses, 1, "应未命中 1 次");
2665
2666 let hit_rate = stats.hit_rate();
2667 assert!(
2668 hit_rate >= 0.8,
2669 "命中率应 >= 80%,实际: {:.2}%",
2670 hit_rate * 100.0
2671 );
2672 }
2673}