Skip to main content

sz_orm_core/
plan_cache.rs

1//! v3.2.0 查询计划缓存
2//!
3//! 缓存 SQL 解析结果(AST)与查询优化结果,相同 SQL 模板第二次起跳过解析/优化(≤1μs)。
4//! 与 L2Cache(数据缓存)职责分离:L2Cache 缓存查询结果数据,PlanCache 缓存查询计划。
5//!
6//! 特性:
7//! - SQL 归一化(忽略空白/注释/参数顺序,参数值替换为占位符)
8//! - xxHash 64bit 键生成(无碰撞差分测试验证)
9//! - 双缓存(parse_cache + optimize_cache)
10//! - LRU 淘汰(arena 双向链表 O(1))
11//! - 表级精确失效(table_index 索引)
12//! - 命中率统计(原子计数器无锁)
13
14use 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
25// ─── SqlNormalizer ───────────────────────────────────────────────
26
27/// SQL 归一化器
28///
29/// 将 SQL 文本归一化为标准形式:忽略大小写差异、空白差异、参数值差异。
30/// 相同语义不同写法的 SQL 归一化后产生相同文本,用于缓存键生成。
31pub struct SqlNormalizer;
32
33impl SqlNormalizer {
34    /// 归一化 SQL 文本
35    ///
36    /// 返回归一化后的 SQL 文本。参数值(如 WHERE id = 42 中的 42)
37    /// 不会被特殊处理——调用方应使用参数化查询(WHERE id = ?),
38    /// 归一化仅处理大小写和空白差异。
39    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    /// 从 SQL 中提取依赖的表名列表
58    ///
59    /// 遍历 AST 提取所有表引用(FROM / JOIN / INTO / UPDATE 等)。
60    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// ─── PlanCacheKey ────────────────────────────────────────────────
146
147/// 查询计划缓存键
148///
149/// 由归一化 SQL 的 xxHash 64bit 哈希值 + 归一化 SQL 文本组成。
150/// 哈希值用于快速查找,SQL 文本用于二次校验(防哈希碰撞)。
151#[derive(Debug, Clone)]
152pub struct PlanCacheKey {
153    /// xxHash 64bit 哈希值
154    pub hash: u64,
155    /// 归一化 SQL 文本(用于碰撞校验)
156    pub sql_normalized: String,
157}
158
159impl PlanCacheKey {
160    /// 从原始 SQL 生成缓存键
161    ///
162    /// 1. 归一化 SQL(忽略大小写/空白差异)
163    /// 2. 计算归一化 SQL 的 xxHash 64bit 哈希
164    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
188// ─── PlanCacheEntry ──────────────────────────────────────────────
189
190/// 查询计划缓存条目
191///
192/// 存储解析后的 AST 和/或优化后的查询分析结果。
193pub struct PlanCacheEntry {
194    /// 解析后的 AST(parse_cache 条目)
195    pub ast: Option<Arc<Statement>>,
196    /// 优化后的查询分析(optimize_cache 条目)
197    pub analysis: Option<Arc<String>>,
198    /// 创建时间
199    pub created_at: Instant,
200    /// 依赖的表名列表(用于表级失效)
201    pub tables: Vec<String>,
202    /// TTL(可选,过期自动失效)
203    pub ttl: Option<Duration>,
204}
205
206impl PlanCacheEntry {
207    /// 检查条目是否已过期
208    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
217// ─── PlanCacheStats ──────────────────────────────────────────────
218
219/// 查询计划缓存统计
220///
221/// 原子计数器无锁统计命中/未命中/淘汰次数。
222pub 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    /// 解析缓存命中率(0.0 ~ 1.0)
262    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    /// 优化缓存命中率(0.0 ~ 1.0)
274    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// ─── PlanCacheStatsSnapshot ──────────────────────────────────────
293
294/// 统计快照(用于读取一致性视图)
295#[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
307// ─── LruOrder64 ──────────────────────────────────────────────────
308
309/// u64 键的 LRU 双向链表(arena 实现,O(1) touch/remove/lru_key)
310struct 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
420// ─── PlanCache ───────────────────────────────────────────────────
421
422/// 查询计划缓存
423///
424/// 双缓存架构:
425/// - `parse_cache`:SQL → AST(解析结果缓存)
426/// - `optimize_cache`:SQL → 优化分析(优化结果缓存)
427///
428/// 锁顺序约定:parse_cache → optimize_cache → access_order → table_index → stats
429/// (按此顺序加锁,避免死锁)
430pub struct PlanCache {
431    /// 解析缓存(hash → entry)
432    parse_cache: RwLock<HashMap<u64, PlanCacheEntry>>,
433    /// 优化缓存(hash → entry)
434    optimize_cache: RwLock<HashMap<u64, PlanCacheEntry>>,
435    /// LRU 访问顺序(arena 双向链表)
436    access_order: RwLock<LruOrder64>,
437    /// 表级失效索引(table → Vec<hash>)
438    table_index: RwLock<HashMap<String, Vec<u64>>>,
439    /// 统计计数器
440    stats: PlanCacheStats,
441    /// 最大缓存条目数
442    max_size: usize,
443    /// 默认 TTL
444    default_ttl: Option<Duration>,
445}
446
447impl PlanCache {
448    /// 创建新的查询计划缓存
449    ///
450    /// - `max_size`:最大缓存条目数(LRU 淘汰)
451    /// - `default_ttl`:默认 TTL(None 表示永不过期)
452    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    /// 获取或解析 SQL
465    ///
466    /// 命中缓存时返回 AST + stats.parse_hits++ + LRU touch。
467    /// 未命中时解析 SQL + 存入缓存 + stats.parse_misses++。
468    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    /// 获取或优化 SQL
528    ///
529    /// 命中缓存时返回优化分析 + stats.optimize_hits++ + LRU touch。
530    /// 未命中时返回 None + stats.optimize_misses++(调用方应执行优化后调用 `store_optimize`)。
531    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    /// 存储优化结果
552    ///
553    /// 在 `get_or_optimize` 返回 None 后,调用方执行优化并存储结果。
554    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    /// 表级精确失效
588    ///
589    /// 失效所有引用指定表的缓存条目,返回失效条目数。
590    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    /// 全量清空缓存
620    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    /// 获取统计快照
628    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    /// 当前缓存大小
643    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// ─── 单元测试 ─────────────────────────────────────────────────────
661
662#[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}