Skip to main content

sz_orm_sqlx/
enhanced.rs

1//! 增强功能模块:事务隔离级别、连接池配置增强、预备语句缓存
2//!
3//! 本模块为 sz-orm-sqlx 提供三项深度增强能力:
4//!
5//! 1. **事务隔离级别**:支持四种标准隔离级别的设置与查询
6//! 2. **连接池配置增强**:提供更丰富的连接池配置选项与构建器模式
7//! 3. **预备语句缓存**:LRU 策略的预备语句缓存,减少 SQL 解析开销
8
9use parking_lot::Mutex;
10use std::collections::HashMap;
11use std::sync::atomic::{AtomicU64, Ordering};
12use std::time::Duration;
13
14use crate::any_driver::AnyBackend;
15
16// ============================================================================
17// 事务隔离级别
18// ============================================================================
19
20/// SQL 事务隔离级别。
21///
22/// 对应 SQL 标准的四种隔离级别,不同后端的 SQL 语法略有差异。
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
24pub enum TransactionIsolation {
25    /// 读未提交(最低隔离级别,允许脏读)
26    ReadUncommitted,
27    /// 读已提交(禁止脏读,允许不可重复读)
28    #[default]
29    ReadCommitted,
30    /// 可重复读(禁止不可重复读,允许幻读)
31    RepeatableRead,
32    /// 串行化(最高隔离级别,完全隔离)
33    Serializable,
34}
35
36impl TransactionIsolation {
37    /// 返回隔离级别的标准名称。
38    pub fn name(&self) -> &'static str {
39        match self {
40            TransactionIsolation::ReadUncommitted => "READ UNCOMMITTED",
41            TransactionIsolation::ReadCommitted => "READ COMMITTED",
42            TransactionIsolation::RepeatableRead => "REPEATABLE READ",
43            TransactionIsolation::Serializable => "SERIALIZABLE",
44        }
45    }
46
47    /// 返回隔离级别的中文描述。
48    pub fn description(&self) -> &'static str {
49        match self {
50            TransactionIsolation::ReadUncommitted => "读未提交",
51            TransactionIsolation::ReadCommitted => "读已提交",
52            TransactionIsolation::RepeatableRead => "可重复读",
53            TransactionIsolation::Serializable => "串行化",
54        }
55    }
56
57    /// 返回隔离级别的严格程度排序值(0=最低,3=最高)。
58    pub fn strictness(&self) -> u8 {
59        match self {
60            TransactionIsolation::ReadUncommitted => 0,
61            TransactionIsolation::ReadCommitted => 1,
62            TransactionIsolation::RepeatableRead => 2,
63            TransactionIsolation::Serializable => 3,
64        }
65    }
66
67    /// 生成设置当前会话隔离级别的 SQL 语句。
68    ///
69    /// 不同后端语法略有差异:
70    /// - MySQL: `SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED`
71    /// - PostgreSQL: `SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED`
72    /// - SQLite: 不支持(SQLite 始终使用 SERIALIZABLE),返回空字符串
73    pub fn set_session_sql(&self, backend: AnyBackend) -> String {
74        match backend {
75            AnyBackend::MySql => {
76                format!("SET SESSION TRANSACTION ISOLATION LEVEL {}", self.name())
77            }
78            AnyBackend::Postgres => {
79                format!(
80                    "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL {}",
81                    self.name()
82                )
83            }
84            AnyBackend::Sqlite => {
85                // SQLite 隐式使用 SERIALIZABLE,不支持设置
86                String::new()
87            }
88        }
89    }
90
91    /// 生成设置下一事务隔离级别的 SQL 语句。
92    ///
93    /// - MySQL: `SET TRANSACTION ISOLATION LEVEL READ COMMITTED`
94    /// - PostgreSQL: `SET TRANSACTION ISOLATION LEVEL READ COMMITTED`
95    /// - SQLite: 不支持,返回空字符串
96    pub fn set_transaction_sql(&self, backend: AnyBackend) -> String {
97        match backend {
98            AnyBackend::MySql => format!("SET TRANSACTION ISOLATION LEVEL {}", self.name()),
99            AnyBackend::Postgres => format!("SET TRANSACTION ISOLATION LEVEL {}", self.name()),
100            AnyBackend::Sqlite => String::new(),
101        }
102    }
103
104    /// 生成查询当前隔离级别的 SQL 语句。
105    ///
106    /// - MySQL: `SELECT @@transaction_isolation`
107    /// - PostgreSQL: `SHOW transaction_isolation`
108    /// - SQLite: 不支持,返回空字符串
109    pub fn query_sql(&self, backend: AnyBackend) -> String {
110        match backend {
111            AnyBackend::MySql => "SELECT @@transaction_isolation".to_string(),
112            AnyBackend::Postgres => "SHOW transaction_isolation".to_string(),
113            AnyBackend::Sqlite => String::new(),
114        }
115    }
116
117    /// 从字符串解析隔离级别(不区分大小写)。
118    #[allow(clippy::should_implement_trait)]
119    pub fn from_str(s: &str) -> Option<Self> {
120        let upper = s.to_uppercase().replace('_', " ");
121        match upper.as_str() {
122            "READ UNCOMMITTED" => Some(TransactionIsolation::ReadUncommitted),
123            "READ COMMITTED" => Some(TransactionIsolation::ReadCommitted),
124            "REPEATABLE READ" => Some(TransactionIsolation::RepeatableRead),
125            "SERIALIZABLE" => Some(TransactionIsolation::Serializable),
126            _ => None,
127        }
128    }
129}
130
131impl std::fmt::Display for TransactionIsolation {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        write!(f, "{}", self.name())
134    }
135}
136
137// ============================================================================
138// 连接池配置增强
139// ============================================================================
140
141/// 增强的连接池配置。
142///
143/// 提供 比 sqlx::PoolOptions 更丰富的配置项,包括健康检查、测试查询等。
144#[derive(Debug, Clone)]
145pub struct EnhancedPoolConfig {
146    /// 最大连接数(默认 10)
147    pub max_connections: u32,
148    /// 最小空闲连接数(默认 0)
149    pub min_idle: Option<u32>,
150    /// 获取连接超时时间(默认 30 秒)
151    pub acquire_timeout: Duration,
152    /// 空闲连接超时时间(默认 600 秒)
153    pub idle_timeout: Option<Duration>,
154    /// 连接最大生存时间(默认 1800 秒)
155    pub max_lifetime: Option<Duration>,
156    /// 获取连接时是否执行测试查询(默认 false)
157    pub test_on_acquire: bool,
158    /// 测试查询 SQL(默认 "SELECT 1")
159    pub test_query: String,
160    /// 连接池名称(用于日志和监控)
161    pub pool_name: Option<String>,
162}
163
164impl Default for EnhancedPoolConfig {
165    fn default() -> Self {
166        Self {
167            max_connections: 10,
168            min_idle: None,
169            acquire_timeout: Duration::from_secs(30),
170            idle_timeout: Some(Duration::from_secs(600)),
171            max_lifetime: Some(Duration::from_secs(1800)),
172            test_on_acquire: false,
173            test_query: "SELECT 1".to_string(),
174            pool_name: None,
175        }
176    }
177}
178
179impl EnhancedPoolConfig {
180    /// 创建新的配置构建器。
181    pub fn builder() -> EnhancedPoolConfigBuilder {
182        EnhancedPoolConfigBuilder::default()
183    }
184
185    /// 校验配置合法性。
186    pub fn validate(&self) -> Result<(), String> {
187        if self.max_connections == 0 {
188            return Err("max_connections 不能为 0".to_string());
189        }
190        if let Some(min) = self.min_idle {
191            if min > self.max_connections {
192                return Err(format!(
193                    "min_idle ({}) 不能大于 max_connections ({})",
194                    min, self.max_connections
195                ));
196            }
197        }
198        if self.acquire_timeout.is_zero() {
199            return Err("acquire_timeout 不能为 0".to_string());
200        }
201        if self.test_query.is_empty() {
202            return Err("test_query 不能为空".to_string());
203        }
204        Ok(())
205    }
206
207    /// 返回配置摘要信息。
208    pub fn summary(&self) -> String {
209        format!(
210            "PoolConfig{{max={}, min_idle={:?}, timeout={}ms, test_on_acquire={}, name={:?}}}",
211            self.max_connections,
212            self.min_idle,
213            self.acquire_timeout.as_millis(),
214            self.test_on_acquire,
215            self.pool_name
216        )
217    }
218}
219
220/// 增强连接池配置构建器。
221#[derive(Debug, Clone, Default)]
222pub struct EnhancedPoolConfigBuilder {
223    config: EnhancedPoolConfig,
224}
225
226impl EnhancedPoolConfigBuilder {
227    /// 设置最大连接数。
228    pub fn max_connections(mut self, n: u32) -> Self {
229        self.config.max_connections = n;
230        self
231    }
232
233    /// 设置最小空闲连接数。
234    pub fn min_idle(mut self, n: u32) -> Self {
235        self.config.min_idle = Some(n);
236        self
237    }
238
239    /// 设置获取连接超时时间(秒)。
240    pub fn acquire_timeout_secs(mut self, secs: u64) -> Self {
241        self.config.acquire_timeout = Duration::from_secs(secs);
242        self
243    }
244
245    /// 设置获取连接超时时间(毫秒)。
246    pub fn acquire_timeout_millis(mut self, millis: u64) -> Self {
247        self.config.acquire_timeout = Duration::from_millis(millis);
248        self
249    }
250
251    /// 设置空闲连接超时时间(秒)。
252    pub fn idle_timeout_secs(mut self, secs: u64) -> Self {
253        self.config.idle_timeout = Some(Duration::from_secs(secs));
254        self
255    }
256
257    /// 设置连接最大生存时间(秒)。
258    pub fn max_lifetime_secs(mut self, secs: u64) -> Self {
259        self.config.max_lifetime = Some(Duration::from_secs(secs));
260        self
261    }
262
263    /// 启用获取连接时的测试查询。
264    pub fn test_on_acquire(mut self) -> Self {
265        self.config.test_on_acquire = true;
266        self
267    }
268
269    /// 设置测试查询 SQL。
270    pub fn test_query(mut self, sql: &str) -> Self {
271        self.config.test_query = sql.to_string();
272        self
273    }
274
275    /// 设置连接池名称。
276    pub fn name(mut self, name: &str) -> Self {
277        self.config.pool_name = Some(name.to_string());
278        self
279    }
280
281    /// 构建配置,执行校验。
282    pub fn build(self) -> Result<EnhancedPoolConfig, String> {
283        self.config.validate()?;
284        Ok(self.config)
285    }
286}
287
288// ============================================================================
289// 预备语句缓存
290// ============================================================================
291
292/// 预备语句缓存条目。
293#[derive(Debug, Clone)]
294#[allow(dead_code)]
295struct CacheEntry {
296    /// 预备语句 ID 或名称
297    statement_id: String,
298    /// 创建时的访问序号
299    created_seq: u64,
300    /// 最后访问序号(用于 LRU 排序,单调递增)
301    last_access_seq: u64,
302    /// 命中次数
303    hit_count: u64,
304}
305
306/// 预备语句缓存统计信息。
307#[derive(Debug, Clone, Default)]
308pub struct CacheStats {
309    /// 缓存命中次数
310    pub hits: u64,
311    /// 缓存未命中次数
312    pub misses: u64,
313    /// 缓存驱逐次数
314    pub evictions: u64,
315    /// 当前缓存条目数
316    pub size: usize,
317    /// 最大缓存容量
318    pub capacity: usize,
319}
320
321impl CacheStats {
322    /// 计算缓存命中率(0.0 ~ 1.0)。
323    pub fn hit_rate(&self) -> f64 {
324        let total = self.hits + self.misses;
325        if total == 0 {
326            return 0.0;
327        }
328        self.hits as f64 / total as f64
329    }
330
331    /// 返回统计信息摘要字符串。
332    pub fn summary(&self) -> String {
333        format!(
334            "CacheStats{{hits={}, misses={}, evictions={}, size={}, capacity={}, hit_rate={:.2}%, capacity_utilization={:.2}%}}",
335            self.hits,
336            self.misses,
337            self.evictions,
338            self.size,
339            self.capacity,
340            self.hit_rate() * 100.0,
341            self.capacity_utilization() * 100.0,
342        )
343    }
344
345    /// 计算容量利用率(0.0 ~ 1.0)。
346    pub fn capacity_utilization(&self) -> f64 {
347        if self.capacity == 0 {
348            return 0.0;
349        }
350        self.size as f64 / self.capacity as f64
351    }
352
353    /// 计算总访问次数。
354    pub fn total_accesses(&self) -> u64 {
355        self.hits + self.misses
356    }
357}
358
359/// 预备语句缓存(LRU 策略)。
360///
361/// 缓存 SQL 语句到预备语句 ID 的映射,避免重复解析和编译 SQL。
362/// 使用 LRU(Least Recently Used)策略在容量满时驱逐最久未使用的条目。
363///
364/// # 线程安全
365///
366/// 内部使用 `Mutex` 保护,可安全跨线程共享。
367/// LRU 排序基于单调递增的原子计数器,不受系统时钟精度影响。
368pub struct PreparedStatementCache {
369    /// 缓存映射:SQL 哈希 → 缓存条目
370    entries: Mutex<HashMap<u64, CacheEntry>>,
371    /// 最大缓存容量
372    capacity: usize,
373    /// 统计信息
374    stats: Mutex<CacheStats>,
375    /// 单调递增的访问序号(用于 LRU 排序,避免时钟精度问题)
376    access_seq: AtomicU64,
377}
378
379impl PreparedStatementCache {
380    /// 创建新的预备语句缓存。
381    ///
382    /// # 参数
383    ///
384    /// - `capacity`: 最大缓存条目数(建议 100-1000)
385    pub fn new(capacity: usize) -> Self {
386        let capacity = capacity.max(1);
387        Self {
388            entries: Mutex::new(HashMap::with_capacity(capacity)),
389            capacity,
390            stats: Mutex::new(CacheStats {
391                hits: 0,
392                misses: 0,
393                evictions: 0,
394                size: 0,
395                capacity,
396            }),
397            access_seq: AtomicU64::new(0),
398        }
399    }
400
401    /// 计算 SQL 语句的哈希值(使用 FNV-1a 算法,无需额外依赖)。
402    fn hash_sql(sql: &str) -> u64 {
403        // FNV-1a 64-bit
404        const FNV_OFFSET: u64 = 0xcbf29ce484222325;
405        const FNV_PRIME: u64 = 0x100000001b3;
406        let mut hash = FNV_OFFSET;
407        for byte in sql.as_bytes() {
408            hash ^= *byte as u64;
409            hash = hash.wrapping_mul(FNV_PRIME);
410        }
411        hash
412    }
413
414    /// 获取下一个单调递增的访问序号。
415    fn next_seq(&self) -> u64 {
416        self.access_seq.fetch_add(1, Ordering::Relaxed)
417    }
418
419    /// 查询缓存中是否存在指定 SQL 的预备语句。
420    ///
421    /// 如果命中,更新最后访问序号并增加命中计数。
422    pub fn get(&self, sql: &str) -> Option<String> {
423        let hash = Self::hash_sql(sql);
424        let seq = self.next_seq();
425
426        let mut entries = self.entries.lock();
427        let mut stats = self.stats.lock();
428
429        if let Some(entry) = entries.get_mut(&hash) {
430            entry.last_access_seq = seq;
431            entry.hit_count += 1;
432            stats.hits += 1;
433            Some(entry.statement_id.clone())
434        } else {
435            stats.misses += 1;
436            None
437        }
438    }
439
440    /// 向缓存中插入预备语句。
441    ///
442    /// 如果缓存已满,驱逐最久未使用的条目(LRU)。
443    pub fn put(&self, sql: &str, statement_id: &str) {
444        let hash = Self::hash_sql(sql);
445        let seq = self.next_seq();
446
447        let mut entries = self.entries.lock();
448        let mut stats = self.stats.lock();
449
450        // 如果已存在,更新
451        if let Some(entry) = entries.get_mut(&hash) {
452            entry.statement_id = statement_id.to_string();
453            entry.last_access_seq = seq;
454            return;
455        }
456
457        // 检查是否需要 LRU 驱逐
458        if entries.len() >= self.capacity {
459            // 找到 last_access_seq 最小的条目(最久未使用)
460            if let Some(&evict_hash) = entries
461                .iter()
462                .min_by_key(|(_, entry)| entry.last_access_seq)
463                .map(|(k, _)| k)
464            {
465                entries.remove(&evict_hash);
466                stats.evictions += 1;
467            }
468        }
469
470        entries.insert(
471            hash,
472            CacheEntry {
473                statement_id: statement_id.to_string(),
474                created_seq: seq,
475                last_access_seq: seq,
476                hit_count: 0,
477            },
478        );
479        stats.size = entries.len();
480    }
481
482    /// 从缓存中移除指定 SQL 的预备语句。
483    pub fn remove(&self, sql: &str) -> bool {
484        let hash = Self::hash_sql(sql);
485        let mut entries = self.entries.lock();
486        let mut stats = self.stats.lock();
487        let removed = entries.remove(&hash).is_some();
488        if removed {
489            stats.size = entries.len();
490        }
491        removed
492    }
493
494    /// 清空缓存。
495    pub fn clear(&self) {
496        let mut entries = self.entries.lock();
497        let mut stats = self.stats.lock();
498        entries.clear();
499        stats.size = 0;
500    }
501
502    /// 获取缓存统计信息。
503    pub fn stats(&self) -> CacheStats {
504        let stats = self.stats.lock();
505        stats.clone()
506    }
507
508    /// 获取缓存容量。
509    pub fn capacity(&self) -> usize {
510        self.capacity
511    }
512
513    /// 获取当前缓存条目数。
514    pub fn len(&self) -> usize {
515        self.entries.lock().len()
516    }
517
518    /// 缓存是否为空。
519    pub fn is_empty(&self) -> bool {
520        self.len() == 0
521    }
522
523    /// 重置统计信息(不清空缓存条目)。
524    pub fn reset_stats(&self) {
525        let mut stats = self.stats.lock();
526        stats.hits = 0;
527        stats.misses = 0;
528        stats.evictions = 0;
529    }
530}
531
532impl Default for PreparedStatementCache {
533    fn default() -> Self {
534        Self::new(256)
535    }
536}
537
538impl std::fmt::Debug for PreparedStatementCache {
539    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
540        let stats = self.stats();
541        f.debug_struct("PreparedStatementCache")
542            .field("capacity", &self.capacity)
543            .field("size", &stats.size)
544            .field("hits", &stats.hits)
545            .field("misses", &stats.misses)
546            .field("evictions", &stats.evictions)
547            .finish()
548    }
549}
550
551// ============================================================================
552// 测试模块
553// ============================================================================
554
555#[cfg(test)]
556mod tests {
557    use super::*;
558
559    // ---- TransactionIsolation 测试 ----
560
561    #[test]
562    fn test_isolation_level_names() {
563        assert_eq!(
564            TransactionIsolation::ReadUncommitted.name(),
565            "READ UNCOMMITTED"
566        );
567        assert_eq!(TransactionIsolation::ReadCommitted.name(), "READ COMMITTED");
568        assert_eq!(
569            TransactionIsolation::RepeatableRead.name(),
570            "REPEATABLE READ"
571        );
572        assert_eq!(TransactionIsolation::Serializable.name(), "SERIALIZABLE");
573    }
574
575    #[test]
576    fn test_isolation_level_descriptions() {
577        assert_eq!(
578            TransactionIsolation::ReadUncommitted.description(),
579            "读未提交"
580        );
581        assert_eq!(
582            TransactionIsolation::ReadCommitted.description(),
583            "读已提交"
584        );
585        assert_eq!(
586            TransactionIsolation::RepeatableRead.description(),
587            "可重复读"
588        );
589        assert_eq!(TransactionIsolation::Serializable.description(), "串行化");
590    }
591
592    #[test]
593    fn test_isolation_level_strictness_order() {
594        assert!(
595            TransactionIsolation::ReadUncommitted.strictness()
596                < TransactionIsolation::ReadCommitted.strictness()
597        );
598        assert!(
599            TransactionIsolation::ReadCommitted.strictness()
600                < TransactionIsolation::RepeatableRead.strictness()
601        );
602        assert!(
603            TransactionIsolation::RepeatableRead.strictness()
604                < TransactionIsolation::Serializable.strictness()
605        );
606    }
607
608    #[test]
609    fn test_isolation_level_from_str() {
610        assert_eq!(
611            TransactionIsolation::from_str("READ COMMITTED"),
612            Some(TransactionIsolation::ReadCommitted)
613        );
614        assert_eq!(
615            TransactionIsolation::from_str("read committed"),
616            Some(TransactionIsolation::ReadCommitted)
617        );
618        assert_eq!(
619            TransactionIsolation::from_str("READ_COMMITTED"),
620            Some(TransactionIsolation::ReadCommitted)
621        );
622        assert_eq!(
623            TransactionIsolation::from_str("SERIALIZABLE"),
624            Some(TransactionIsolation::Serializable)
625        );
626        assert_eq!(TransactionIsolation::from_str("UNKNOWN"), None);
627    }
628
629    #[test]
630    fn test_isolation_level_set_session_sql_mysql() {
631        let sql = TransactionIsolation::ReadCommitted.set_session_sql(AnyBackend::MySql);
632        assert!(sql.contains("SET SESSION TRANSACTION ISOLATION LEVEL"));
633        assert!(sql.contains("READ COMMITTED"));
634    }
635
636    #[test]
637    fn test_isolation_level_set_session_sql_postgres() {
638        let sql = TransactionIsolation::Serializable.set_session_sql(AnyBackend::Postgres);
639        assert!(sql.contains("SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL"));
640        assert!(sql.contains("SERIALIZABLE"));
641    }
642
643    #[test]
644    fn test_isolation_level_set_session_sql_sqlite_empty() {
645        let sql = TransactionIsolation::ReadCommitted.set_session_sql(AnyBackend::Sqlite);
646        assert!(sql.is_empty(), "SQLite 不支持设置隔离级别");
647    }
648
649    #[test]
650    fn test_isolation_level_set_transaction_sql() {
651        let mysql_sql = TransactionIsolation::RepeatableRead.set_transaction_sql(AnyBackend::MySql);
652        assert!(mysql_sql.contains("SET TRANSACTION ISOLATION LEVEL"));
653        assert!(mysql_sql.contains("REPEATABLE READ"));
654
655        let pg_sql = TransactionIsolation::RepeatableRead.set_transaction_sql(AnyBackend::Postgres);
656        assert!(pg_sql.contains("SET TRANSACTION ISOLATION LEVEL"));
657
658        let sqlite_sql =
659            TransactionIsolation::RepeatableRead.set_transaction_sql(AnyBackend::Sqlite);
660        assert!(sqlite_sql.is_empty());
661    }
662
663    #[test]
664    fn test_isolation_level_query_sql() {
665        assert_eq!(
666            TransactionIsolation::ReadCommitted.query_sql(AnyBackend::MySql),
667            "SELECT @@transaction_isolation"
668        );
669        assert_eq!(
670            TransactionIsolation::ReadCommitted.query_sql(AnyBackend::Postgres),
671            "SHOW transaction_isolation"
672        );
673        assert_eq!(
674            TransactionIsolation::ReadCommitted.query_sql(AnyBackend::Sqlite),
675            ""
676        );
677    }
678
679    #[test]
680    fn test_isolation_level_display() {
681        let level = TransactionIsolation::Serializable;
682        let s = format!("{}", level);
683        assert_eq!(s, "SERIALIZABLE");
684    }
685
686    #[test]
687    fn test_isolation_level_default() {
688        let level = TransactionIsolation::default();
689        assert_eq!(level, TransactionIsolation::ReadCommitted);
690    }
691
692    #[test]
693    fn test_isolation_level_equality() {
694        assert_eq!(
695            TransactionIsolation::ReadCommitted,
696            TransactionIsolation::ReadCommitted
697        );
698        assert_ne!(
699            TransactionIsolation::ReadCommitted,
700            TransactionIsolation::Serializable
701        );
702    }
703
704    // ---- EnhancedPoolConfig 测试 ----
705
706    #[test]
707    fn test_pool_config_default() {
708        let config = EnhancedPoolConfig::default();
709        assert_eq!(config.max_connections, 10);
710        assert!(config.min_idle.is_none());
711        assert_eq!(config.acquire_timeout, Duration::from_secs(30));
712        assert_eq!(config.test_query, "SELECT 1");
713        assert!(!config.test_on_acquire);
714    }
715
716    #[test]
717    fn test_pool_config_builder_basic() {
718        let config = EnhancedPoolConfig::builder()
719            .max_connections(20)
720            .min_idle(5)
721            .acquire_timeout_secs(60)
722            .build()
723            .unwrap();
724        assert_eq!(config.max_connections, 20);
725        assert_eq!(config.min_idle, Some(5));
726        assert_eq!(config.acquire_timeout, Duration::from_secs(60));
727    }
728
729    #[test]
730    fn test_pool_config_builder_test_on_acquire() {
731        let config = EnhancedPoolConfig::builder()
732            .test_on_acquire()
733            .test_query("SELECT 1 FROM dual")
734            .build()
735            .unwrap();
736        assert!(config.test_on_acquire);
737        assert_eq!(config.test_query, "SELECT 1 FROM dual");
738    }
739
740    #[test]
741    fn test_pool_config_builder_with_name() {
742        let config = EnhancedPoolConfig::builder()
743            .name("primary-pool")
744            .build()
745            .unwrap();
746        assert_eq!(config.pool_name, Some("primary-pool".to_string()));
747    }
748
749    #[test]
750    fn test_pool_config_validate_max_connections_zero() {
751        let config = EnhancedPoolConfig {
752            max_connections: 0,
753            ..Default::default()
754        };
755        assert!(config.validate().is_err());
756    }
757
758    #[test]
759    fn test_pool_config_validate_min_idle_exceeds_max() {
760        let config = EnhancedPoolConfig {
761            max_connections: 5,
762            min_idle: Some(10),
763            ..Default::default()
764        };
765        assert!(config.validate().is_err());
766    }
767
768    #[test]
769    fn test_pool_config_validate_timeout_zero() {
770        let config = EnhancedPoolConfig {
771            acquire_timeout: Duration::from_secs(0),
772            ..Default::default()
773        };
774        assert!(config.validate().is_err());
775    }
776
777    #[test]
778    fn test_pool_config_validate_empty_test_query() {
779        let config = EnhancedPoolConfig {
780            test_query: "".to_string(),
781            ..Default::default()
782        };
783        assert!(config.validate().is_err());
784    }
785
786    #[test]
787    fn test_pool_config_validate_valid() {
788        let config = EnhancedPoolConfig::default();
789        assert!(config.validate().is_ok());
790    }
791
792    #[test]
793    fn test_pool_config_summary() {
794        let config = EnhancedPoolConfig::builder()
795            .max_connections(15)
796            .name("test-pool")
797            .build()
798            .unwrap();
799        let summary = config.summary();
800        assert!(summary.contains("max=15"));
801        assert!(summary.contains("name=Some(\"test-pool\")"));
802    }
803
804    #[test]
805    fn test_pool_config_builder_millis_timeout() {
806        let config = EnhancedPoolConfig::builder()
807            .acquire_timeout_millis(500)
808            .build()
809            .unwrap();
810        assert_eq!(config.acquire_timeout, Duration::from_millis(500));
811    }
812
813    #[test]
814    fn test_pool_config_builder_idle_and_lifetime() {
815        let config = EnhancedPoolConfig::builder()
816            .idle_timeout_secs(300)
817            .max_lifetime_secs(900)
818            .build()
819            .unwrap();
820        assert_eq!(config.idle_timeout, Some(Duration::from_secs(300)));
821        assert_eq!(config.max_lifetime, Some(Duration::from_secs(900)));
822    }
823
824    // ---- PreparedStatementCache 测试 ----
825
826    #[test]
827    fn test_cache_basic_put_and_get() {
828        let cache = PreparedStatementCache::new(10);
829        cache.put("SELECT * FROM users WHERE id = ?", "stmt_1");
830
831        let result = cache.get("SELECT * FROM users WHERE id = ?");
832        assert_eq!(result, Some("stmt_1".to_string()));
833    }
834
835    #[test]
836    fn test_cache_miss() {
837        let cache = PreparedStatementCache::new(10);
838        let result = cache.get("SELECT * FROM nonexist");
839        assert!(result.is_none());
840
841        let stats = cache.stats();
842        assert_eq!(stats.misses, 1);
843        assert_eq!(stats.hits, 0);
844    }
845
846    #[test]
847    fn test_cache_hit_increments_counter() {
848        let cache = PreparedStatementCache::new(10);
849        cache.put("SELECT 1", "stmt_1");
850
851        cache.get("SELECT 1");
852        cache.get("SELECT 1");
853        cache.get("SELECT 1");
854
855        let stats = cache.stats();
856        assert_eq!(stats.hits, 3);
857    }
858
859    #[test]
860    fn test_cache_remove() {
861        let cache = PreparedStatementCache::new(10);
862        cache.put("SELECT 1", "stmt_1");
863        assert!(cache.remove("SELECT 1"));
864        assert!(cache.get("SELECT 1").is_none());
865    }
866
867    #[test]
868    fn test_cache_remove_nonexistent() {
869        let cache = PreparedStatementCache::new(10);
870        assert!(!cache.remove("SELECT 1"));
871    }
872
873    #[test]
874    fn test_cache_clear() {
875        let cache = PreparedStatementCache::new(10);
876        cache.put("SELECT 1", "stmt_1");
877        cache.put("SELECT 2", "stmt_2");
878        cache.clear();
879        assert_eq!(cache.len(), 0);
880        assert!(cache.is_empty());
881    }
882
883    #[test]
884    fn test_cache_lru_eviction() {
885        let cache = PreparedStatementCache::new(2);
886        cache.put("sql_1", "stmt_1");
887        cache.put("sql_2", "stmt_2");
888
889        // 访问 sql_1 使其成为最近使用
890        cache.get("sql_1");
891
892        // 插入 sql_3,应驱逐最久未使用的 sql_2
893        cache.put("sql_3", "stmt_3");
894
895        assert!(cache.get("sql_1").is_some(), "sql_1 应被保留(最近使用)");
896        assert!(cache.get("sql_2").is_none(), "sql_2 应被 LRU 驱逐");
897        assert!(cache.get("sql_3").is_some(), "sql_3 应存在");
898
899        let stats = cache.stats();
900        assert!(stats.evictions >= 1, "应至少有 1 次驱逐");
901    }
902
903    #[test]
904    fn test_cache_update_existing() {
905        let cache = PreparedStatementCache::new(10);
906        cache.put("SELECT 1", "stmt_1");
907        cache.put("SELECT 1", "stmt_2"); // 更新
908
909        let result = cache.get("SELECT 1");
910        assert_eq!(result, Some("stmt_2".to_string()));
911
912        // 更新不应增加条目数
913        assert_eq!(cache.len(), 1);
914    }
915
916    #[test]
917    fn test_cache_stats_hit_rate() {
918        let cache = PreparedStatementCache::new(10);
919        cache.put("SELECT 1", "stmt_1");
920
921        // 3 次命中
922        cache.get("SELECT 1");
923        cache.get("SELECT 1");
924        cache.get("SELECT 1");
925        // 2 次未命中
926        cache.get("SELECT 2");
927        cache.get("SELECT 3");
928
929        let stats = cache.stats();
930        assert_eq!(stats.hits, 3);
931        assert_eq!(stats.misses, 2);
932        assert_eq!(stats.total_accesses(), 5);
933        let expected_rate = 3.0 / 5.0;
934        assert!((stats.hit_rate() - expected_rate).abs() < 0.001);
935    }
936
937    #[test]
938    fn test_cache_stats_summary() {
939        let cache = PreparedStatementCache::new(100);
940        cache.put("SELECT 1", "stmt_1");
941        cache.get("SELECT 1");
942
943        let stats = cache.stats();
944        let summary = stats.summary();
945        assert!(summary.contains("hits=1"));
946        assert!(summary.contains("capacity=100"));
947        assert!(summary.contains("hit_rate="));
948    }
949
950    #[test]
951    fn test_cache_capacity_utilization() {
952        let cache = PreparedStatementCache::new(10);
953        cache.put("sql_1", "stmt_1");
954        cache.put("sql_2", "stmt_2");
955
956        let stats = cache.stats();
957        assert_eq!(stats.size, 2);
958        assert_eq!(stats.capacity, 10);
959        assert!((stats.capacity_utilization() - 0.2).abs() < 0.001);
960    }
961
962    #[test]
963    fn test_cache_reset_stats() {
964        let cache = PreparedStatementCache::new(10);
965        cache.put("SELECT 1", "stmt_1");
966        cache.get("SELECT 1");
967        cache.get("SELECT 2");
968
969        cache.reset_stats();
970        let stats = cache.stats();
971        assert_eq!(stats.hits, 0);
972        assert_eq!(stats.misses, 0);
973        assert_eq!(stats.evictions, 0);
974        // 条目不清空
975        assert_eq!(stats.size, 1);
976    }
977
978    #[test]
979    fn test_cache_default_capacity() {
980        let cache = PreparedStatementCache::default();
981        assert_eq!(cache.capacity(), 256);
982    }
983
984    #[test]
985    fn test_cache_min_capacity_1() {
986        let cache = PreparedStatementCache::new(0);
987        assert_eq!(cache.capacity(), 1, "容量为 0 时应自动设为 1");
988    }
989
990    #[test]
991    fn test_cache_debug_format() {
992        let cache = PreparedStatementCache::new(10);
993        cache.put("SELECT 1", "stmt_1");
994        let debug_str = format!("{:?}", cache);
995        assert!(debug_str.contains("PreparedStatementCache"));
996        // debug_struct 使用 `: ` 作为键值分隔符
997        assert!(debug_str.contains("capacity: 10"));
998        assert!(debug_str.contains("size: 1"));
999    }
1000
1001    #[test]
1002    fn test_cache_concurrent_access() {
1003        use std::sync::Arc;
1004        use std::thread;
1005
1006        let cache = Arc::new(PreparedStatementCache::new(100));
1007        let mut handles = Vec::new();
1008
1009        for i in 0..4 {
1010            let c = cache.clone();
1011            handles.push(thread::spawn(move || {
1012                for j in 0..10 {
1013                    let sql = format!("SELECT {}", i * 10 + j);
1014                    c.put(&sql, &format!("stmt_{}", i * 10 + j));
1015                    c.get(&sql);
1016                }
1017            }));
1018        }
1019
1020        for h in handles {
1021            h.join().unwrap();
1022        }
1023
1024        // 所有线程完成后,缓存应包含 40 个条目
1025        assert_eq!(cache.len(), 40);
1026        let stats = cache.stats();
1027        assert!(stats.hits >= 40, "每个 put 后立即 get 应产生 40 次命中");
1028    }
1029
1030    #[test]
1031    fn test_cache_same_sql_different_whitespace_same_hash() {
1032        // 注意:当前实现基于字节级哈希,不同空格的 SQL 会被视为不同条目
1033        // 此测试验证行为符合预期(字节级哈希)
1034        let cache = PreparedStatementCache::new(10);
1035        cache.put("SELECT 1", "stmt_1");
1036        cache.put("SELECT  1", "stmt_2"); // 两个空格
1037        assert_eq!(cache.len(), 2, "不同空格的 SQL 应为不同条目");
1038    }
1039}