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