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 std::hash::Hasher;
24use twox_hash::XxHash64;
25
26// ─── SqlNormalizer ───────────────────────────────────────────────
27
28/// SQL 归一化器
29///
30/// 将 SQL 文本归一化为标准形式:忽略大小写差异、空白差异、参数值差异。
31/// 相同语义不同写法的 SQL 归一化后产生相同文本,用于缓存键生成。
32pub struct SqlNormalizer;
33
34impl SqlNormalizer {
35    /// 归一化 SQL 文本
36    ///
37    /// 返回归一化后的 SQL 文本。参数值(如 WHERE id = 42 中的 42)
38    /// 不会被特殊处理——调用方应使用参数化查询(WHERE id = ?),
39    /// 归一化仅处理大小写和空白差异。
40    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    /// 从 SQL 中提取依赖的表名列表
59    ///
60    /// 遍历 AST 提取所有表引用(FROM / JOIN / INTO / UPDATE 等)。
61    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// ─── PlanCacheKey ────────────────────────────────────────────────
147
148/// 查询计划缓存键
149///
150/// 由归一化 SQL 的 xxHash 64bit 哈希值 + 归一化 SQL 文本组成。
151/// 哈希值用于快速查找,SQL 文本用于二次校验(防哈希碰撞)。
152#[derive(Debug, Clone)]
153pub struct PlanCacheKey {
154    /// xxHash 64bit 哈希值
155    pub hash: u64,
156    /// 归一化 SQL 文本(用于碰撞校验)
157    pub sql_normalized: String,
158}
159
160impl PlanCacheKey {
161    /// 从原始 SQL 生成缓存键
162    ///
163    /// 1. 归一化 SQL(忽略大小写/空白差异)
164    /// 2. 计算归一化 SQL 的 xxHash 64bit 哈希
165    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
191// ─── PlanCacheEntry ──────────────────────────────────────────────
192
193/// 查询计划缓存条目
194///
195/// 存储解析后的 AST 和/或优化后的查询分析结果。
196pub struct PlanCacheEntry {
197    /// 解析后的 AST(parse_cache 条目)
198    pub ast: Option<Arc<Statement>>,
199    /// 优化后的查询分析(optimize_cache 条目)
200    pub analysis: Option<Arc<String>>,
201    /// 创建时间
202    pub created_at: Instant,
203    /// 依赖的表名列表(用于表级失效)
204    pub tables: Vec<String>,
205    /// TTL(可选,过期自动失效)
206    pub ttl: Option<Duration>,
207}
208
209impl PlanCacheEntry {
210    /// 检查条目是否已过期
211    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
220// ─── PlanCacheStats ──────────────────────────────────────────────
221
222/// 查询计划缓存统计
223///
224/// 原子计数器无锁统计命中/未命中/淘汰次数。
225pub 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    /// 解析缓存命中次数
245    pub fn parse_hits(&self) -> u64 {
246        self.parse_hits.load(Ordering::Relaxed)
247    }
248
249    /// 解析缓存未命中次数
250    pub fn parse_misses(&self) -> u64 {
251        self.parse_misses.load(Ordering::Relaxed)
252    }
253
254    /// 优化缓存命中次数
255    pub fn optimize_hits(&self) -> u64 {
256        self.optimize_hits.load(Ordering::Relaxed)
257    }
258
259    /// 优化缓存未命中次数
260    pub fn optimize_misses(&self) -> u64 {
261        self.optimize_misses.load(Ordering::Relaxed)
262    }
263
264    /// LRU 淘汰次数
265    pub fn evictions(&self) -> u64 {
266        self.evictions.load(Ordering::Relaxed)
267    }
268
269    /// 解析缓存命中率(0.0 ~ 1.0)
270    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    /// 优化缓存命中率(0.0 ~ 1.0)
282    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// ─── PlanCacheStatsSnapshot ──────────────────────────────────────
301
302/// 统计快照(用于读取一致性视图)
303#[derive(Debug, Clone)]
304pub struct PlanCacheStatsSnapshot {
305    /// 解析缓存命中次数
306    pub parse_hits: u64,
307    /// 解析缓存未命中次数
308    pub parse_misses: u64,
309    /// 优化缓存命中次数
310    pub optimize_hits: u64,
311    /// 优化缓存未命中次数
312    pub optimize_misses: u64,
313    /// LRU 淘汰次数
314    pub evictions: u64,
315    /// 当前缓存条目数
316    pub size: usize,
317    /// 解析缓存命中率
318    pub parse_hit_rate: f64,
319    /// 优化缓存命中率
320    pub optimize_hit_rate: f64,
321}
322
323// ─── LruOrder64 ──────────────────────────────────────────────────
324
325/// u64 键的 LRU 双向链表(arena 实现,O(1) touch/remove/lru_key)
326struct 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
436// ─── PlanCache ───────────────────────────────────────────────────
437
438/// 查询计划缓存
439///
440/// 双缓存架构:
441/// - `parse_cache`:SQL → AST(解析结果缓存)
442/// - `optimize_cache`:SQL → 优化分析(优化结果缓存)
443///
444/// 锁顺序约定:parse_cache → optimize_cache → access_order → table_index → stats
445/// (按此顺序加锁,避免死锁)
446pub struct PlanCache {
447    /// 解析缓存(hash → entry)
448    parse_cache: RwLock<HashMap<u64, PlanCacheEntry>>,
449    /// 优化缓存(hash → entry)
450    optimize_cache: RwLock<HashMap<u64, PlanCacheEntry>>,
451    /// LRU 访问顺序(arena 双向链表)
452    access_order: RwLock<LruOrder64>,
453    /// 表级失效索引(table → `Vec<hash>`)
454    table_index: RwLock<HashMap<String, Vec<u64>>>,
455    /// 统计计数器
456    stats: PlanCacheStats,
457    /// 最大缓存条目数
458    max_size: usize,
459    /// 默认 TTL
460    default_ttl: Option<Duration>,
461}
462
463impl PlanCache {
464    /// 创建新的查询计划缓存
465    ///
466    /// - `max_size`:最大缓存条目数(LRU 淘汰)
467    /// - `default_ttl`:默认 TTL(None 表示永不过期)
468    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    /// 获取或解析 SQL
481    ///
482    /// 命中缓存时返回 AST + stats.parse_hits++ + LRU touch。
483    /// 未命中时解析 SQL + 存入缓存 + stats.parse_misses++。
484    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    /// 获取或优化 SQL
544    ///
545    /// 命中缓存时返回优化分析 + stats.optimize_hits++ + LRU touch。
546    /// 未命中时返回 None + stats.optimize_misses++(调用方应执行优化后调用 `store_optimize`)。
547    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    /// 存储优化结果
568    ///
569    /// 在 `get_or_optimize` 返回 None 后,调用方执行优化并存储结果。
570    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    /// 表级精确失效
604    ///
605    /// 失效所有引用指定表的缓存条目,返回失效条目数。
606    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    /// 全量清空缓存
636    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    /// 获取统计快照
644    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    /// 当前缓存大小
659    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// ─── 单元测试 ─────────────────────────────────────────────────────
677
678#[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}