1use std::collections::HashMap;
15use std::sync::atomic::{AtomicU64, Ordering};
16use std::sync::Arc;
17use std::time::{Duration, Instant};
18
19use parking_lot::RwLock;
20use sqlparser::ast::Statement;
21use sqlparser::dialect::GenericDialect;
22use sqlparser::parser::Parser;
23use std::hash::Hasher;
24use twox_hash::XxHash64;
25
26pub struct SqlNormalizer;
33
34impl SqlNormalizer {
35 pub fn normalize(sql: &str) -> String {
41 let dialect = GenericDialect {};
42 match Parser::parse_sql(&dialect, sql) {
43 Ok(statements) => {
44 if statements.is_empty() {
45 return sql.trim().to_lowercase();
46 }
47 let normalized: Vec<String> =
48 statements.iter().map(|stmt| stmt.to_string()).collect();
49 normalized.join("; ")
50 }
51 Err(_) => {
52 let trimmed: String = sql.split_whitespace().collect::<Vec<_>>().join(" ");
53 trimmed.to_lowercase()
54 }
55 }
56 }
57
58 pub fn extract_tables(sql: &str) -> Vec<String> {
62 let dialect = GenericDialect {};
63 let mut tables = Vec::new();
64
65 if let Ok(statements) = Parser::parse_sql(&dialect, sql) {
66 for stmt in &statements {
67 Self::extract_tables_from_stmt(stmt, &mut tables);
68 }
69 }
70
71 if tables.is_empty() {
72 Self::extract_tables_from_str(sql, &mut tables);
73 }
74
75 tables.sort();
76 tables.dedup();
77 tables
78 }
79
80 fn extract_tables_from_stmt(stmt: &Statement, tables: &mut Vec<String>) {
81 use sqlparser::ast::SetExpr;
82
83 match stmt {
84 Statement::Query(query) => {
85 if let SetExpr::Select(select) = &*query.body {
86 for from in &select.from {
87 Self::extract_table_factor(&from.relation, tables);
88 for join in &from.joins {
89 Self::extract_table_factor(&join.relation, tables);
90 }
91 }
92 }
93 }
94 Statement::Insert(_) => {}
95 Statement::Update { table, .. } => {
96 Self::extract_table_factor(&table.relation, tables);
97 }
98 Statement::Delete(delete) => {
99 for table_name in &delete.tables {
100 let full_name = table_name
101 .0
102 .iter()
103 .map(|i| i.value.clone())
104 .collect::<Vec<_>>()
105 .join(".");
106 tables.push(full_name);
107 }
108 }
109 _ => {}
110 }
111 }
112
113 fn extract_table_factor(factor: &sqlparser::ast::TableFactor, tables: &mut Vec<String>) {
114 use sqlparser::ast::TableFactor;
115 if let TableFactor::Table { name, .. } = factor {
116 let full_name = name
117 .0
118 .iter()
119 .map(|i| i.value.clone())
120 .collect::<Vec<_>>()
121 .join(".");
122 tables.push(full_name);
123 }
124 }
125
126 fn extract_tables_from_str(sql: &str, tables: &mut Vec<String>) {
127 let lower = sql.to_lowercase();
128 for keyword in ["into ", "from ", "update ", "join "] {
129 let mut search_pos = 0;
130 while let Some(pos) = lower[search_pos..].find(keyword) {
131 let abs_pos = search_pos + pos;
132 let rest = &sql[abs_pos + keyword.len()..];
133 let table: String = rest
134 .chars()
135 .take_while(|c| c.is_alphanumeric() || *c == '_' || *c == '.')
136 .collect();
137 if !table.is_empty() && !table.chars().all(|c| c.is_numeric()) {
138 tables.push(table);
139 }
140 search_pos = abs_pos + keyword.len();
141 }
142 }
143 }
144}
145
146#[derive(Debug, Clone)]
153pub struct PlanCacheKey {
154 pub hash: u64,
156 pub sql_normalized: String,
158}
159
160impl PlanCacheKey {
161 pub fn from_sql(sql: &str) -> Self {
166 let sql_normalized = SqlNormalizer::normalize(sql);
167 let mut h = XxHash64::with_seed(0);
168 h.write(sql_normalized.as_bytes());
169 let hash = h.finish();
170 Self {
171 hash,
172 sql_normalized,
173 }
174 }
175}
176
177impl PartialEq for PlanCacheKey {
178 fn eq(&self, other: &Self) -> bool {
179 self.hash == other.hash && self.sql_normalized == other.sql_normalized
180 }
181}
182
183impl Eq for PlanCacheKey {}
184
185impl std::hash::Hash for PlanCacheKey {
186 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
187 self.hash.hash(state);
188 }
189}
190
191pub struct PlanCacheEntry {
197 pub ast: Option<Arc<Statement>>,
199 pub analysis: Option<Arc<String>>,
201 pub created_at: Instant,
203 pub tables: Vec<String>,
205 pub ttl: Option<Duration>,
207}
208
209impl PlanCacheEntry {
210 pub fn is_expired(&self) -> bool {
212 if let Some(ttl) = self.ttl {
213 self.created_at.elapsed() >= ttl
214 } else {
215 false
216 }
217 }
218}
219
220pub struct PlanCacheStats {
226 parse_hits: AtomicU64,
227 parse_misses: AtomicU64,
228 optimize_hits: AtomicU64,
229 optimize_misses: AtomicU64,
230 evictions: AtomicU64,
231}
232
233impl PlanCacheStats {
234 fn new() -> Self {
235 Self {
236 parse_hits: AtomicU64::new(0),
237 parse_misses: AtomicU64::new(0),
238 optimize_hits: AtomicU64::new(0),
239 optimize_misses: AtomicU64::new(0),
240 evictions: AtomicU64::new(0),
241 }
242 }
243
244 pub fn parse_hits(&self) -> u64 {
246 self.parse_hits.load(Ordering::Relaxed)
247 }
248
249 pub fn parse_misses(&self) -> u64 {
251 self.parse_misses.load(Ordering::Relaxed)
252 }
253
254 pub fn optimize_hits(&self) -> u64 {
256 self.optimize_hits.load(Ordering::Relaxed)
257 }
258
259 pub fn optimize_misses(&self) -> u64 {
261 self.optimize_misses.load(Ordering::Relaxed)
262 }
263
264 pub fn evictions(&self) -> u64 {
266 self.evictions.load(Ordering::Relaxed)
267 }
268
269 pub fn parse_hit_rate(&self) -> f64 {
271 let hits = self.parse_hits();
272 let misses = self.parse_misses();
273 let total = hits + misses;
274 if total == 0 {
275 0.0
276 } else {
277 hits as f64 / total as f64
278 }
279 }
280
281 pub fn optimize_hit_rate(&self) -> f64 {
283 let hits = self.optimize_hits();
284 let misses = self.optimize_misses();
285 let total = hits + misses;
286 if total == 0 {
287 0.0
288 } else {
289 hits as f64 / total as f64
290 }
291 }
292}
293
294impl Default for PlanCacheStats {
295 fn default() -> Self {
296 Self::new()
297 }
298}
299
300#[derive(Debug, Clone)]
304pub struct PlanCacheStatsSnapshot {
305 pub parse_hits: u64,
307 pub parse_misses: u64,
309 pub optimize_hits: u64,
311 pub optimize_misses: u64,
313 pub evictions: u64,
315 pub size: usize,
317 pub parse_hit_rate: f64,
319 pub optimize_hit_rate: f64,
321}
322
323struct LruOrder64 {
327 nodes: Vec<LruNode64>,
328 free_list: Vec<usize>,
329 index: HashMap<u64, usize>,
330 head: Option<usize>,
331 tail: Option<usize>,
332}
333
334struct LruNode64 {
335 key: u64,
336 prev: Option<usize>,
337 next: Option<usize>,
338}
339
340impl LruOrder64 {
341 fn new() -> Self {
342 Self {
343 nodes: Vec::new(),
344 free_list: Vec::new(),
345 index: HashMap::new(),
346 head: None,
347 tail: None,
348 }
349 }
350
351 fn touch(&mut self, key: u64) {
352 if let Some(&idx) = self.index.get(&key) {
353 self.unlink(idx);
354 self.link_tail(idx);
355 } else {
356 let idx = self.alloc_node(key);
357 self.link_tail(idx);
358 self.index.insert(key, idx);
359 }
360 }
361
362 fn remove(&mut self, key: u64) {
363 if let Some(idx) = self.index.remove(&key) {
364 self.unlink(idx);
365 self.free_node(idx);
366 }
367 }
368
369 fn lru_key(&self) -> Option<u64> {
370 self.head.map(|idx| self.nodes[idx].key)
371 }
372
373 fn clear(&mut self) {
374 self.nodes.clear();
375 self.free_list.clear();
376 self.index.clear();
377 self.head = None;
378 self.tail = None;
379 }
380
381 fn len(&self) -> usize {
382 self.index.len()
383 }
384
385 fn alloc_node(&mut self, key: u64) -> usize {
386 if let Some(idx) = self.free_list.pop() {
387 self.nodes[idx] = LruNode64 {
388 key,
389 prev: None,
390 next: None,
391 };
392 idx
393 } else {
394 self.nodes.push(LruNode64 {
395 key,
396 prev: None,
397 next: None,
398 });
399 self.nodes.len() - 1
400 }
401 }
402
403 fn free_node(&mut self, idx: usize) {
404 self.free_list.push(idx);
405 }
406
407 fn unlink(&mut self, idx: usize) {
408 let prev = self.nodes[idx].prev;
409 let next = self.nodes[idx].next;
410 match prev {
411 Some(p) => self.nodes[p].next = next,
412 None => self.head = next,
413 }
414 match next {
415 Some(n) => self.nodes[n].prev = prev,
416 None => self.tail = prev,
417 }
418 self.nodes[idx].prev = None;
419 self.nodes[idx].next = None;
420 }
421
422 fn link_tail(&mut self, idx: usize) {
423 match self.tail {
424 Some(t) => {
425 self.nodes[idx].prev = Some(t);
426 self.nodes[t].next = Some(idx);
427 }
428 None => {
429 self.head = Some(idx);
430 }
431 }
432 self.tail = Some(idx);
433 }
434}
435
436pub struct PlanCache {
447 parse_cache: RwLock<HashMap<u64, PlanCacheEntry>>,
449 optimize_cache: RwLock<HashMap<u64, PlanCacheEntry>>,
451 access_order: RwLock<LruOrder64>,
453 table_index: RwLock<HashMap<String, Vec<u64>>>,
455 stats: PlanCacheStats,
457 max_size: usize,
459 default_ttl: Option<Duration>,
461}
462
463impl PlanCache {
464 pub fn new(max_size: usize, default_ttl: Option<Duration>) -> Self {
469 Self {
470 parse_cache: RwLock::new(HashMap::new()),
471 optimize_cache: RwLock::new(HashMap::new()),
472 access_order: RwLock::new(LruOrder64::new()),
473 table_index: RwLock::new(HashMap::new()),
474 stats: PlanCacheStats::new(),
475 max_size,
476 default_ttl,
477 }
478 }
479
480 pub fn get_or_parse(&self, sql: &str) -> Result<Arc<Statement>, String> {
485 let key = PlanCacheKey::from_sql(sql);
486
487 {
488 let cache = self.parse_cache.read();
489 if let Some(entry) = cache.get(&key.hash) {
490 if !entry.is_expired() {
491 if let Some(ast) = &entry.ast {
492 self.stats.parse_hits.fetch_add(1, Ordering::Relaxed);
493 self.access_order.write().touch(key.hash);
494 return Ok(ast.clone());
495 }
496 }
497 }
498 }
499
500 self.stats.parse_misses.fetch_add(1, Ordering::Relaxed);
501
502 let dialect = GenericDialect {};
503 let statements = Parser::parse_sql(&dialect, sql).map_err(|e| e.to_string())?;
504 if statements.is_empty() {
505 return Err("empty SQL".to_string());
506 }
507 let ast = Arc::new(statements.into_iter().next().unwrap());
508 let tables = SqlNormalizer::extract_tables(sql);
509
510 {
511 let mut access_order = self.access_order.write();
512 let mut cache = self.parse_cache.write();
513 let mut table_index = self.table_index.write();
514
515 if cache.len() >= self.max_size {
516 if let Some(lru_hash) = access_order.lru_key() {
517 access_order.remove(lru_hash);
518 cache.remove(&lru_hash);
519 self.optimize_cache.write().remove(&lru_hash);
520 Self::remove_from_table_index(&mut table_index, lru_hash);
521 self.stats.evictions.fetch_add(1, Ordering::Relaxed);
522 }
523 }
524
525 let entry = PlanCacheEntry {
526 ast: Some(ast.clone()),
527 analysis: None,
528 created_at: Instant::now(),
529 tables: tables.clone(),
530 ttl: self.default_ttl,
531 };
532 cache.insert(key.hash, entry);
533 access_order.touch(key.hash);
534
535 for table in &tables {
536 table_index.entry(table.clone()).or_default().push(key.hash);
537 }
538 }
539
540 Ok(ast)
541 }
542
543 pub fn get_or_optimize(&self, sql: &str) -> Option<Arc<String>> {
548 let key = PlanCacheKey::from_sql(sql);
549
550 {
551 let cache = self.optimize_cache.read();
552 if let Some(entry) = cache.get(&key.hash) {
553 if !entry.is_expired() {
554 if let Some(analysis) = &entry.analysis {
555 self.stats.optimize_hits.fetch_add(1, Ordering::Relaxed);
556 self.access_order.write().touch(key.hash);
557 return Some(analysis.clone());
558 }
559 }
560 }
561 }
562
563 self.stats.optimize_misses.fetch_add(1, Ordering::Relaxed);
564 None
565 }
566
567 pub fn store_optimize(&self, sql: &str, analysis: Arc<String>) {
571 let key = PlanCacheKey::from_sql(sql);
572 let tables = SqlNormalizer::extract_tables(sql);
573
574 let mut access_order = self.access_order.write();
575 let mut cache = self.optimize_cache.write();
576 let mut table_index = self.table_index.write();
577
578 if cache.len() >= self.max_size {
579 if let Some(lru_hash) = access_order.lru_key() {
580 access_order.remove(lru_hash);
581 cache.remove(&lru_hash);
582 self.parse_cache.write().remove(&lru_hash);
583 Self::remove_from_table_index(&mut table_index, lru_hash);
584 self.stats.evictions.fetch_add(1, Ordering::Relaxed);
585 }
586 }
587
588 let entry = PlanCacheEntry {
589 ast: None,
590 analysis: Some(analysis),
591 created_at: Instant::now(),
592 tables: tables.clone(),
593 ttl: self.default_ttl,
594 };
595 cache.insert(key.hash, entry);
596 access_order.touch(key.hash);
597
598 for table in &tables {
599 table_index.entry(table.clone()).or_default().push(key.hash);
600 }
601 }
602
603 pub fn invalidate_table(&self, table: &str) -> usize {
607 let mut table_index = self.table_index.write();
608 let keys = table_index.remove(table).unwrap_or_default();
609
610 if keys.is_empty() {
611 return 0;
612 }
613
614 let count = keys.len();
615 let mut access_order = self.access_order.write();
616 let mut parse_cache = self.parse_cache.write();
617 let mut optimize_cache = self.optimize_cache.write();
618
619 for &hash in &keys {
620 access_order.remove(hash);
621 parse_cache.remove(&hash);
622 optimize_cache.remove(&hash);
623 }
624
625 for remaining_keys in table_index.values_mut() {
626 remaining_keys.retain(|k| !keys.contains(k));
627 }
628
629 self.stats
630 .evictions
631 .fetch_add(count as u64, Ordering::Relaxed);
632 count
633 }
634
635 pub fn invalidate_all(&self) {
637 self.parse_cache.write().clear();
638 self.optimize_cache.write().clear();
639 self.access_order.write().clear();
640 self.table_index.write().clear();
641 }
642
643 pub fn stats(&self) -> PlanCacheStatsSnapshot {
645 let size = self.access_order.read().len();
646 PlanCacheStatsSnapshot {
647 parse_hits: self.stats.parse_hits(),
648 parse_misses: self.stats.parse_misses(),
649 optimize_hits: self.stats.optimize_hits(),
650 optimize_misses: self.stats.optimize_misses(),
651 evictions: self.stats.evictions(),
652 size,
653 parse_hit_rate: self.stats.parse_hit_rate(),
654 optimize_hit_rate: self.stats.optimize_hit_rate(),
655 }
656 }
657
658 pub fn size(&self) -> usize {
660 self.access_order.read().len()
661 }
662
663 fn remove_from_table_index(table_index: &mut HashMap<String, Vec<u64>>, hash: u64) {
664 for keys in table_index.values_mut() {
665 keys.retain(|k| *k != hash);
666 }
667 }
668}
669
670impl Default for PlanCache {
671 fn default() -> Self {
672 Self::new(1024, None)
673 }
674}
675
676#[cfg(test)]
679mod tests {
680 use super::*;
681
682 #[test]
683 fn test_sql_normalizer_basic() {
684 let sql1 = "SELECT * FROM users WHERE id = ?";
685 let sql2 = "select * from users where id = ?";
686 let n1 = SqlNormalizer::normalize(sql1);
687 let n2 = SqlNormalizer::normalize(sql2);
688 assert_eq!(n1, n2, "大小写差异应归一化");
689 }
690
691 #[test]
692 fn test_sql_normalizer_whitespace() {
693 let sql1 = "SELECT * FROM users";
694 let sql2 = "SELECT * FROM users";
695 let n1 = SqlNormalizer::normalize(sql1);
696 let n2 = SqlNormalizer::normalize(sql2);
697 assert_eq!(n1, n2, "空白差异应归一化");
698 }
699
700 #[test]
701 fn test_sql_normalizer_different_semantics() {
702 let sql1 = "SELECT * FROM users WHERE id = ?";
703 let sql2 = "SELECT * FROM orders WHERE id = ?";
704 let n1 = SqlNormalizer::normalize(sql1);
705 let n2 = SqlNormalizer::normalize(sql2);
706 assert_ne!(n1, n2, "不同表名应产生不同归一化");
707 }
708
709 #[test]
710 fn test_sql_normalizer_parse_error_fallback() {
711 let sql = "this is not valid sql !!!";
712 let normalized = SqlNormalizer::normalize(sql);
713 assert!(!normalized.is_empty());
714 }
715
716 #[test]
717 fn test_extract_tables_select() {
718 let sql = "SELECT * FROM users JOIN orders ON users.id = orders.user_id";
719 let tables = SqlNormalizer::extract_tables(sql);
720 assert!(tables.contains(&"users".to_string()));
721 assert!(tables.contains(&"orders".to_string()));
722 }
723
724 #[test]
725 fn test_extract_tables_insert() {
726 let sql = "INSERT INTO products (name) VALUES (?)";
727 let tables = SqlNormalizer::extract_tables(sql);
728 assert!(tables.contains(&"products".to_string()));
729 }
730
731 #[test]
732 fn test_extract_tables_update() {
733 let sql = "UPDATE products SET name = ? WHERE id = ?";
734 let tables = SqlNormalizer::extract_tables(sql);
735 assert!(tables.contains(&"products".to_string()));
736 }
737
738 #[test]
739 fn test_extract_tables_delete() {
740 let sql = "DELETE FROM products WHERE id = ?";
741 let tables = SqlNormalizer::extract_tables(sql);
742 assert!(
743 tables.iter().any(|t| t.contains("products")),
744 "应包含 products 表,实际: {:?}",
745 tables
746 );
747 }
748
749 #[test]
750 fn test_plan_cache_key_same_sql() {
751 let k1 = PlanCacheKey::from_sql("SELECT * FROM users WHERE id = ?");
752 let k2 = PlanCacheKey::from_sql("select * from users where id = ?");
753 assert_eq!(k1.hash, k2.hash, "相同 SQL 模板应产生相同 hash");
754 }
755
756 #[test]
757 fn test_plan_cache_key_different_sql() {
758 let k1 = PlanCacheKey::from_sql("SELECT * FROM users");
759 let k2 = PlanCacheKey::from_sql("SELECT * FROM orders");
760 assert_ne!(k1.hash, k2.hash, "不同 SQL 应产生不同 hash");
761 }
762
763 #[test]
764 fn test_plan_cache_key_no_sensitive_data() {
765 let key = PlanCacheKey::from_sql("SELECT * FROM users WHERE password = ?");
766 assert!(
767 !key.sql_normalized.contains("secret123"),
768 "参数化查询缓存键不应包含参数值"
769 );
770 }
771
772 #[test]
773 fn test_plan_cache_new() {
774 let cache = PlanCache::new(1024, None);
775 assert_eq!(cache.size(), 0);
776 let stats = cache.stats();
777 assert_eq!(stats.size, 0);
778 assert_eq!(stats.parse_hits, 0);
779 assert_eq!(stats.parse_misses, 0);
780 }
781
782 #[test]
783 fn test_plan_cache_default() {
784 let cache = PlanCache::default();
785 assert_eq!(cache.size(), 0);
786 }
787
788 #[test]
789 fn test_get_or_parse_hit() {
790 let cache = PlanCache::new(100, None);
791 let sql = "SELECT * FROM users WHERE id = ?";
792
793 let ast1 = cache.get_or_parse(sql).expect("parse");
794 assert_eq!(cache.stats().parse_misses, 1, "首次应 miss");
795
796 let ast2 = cache.get_or_parse(sql).expect("parse");
797 assert_eq!(cache.stats().parse_hits, 1, "第二次应 hit");
798 assert_eq!(cache.stats().parse_misses, 1, "misses 不变");
799
800 assert!(Arc::ptr_eq(&ast1, &ast2), "命中应返回相同 Arc");
801 }
802
803 #[test]
804 fn test_get_or_parse_different_params_same_template() {
805 let cache = PlanCache::new(100, None);
806 let sql1 = "SELECT * FROM users WHERE id = ?";
807 let sql2 = "select * from users where id = ?";
808
809 cache.get_or_parse(sql1).expect("parse");
810 cache.get_or_parse(sql2).expect("parse");
811
812 assert_eq!(cache.stats().parse_hits, 1, "相同模板不同写法应命中");
813 assert_eq!(cache.stats().parse_misses, 1);
814 }
815
816 #[test]
817 fn test_get_or_parse_different_sql() {
818 let cache = PlanCache::new(100, None);
819 cache.get_or_parse("SELECT * FROM users").expect("parse");
820 cache.get_or_parse("SELECT * FROM orders").expect("parse");
821
822 assert_eq!(cache.stats().parse_misses, 2, "不同 SQL 应各 miss 一次");
823 assert_eq!(cache.stats().parse_hits, 0);
824 }
825
826 #[test]
827 fn test_get_or_optimize_miss_then_store_then_hit() {
828 let cache = PlanCache::new(100, None);
829 let sql = "SELECT * FROM users WHERE id = ?";
830
831 assert!(cache.get_or_optimize(sql).is_none(), "首次应 miss");
832 assert_eq!(cache.stats().optimize_misses, 1);
833
834 cache.store_optimize(sql, Arc::new("optimized plan".to_string()));
835
836 let result = cache.get_or_optimize(sql);
837 assert!(result.is_some(), "存储后应 hit");
838 assert_eq!(cache.stats().optimize_hits, 1);
839 assert_eq!(*result.unwrap().as_ref(), "optimized plan");
840 }
841
842 #[test]
843 fn test_invalidate_table_precise() {
844 let cache = PlanCache::new(100, None);
845 cache.get_or_parse("SELECT * FROM users").expect("parse");
846 cache.get_or_parse("SELECT * FROM orders").expect("parse");
847 assert_eq!(cache.size(), 2);
848
849 let evicted = cache.invalidate_table("users");
850 assert_eq!(evicted, 1, "应失效 1 条");
851 assert_eq!(cache.size(), 1, "应剩余 1 条");
852
853 let stats = cache.stats();
854 assert!(stats.parse_hits == 0, "orders 缓存应不受影响");
855 cache.get_or_parse("SELECT * FROM orders").expect("parse");
856 assert_eq!(cache.stats().parse_hits, 1, "orders 应命中缓存");
857 }
858
859 #[test]
860 fn test_invalidate_table_nonexistent() {
861 let cache = PlanCache::new(100, None);
862 cache.get_or_parse("SELECT * FROM users").expect("parse");
863 let evicted = cache.invalidate_table("nonexistent");
864 assert_eq!(evicted, 0, "不存在的表应返回 0");
865 assert_eq!(cache.size(), 1, "缓存不应受影响");
866 }
867
868 #[test]
869 fn test_invalidate_all() {
870 let cache = PlanCache::new(100, None);
871 cache.get_or_parse("SELECT * FROM users").expect("parse");
872 cache.get_or_parse("SELECT * FROM orders").expect("parse");
873 assert_eq!(cache.size(), 2);
874
875 cache.invalidate_all();
876 assert_eq!(cache.size(), 0, "全量清空后 size 应为 0");
877 }
878
879 #[test]
880 fn test_lru_eviction() {
881 let cache = PlanCache::new(3, None);
882 cache.get_or_parse("SELECT * FROM t1").expect("parse");
883 cache.get_or_parse("SELECT * FROM t2").expect("parse");
884 cache.get_or_parse("SELECT * FROM t3").expect("parse");
885 assert_eq!(cache.size(), 3);
886
887 cache.get_or_parse("SELECT * FROM t4").expect("parse");
888 assert_eq!(cache.size(), 3, "max_size=3 应保持 3 条");
889 assert!(cache.stats().evictions >= 1, "应有淘汰");
890
891 cache.get_or_parse("SELECT * FROM t1").expect("parse");
892 assert!(cache.stats().parse_misses >= 4, "t1 被淘汰后应重新 miss");
893 }
894
895 #[test]
896 fn test_lru_eviction_max_size_1() {
897 let cache = PlanCache::new(1, None);
898 cache.get_or_parse("SELECT * FROM t1").expect("parse");
899 assert_eq!(cache.size(), 1);
900
901 cache.get_or_parse("SELECT * FROM t2").expect("parse");
902 assert_eq!(cache.size(), 1, "max_size=1 应保持 1 条");
903
904 cache.get_or_parse("SELECT * FROM t1").expect("parse");
905 assert!(cache.stats().parse_misses >= 3, "t1 应被淘汰后重新 miss");
906 }
907
908 #[test]
909 fn test_stats_hit_rate() {
910 let cache = PlanCache::new(100, None);
911 let sql = "SELECT * FROM users";
912
913 cache.get_or_parse(sql).expect("parse");
914 cache.get_or_parse(sql).expect("parse");
915 cache.get_or_parse(sql).expect("parse");
916
917 let stats = cache.stats();
918 assert_eq!(stats.parse_hits, 2);
919 assert_eq!(stats.parse_misses, 1);
920 assert!((stats.parse_hit_rate - (2.0 / 3.0)).abs() < 0.001);
921 }
922
923 #[test]
924 fn test_stats_hit_rate_empty() {
925 let cache = PlanCache::new(100, None);
926 let stats = cache.stats();
927 assert_eq!(stats.parse_hit_rate, 0.0, "空缓存命中率应为 0.0");
928 assert_eq!(stats.optimize_hit_rate, 0.0);
929 }
930
931 #[test]
932 fn test_ttl_expiration() {
933 let cache = PlanCache::new(100, Some(Duration::from_nanos(1)));
934 let sql = "SELECT * FROM users";
935
936 cache.get_or_parse(sql).expect("parse");
937 std::thread::sleep(Duration::from_millis(10));
938
939 cache.get_or_parse(sql).expect("parse");
940 assert!(cache.stats().parse_misses >= 2, "TTL 过期后应重新 miss");
941 }
942
943 #[test]
944 fn test_plan_cache_concurrent_same_sql() {
945 use std::sync::Arc;
946 use std::thread;
947
948 let cache = Arc::new(PlanCache::new(100, None));
949 let sql = "SELECT * FROM users WHERE id = ?";
950 let mut handles = Vec::new();
951
952 for _ in 0..10 {
953 let cache = cache.clone();
954 handles.push(thread::spawn(move || {
955 cache.get_or_parse(sql).expect("parse");
956 }));
957 }
958
959 for h in handles {
960 h.join().expect("thread");
961 }
962
963 assert!(cache.size() >= 1, "并发后应至少有 1 条缓存");
964 assert!(
965 cache.stats().parse_misses + cache.stats().parse_hits >= 10,
966 "应有 10 次访问记录"
967 );
968 }
969}