Skip to main content

sz_rust_core/
schema_cache.rs

1//! Schema 缓存模块 — 数据表字段元数据缓存(对齐 PHP `think\db\Fetch::getFields`)
2//!
3//! 本模块提供数据表字段元数据(schema)的缓存能力,对齐 PHP ThinkPHP
4//! `think\db\Fetch` 的字段缓存机制。在 PHP 端,每次查询表字段信息都需要
5//! 执行 `SHOW COLUMNS FROM <table>`(MySQL)或等价 SQL,为避免重复查询,
6//! ThinkPHP 将字段元数据缓存到 Cache 中(默认永不过期)。
7//!
8//! ## PHP 对齐
9//!
10//! ### 核心 API 映射
11//!
12//! | PHP 方法 | Rust 方法 | 说明 |
13//! |---------|-----------|------|
14//! | `Fetch::getFields($table)` | [`SchemaCache::get_schema`] | 从缓存读取字段元数据 |
15//! | `Fetch::setFieldCache($table, $data)` | [`SchemaCache::set_schema`] | 写入字段缓存 |
16//! | `Fetch::getFieldCacheKey($table)` | [`SchemaCache::cache_key`] | 构造缓存 key |
17//! | `Cache::delete($key)` | [`SchemaCache::forget_schema`] | 清除单表字段缓存 |
18//! | `Cache::clear()` | [`SchemaCache::clear`] | 清除所有字段缓存 |
19//! | `Cache::has($key)` | [`SchemaCache::has_schema`] | 判断字段缓存是否存在 |
20//! | `Fetch::getFields` 内部回源 | [`SchemaCache::remember_schema`] | 缓存未命中时回源加载 |
21//!
22//! ### PHP `getFields` 缓存行为
23//!
24//! PHP `think\db\Fetch::getFields` 核心逻辑:
25//!
26//! ```php
27//! protected function getFields(string $tableName): array
28//! {
29//!     // 1. 从缓存读取
30//!     if ($this->config['fields_cache']) {
31//!         $guid = $tableName . $this->connection->getConfig('fields_cache_flag');
32//!         $content = $this->connection->getCacheHandler()->get($guid);
33//!         if ($content) {
34//!             return $content;  // 缓存命中
35//!         }
36//!     }
37//!
38//!     // 2. 缓存未命中,查询数据库
39//!     $fields = $this->connection->getFields($tableName);
40//!
41//!     // 3. 写入缓存(永不过期)
42//!     if ($this->config['fields_cache']) {
43//!         $guid = $tableName . $this->connection->getConfig('fields_cache_flag');
44//!         $this->connection->getCacheHandler()->set($guid, $fields);
45//!     }
46//!
47//!     return $fields;
48//! }
49//! ```
50//!
51//! **关键行为对齐**:
52//! - 默认 TTL = None(永不过期),对齐 PHP `$expire = null`
53//! - 缓存 key 格式:`schema_cache:<table_name>`(PHP 端为 `db_<flag>_<table_name>`)
54//! - 缓存未命中时通过 loader 回源加载(对齐 PHP `getFields` 内部查询)
55//!
56//! ### PHP 字段元数据结构
57//!
58//! PHP `getFields` 返回的字段元数据结构:
59//!
60//! ```php
61//! [
62//!     'id' => [
63//!         'name'      => 'id',
64//!         'type'      => 'int(11) unsigned',
65//!         'notnull'   => true,
66//!         'default'   => null,
67//!         'primary'   => true,
68//!         'autoinc'   => true,
69//!     ],
70//!     'name' => [
71//!         'name'      => 'name',
72//!         'type'      => 'varchar(255)',
73//!         'notnull'   => false,
74//!         'default'   => null,
75//!         'primary'   => false,
76//!         'autoinc'   => false,
77//!     ],
78//! ]
79//! ```
80//!
81//! Rust 端通过 [`ColumnDefinition`] 提供等价结构,并扩展 `unsigned`、`comment` 字段。
82//!
83//! ## 架构说明
84//!
85//! - **基于 `Cache` facade**:复用 [`crate::cache::Cache`] 的驱动管理、序列化、标签系统,
86//!   不重新实现底层存储
87//! - **标签批量清除**:所有字段缓存 key 注册到标签(tag),[`SchemaCache::clear`]
88//!   通过标签一次性清除所有表字段缓存,对齐 PHP `Cache::clear()` 的批量语义
89//! - **无锁设计**:`SchemaCache` 自身状态在构造后不可变(`key_prefix`、`tag_name`),
90//!   所有并发安全由底层 `Cache` 保证(`RwLock` + `parking_lot`)
91//! - **可配置前缀**:通过 [`SchemaCache::with_prefix`] 可自定义缓存 key 前缀,
92//!   支持多实例隔离(如不同数据库连接使用不同前缀)
93//!
94//! ## 使用示例
95//!
96//! ```ignore
97//! use sz_rust_core::cache::{Cache, MemoryCacheDriver};
98//! use sz_rust_core::schema_cache::{SchemaCache, TableSchema, ColumnDefinition};
99//! use std::sync::Arc;
100//!
101//! // 创建 Cache facade
102//! let cache = Arc::new(Cache::new());
103//! cache.register_default(MemoryCacheDriver::new());
104//!
105//! // 创建 SchemaCache
106//! let schema_cache = SchemaCache::new(cache.clone());
107//!
108//! // 构造表字段元数据
109//! let schema = TableSchema::new("users", vec![
110//!     ColumnDefinition::new("id", "int(11) unsigned")
111//!         .nullable(false)
112//!         .primary_key(true)
113//!         .auto_increment(true),
114//!     ColumnDefinition::new("name", "varchar(255)")
115//!         .nullable(false),
116//! ]);
117//!
118//! // 写入缓存(永不过期)
119//! schema_cache.set_schema("users", &schema, None).unwrap();
120//!
121//! // 从缓存读取
122//! let cached = schema_cache.get_schema("users").unwrap().unwrap();
123//! assert_eq!(cached.columns.len(), 2);
124//!
125//! // remember_schema:缓存未命中时自动加载
126//! let schema = schema_cache.remember_schema("orders", |_table| {
127//!     Ok(TableSchema::new("orders", vec![
128//!         ColumnDefinition::new("id", "bigint(20)")
129//!             .primary_key(true)
130//!             .auto_increment(true),
131//!     ]))
132//! }).unwrap();
133//! ```
134
135use std::sync::Arc;
136use std::time::Duration;
137
138use thiserror::Error;
139
140use crate::cache::Cache;
141
142// ============================================================================
143// 常量
144// ============================================================================
145
146/// 默认缓存 key 前缀(对齐 PHP `fields_cache_flag` 默认值)
147const DEFAULT_KEY_PREFIX: &str = "schema_cache";
148
149// ============================================================================
150// 错误类型
151// ============================================================================
152
153/// Schema 缓存错误
154///
155/// 对齐 PHP `think\db\Fetch` 字段缓存操作中可能产生的错误。
156#[derive(Debug, Error)]
157pub enum SchemaCacheError {
158    /// 底层缓存操作失败(读/写/删除/清除等)
159    #[error("缓存操作失败: {0}")]
160    CacheError(#[from] sz_orm_core::CacheError),
161    /// Schema 加载器执行失败(`remember_schema` 中 loader 返回的错误)
162    #[error("Schema 加载失败: {0}")]
163    LoaderError(String),
164    /// 序列化/反序列化失败(TableSchema 与缓存字节之间的转换错误)
165    #[error("Schema 序列化失败: {0}")]
166    Serialize(String),
167}
168
169// ============================================================================
170// ColumnDefinition — 单个字段定义
171// ============================================================================
172
173/// 单个字段定义(对齐 PHP `think\db\Fetch::getFields` 返回的单字段元数据)
174///
175/// 存储数据表单个字段的完整元数据,包括字段名、类型、约束、默认值等。
176///
177/// # PHP 对齐
178///
179/// PHP `getFields` 返回字段数组,每个元素包含:
180/// - `name`:字段名
181/// - `type`:字段类型(如 `int(11) unsigned`)
182/// - `notnull`:是否 NOT NULL(注意 PHP 是"是否非空",Rust 用 `nullable` 取反)
183/// - `default`:默认值
184/// - `primary`:是否主键
185/// - `autoinc`:是否自增
186///
187/// Rust 端额外扩展 `unsigned`(无符号)和 `comment`(字段注释)字段。
188#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
189pub struct ColumnDefinition {
190    /// 字段名
191    pub name: String,
192    /// 字段数据类型(如 `int(11)`, `varchar(255)`, `text`, `decimal(10,2)`)
193    pub data_type: String,
194    /// 是否允许 NULL(对齐 PHP `notnull` 取反:`nullable = !notnull`)
195    pub nullable: bool,
196    /// 是否为主键(对齐 PHP `primary`)
197    pub primary_key: bool,
198    /// 是否自增(对齐 PHP `autoinc`)
199    pub auto_increment: bool,
200    /// 是否无符号(数值类型扩展字段,PHP 端从 `type` 字符串解析)
201    pub unsigned: bool,
202    /// 默认值(`None` 表示无 DEFAULT 子句,`Some(value)` 表示有默认值)
203    pub default: Option<String>,
204    /// 字段注释(从 `COMMENT` 子句获取,PHP 端通常不缓存)
205    pub comment: Option<String>,
206}
207
208impl ColumnDefinition {
209    /// 创建新的字段定义
210    ///
211    /// 默认值:可空、非主键、非自增、非无符号、无默认值、无注释。
212    ///
213    /// # 参数
214    ///
215    /// - `name`: 字段名
216    /// - `data_type`: 字段数据类型
217    pub fn new(name: impl Into<String>, data_type: impl Into<String>) -> Self {
218        Self {
219            name: name.into(),
220            data_type: data_type.into(),
221            nullable: true,
222            primary_key: false,
223            auto_increment: false,
224            unsigned: false,
225            default: None,
226            comment: None,
227        }
228    }
229
230    /// 设置是否允许 NULL(Builder 模式)
231    pub fn nullable(mut self, nullable: bool) -> Self {
232        self.nullable = nullable;
233        self
234    }
235
236    /// 设置是否为主键(Builder 模式)
237    pub fn primary_key(mut self, primary_key: bool) -> Self {
238        self.primary_key = primary_key;
239        self
240    }
241
242    /// 设置是否自增(Builder 模式)
243    pub fn auto_increment(mut self, auto_increment: bool) -> Self {
244        self.auto_increment = auto_increment;
245        self
246    }
247
248    /// 设置是否无符号(Builder 模式)
249    pub fn unsigned(mut self, unsigned: bool) -> Self {
250        self.unsigned = unsigned;
251        self
252    }
253
254    /// 设置默认值(Builder 模式)
255    pub fn default_value(mut self, default: impl Into<String>) -> Self {
256        self.default = Some(default.into());
257        self
258    }
259
260    /// 设置字段注释(Builder 模式)
261    pub fn comment(mut self, comment: impl Into<String>) -> Self {
262        self.comment = Some(comment.into());
263        self
264    }
265}
266
267// ============================================================================
268// TableSchema — 数据表字段元数据
269// ============================================================================
270
271/// 数据表字段元数据(对齐 PHP `think\db\Fetch::getFields` 返回的完整字段列表)
272///
273/// 存储一张表所有字段的元数据,以及主键信息和缓存时间戳。
274#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
275pub struct TableSchema {
276    /// 表名
277    pub table_name: String,
278    /// 字段列表(按表定义顺序)
279    pub columns: Vec<ColumnDefinition>,
280    /// 主键字段名列表(从 `columns` 中 `primary_key == true` 的字段自动提取)
281    pub primary_keys: Vec<String>,
282    /// 缓存写入时间戳(Unix 秒,用于判断缓存新鲜度)
283    pub cached_at: i64,
284}
285
286impl TableSchema {
287    /// 创建新的表字段元数据
288    ///
289    /// 自动从 `columns` 中提取主键字段名列表。
290    ///
291    /// # 参数
292    ///
293    /// - `table_name`: 表名
294    /// - `columns`: 字段定义列表
295    pub fn new(table_name: impl Into<String>, columns: Vec<ColumnDefinition>) -> Self {
296        let table_name = table_name.into();
297        let primary_keys: Vec<String> = columns
298            .iter()
299            .filter(|col| col.primary_key)
300            .map(|col| col.name.clone())
301            .collect();
302        Self {
303            table_name,
304            columns,
305            primary_keys,
306            cached_at: chrono::Utc::now().timestamp(),
307        }
308    }
309
310    /// 按字段名查找字段定义
311    ///
312    /// # 参数
313    ///
314    /// - `name`: 字段名
315    ///
316    /// # 返回
317    ///
318    /// 找到返回 `Some(&ColumnDefinition)`,未找到返回 `None`
319    pub fn column(&self, name: &str) -> Option<&ColumnDefinition> {
320        self.columns.iter().find(|col| col.name == name)
321    }
322
323    /// 判断是否存在指定字段
324    ///
325    /// # 参数
326    ///
327    /// - `name`: 字段名
328    pub fn has_column(&self, name: &str) -> bool {
329        self.column(name).is_some()
330    }
331
332    /// 获取所有字段名列表
333    pub fn column_names(&self) -> Vec<&str> {
334        self.columns.iter().map(|col| col.name.as_str()).collect()
335    }
336}
337
338// ============================================================================
339// SchemaCache — 数据表字段缓存
340// ============================================================================
341
342/// 数据表字段缓存(对齐 PHP `think\db\Fetch` 字段缓存机制)
343///
344/// 基于 [`Cache`] facade 实现表字段元数据的缓存,支持:
345/// - 按 key 读写单表字段缓存
346/// - `remember_schema` 缓存未命中时自动回源加载
347/// - 按表名清除单表缓存
348/// - 按标签批量清除所有表字段缓存
349///
350/// # 线程安全
351///
352/// `SchemaCache` 自身状态在构造后不可变(`key_prefix`、`tag_name`),
353/// 所有并发安全由底层 [`Cache`] 保证。`SchemaCache` 是 `Send + Sync`。
354///
355/// # 缓存 key 格式
356///
357/// 默认格式:`schema_cache:<table_name>`
358///
359/// 可通过 [`SchemaCache::with_prefix`] 自定义前缀。
360///
361/// # 标签批量清除
362///
363/// 所有通过 [`SchemaCache::set_schema`] 写入的缓存 key 自动注册到标签
364/// (标签名 = key 前缀),[`SchemaCache::clear`] 通过标签一次性清除所有
365/// 表字段缓存,不影响 Cache 中的其他缓存。
366pub struct SchemaCache {
367    /// 底层 Cache facade 实例
368    cache: Arc<Cache>,
369    /// 缓存 key 前缀(默认 `schema_cache`)
370    key_prefix: String,
371    /// 标签名(用于批量清除,默认等于 `key_prefix`)
372    tag_name: String,
373}
374
375impl SchemaCache {
376    /// 创建 SchemaCache(使用默认前缀 `schema_cache`)
377    ///
378    /// # 参数
379    ///
380    /// - `cache`: Cache facade 实例(`Arc<Cache>`)
381    pub fn new(cache: Arc<Cache>) -> Self {
382        Self {
383            cache,
384            key_prefix: DEFAULT_KEY_PREFIX.to_string(),
385            tag_name: DEFAULT_KEY_PREFIX.to_string(),
386        }
387    }
388
389    /// 创建 SchemaCache(使用自定义缓存 key 前缀)
390    ///
391    /// 用于多实例隔离场景,如不同数据库连接使用不同前缀:
392    /// `schema_cache_db1:users`、`schema_cache_db2:users`。
393    ///
394    /// # 参数
395    ///
396    /// - `cache`: Cache facade 实例
397    /// - `prefix`: 缓存 key 前缀(同时用作标签名)
398    pub fn with_prefix(cache: Arc<Cache>, prefix: impl Into<String>) -> Self {
399        let prefix = prefix.into();
400        Self {
401            cache,
402            tag_name: prefix.clone(),
403            key_prefix: prefix,
404        }
405    }
406
407    /// 构造完整缓存 key(`{prefix}:{table_name}`)
408    ///
409    /// 对齐 PHP `Fetch::getFieldCacheKey($tableName)`。
410    ///
411    /// # 参数
412    ///
413    /// - `table_name`: 表名
414    pub fn cache_key(&self, table_name: &str) -> String {
415        format!("{}:{}", self.key_prefix, table_name)
416    }
417
418    /// 从缓存获取表字段元数据(对齐 PHP `Fetch::getFields` 缓存读取)
419    ///
420    /// # 参数
421    ///
422    /// - `table_name`: 表名
423    ///
424    /// # 返回
425    ///
426    /// - `Ok(Some(schema))`: 缓存命中
427    /// - `Ok(None)`: 缓存未命中或已过期
428    pub fn get_schema(&self, table_name: &str) -> Result<Option<TableSchema>, SchemaCacheError> {
429        let key = self.cache_key(table_name);
430        let result = self.cache.get::<TableSchema>(&key)?;
431        Ok(result)
432    }
433
434    /// 缓存未命中时调用 loader 加载并缓存(对齐 PHP `Fetch::getFields` 回源逻辑)
435    ///
436    /// 1. 先从缓存读取,命中则直接返回
437    /// 2. 未命中时调用 `loader(table_name)` 加载字段元数据
438    /// 3. 将 loader 返回的 schema 写入缓存(TTL = None,永不过期)
439    /// 4. 返回 schema
440    ///
441    /// # 参数
442    ///
443    /// - `table_name`: 表名
444    /// - `loader`: 回源加载闭包,接收表名,返回 `Result<TableSchema, SchemaCacheError>`
445    pub fn remember_schema<F>(
446        &self,
447        table_name: &str,
448        loader: F,
449    ) -> Result<TableSchema, SchemaCacheError>
450    where
451        F: FnOnce(&str) -> Result<TableSchema, SchemaCacheError>,
452    {
453        // 1. 先从缓存读取(对齐 PHP: $content = $this->connection->getCacheHandler()->get($guid))
454        if let Some(schema) = self.get_schema(table_name)? {
455            return Ok(schema);
456        }
457        // 2. 缓存未命中,调用 loader 加载(对齐 PHP: $fields = $this->connection->getFields($tableName))
458        let schema = loader(table_name)?;
459        // 3. 写入缓存(默认永不过期,对齐 PHP: $this->connection->getCacheHandler()->set($guid, $fields))
460        self.set_schema(table_name, &schema, None)?;
461        // 4. 返回 schema
462        Ok(schema)
463    }
464
465    /// 写入表字段缓存(对齐 PHP `Fetch::setFieldCache`)
466    ///
467    /// 缓存 key 自动注册到标签,支持 [`SchemaCache::clear`] 批量清除。
468    ///
469    /// # 参数
470    ///
471    /// - `table_name`: 表名
472    /// - `schema`: 表字段元数据
473    /// - `ttl`: 过期时间(`None` 永不过期,对齐 PHP 默认行为)
474    pub fn set_schema(
475        &self,
476        table_name: &str,
477        schema: &TableSchema,
478        ttl: Option<Duration>,
479    ) -> Result<(), SchemaCacheError> {
480        let key = self.cache_key(table_name);
481        // 使用 tag().set() 写入缓存,同时将 key 注册到标签(对齐 PHP TagSet::set)
482        self.cache.tag(&self.tag_name).set(&key, schema, ttl)?;
483        Ok(())
484    }
485
486    /// 清除单表字段缓存(对齐 PHP `Cache::delete($key)`)
487    ///
488    /// # 参数
489    ///
490    /// - `table_name`: 表名
491    pub fn forget_schema(&self, table_name: &str) -> Result<(), SchemaCacheError> {
492        let key = self.cache_key(table_name);
493        self.cache.delete(&key)?;
494        Ok(())
495    }
496
497    /// 清除所有表字段缓存(对齐 PHP `Cache::clear()` 批量语义)
498    ///
499    /// 通过标签批量清除所有 `set_schema` 写入的缓存 key,不影响 Cache 中的
500    /// 其他缓存(如业务缓存、Session 等)。
501    pub fn clear(&self) -> Result<(), SchemaCacheError> {
502        self.cache.tag(&self.tag_name).clear()?;
503        Ok(())
504    }
505
506    /// 判断表字段缓存是否存在(对齐 PHP `Cache::has($key)`)
507    ///
508    /// # 参数
509    ///
510    /// - `table_name`: 表名
511    pub fn has_schema(&self, table_name: &str) -> Result<bool, SchemaCacheError> {
512        let key = self.cache_key(table_name);
513        Ok(self.cache.has(&key)?)
514    }
515}
516
517// ============================================================================
518// 单元测试
519// ============================================================================
520
521#[cfg(test)]
522mod tests {
523    use super::*;
524    use crate::cache::{Cache, MemoryCacheDriver};
525    use std::sync::atomic::{AtomicBool, Ordering};
526
527    /// 创建带默认驱动的测试用 Cache(Arc 包装)
528    fn make_cache() -> Arc<Cache> {
529        let cache = Arc::new(Cache::new());
530        cache.register_default(MemoryCacheDriver::new());
531        cache
532    }
533
534    /// 创建测试用 ColumnDefinition 列表(模拟 users 表)
535    fn make_columns() -> Vec<ColumnDefinition> {
536        vec![
537            ColumnDefinition::new("id", "int(11) unsigned")
538                .nullable(false)
539                .primary_key(true)
540                .auto_increment(true)
541                .unsigned(true),
542            ColumnDefinition::new("name", "varchar(255)")
543                .nullable(false)
544                .default_value(""),
545            ColumnDefinition::new("email", "varchar(255)")
546                .nullable(true)
547                .comment("用户邮箱"),
548        ]
549    }
550
551    // ------------------------------------------------------------------------
552    // ColumnDefinition / TableSchema 结构测试
553    // ------------------------------------------------------------------------
554
555    /// 测试 ColumnDefinition builder 模式
556    #[test]
557    fn test_column_definition_builder() {
558        let col = ColumnDefinition::new("id", "int(11) unsigned")
559            .nullable(false)
560            .primary_key(true)
561            .auto_increment(true)
562            .unsigned(true)
563            .default_value("0")
564            .comment("主键");
565
566        assert_eq!(col.name, "id");
567        assert_eq!(col.data_type, "int(11) unsigned");
568        assert!(!col.nullable);
569        assert!(col.primary_key);
570        assert!(col.auto_increment);
571        assert!(col.unsigned);
572        assert_eq!(col.default, Some("0".to_string()));
573        assert_eq!(col.comment, Some("主键".to_string()));
574    }
575
576    /// 测试 ColumnDefinition 默认值
577    #[test]
578    fn test_column_definition_defaults() {
579        let col = ColumnDefinition::new("name", "varchar(255)");
580        assert_eq!(col.name, "name");
581        assert!(col.nullable);
582        assert!(!col.primary_key);
583        assert!(!col.auto_increment);
584        assert!(!col.unsigned);
585        assert_eq!(col.default, None);
586        assert_eq!(col.comment, None);
587    }
588
589    /// 测试 TableSchema 自动提取主键
590    #[test]
591    fn test_table_schema_primary_keys() {
592        let schema = TableSchema::new("users", make_columns());
593        assert_eq!(schema.table_name, "users");
594        assert_eq!(schema.columns.len(), 3);
595        assert_eq!(schema.primary_keys, vec!["id"]);
596    }
597
598    /// 测试 TableSchema 查找字段
599    #[test]
600    fn test_table_schema_column_lookup() {
601        let schema = TableSchema::new("users", make_columns());
602
603        assert!(schema.has_column("id"));
604        assert!(schema.has_column("name"));
605        assert!(schema.has_column("email"));
606        assert!(!schema.has_column("nonexistent"));
607
608        let col = schema.column("email").unwrap();
609        assert_eq!(col.data_type, "varchar(255)");
610        assert_eq!(col.comment, Some("用户邮箱".to_string()));
611    }
612
613    /// 测试 TableSchema::column_names
614    #[test]
615    fn test_table_schema_column_names() {
616        let schema = TableSchema::new("users", make_columns());
617        let names = schema.column_names();
618        assert_eq!(names, vec!["id", "name", "email"]);
619    }
620
621    /// 测试 TableSchema 序列化/反序列化
622    #[test]
623    fn test_table_schema_serde() {
624        let schema = TableSchema::new("users", make_columns());
625        let json = serde_json::to_string(&schema).unwrap();
626        let deserialized: TableSchema = serde_json::from_str(&json).unwrap();
627        assert_eq!(schema, deserialized);
628    }
629
630    // ------------------------------------------------------------------------
631    // SchemaCache: set/get 基本流程
632    // ------------------------------------------------------------------------
633
634    /// 测试 set/get schema 基本流程
635    #[test]
636    fn test_set_get_schema() {
637        let cache = make_cache();
638        let schema_cache = SchemaCache::new(cache);
639
640        let schema = TableSchema::new("users", make_columns());
641        schema_cache.set_schema("users", &schema, None).unwrap();
642
643        let result = schema_cache.get_schema("users").unwrap();
644        assert!(result.is_some());
645        let cached = result.unwrap();
646        assert_eq!(cached.table_name, "users");
647        assert_eq!(cached.columns.len(), 3);
648        assert_eq!(cached.primary_keys, vec!["id"]);
649        assert_eq!(cached.columns[0].name, "id");
650        assert!(cached.columns[0].primary_key);
651        assert!(cached.columns[0].auto_increment);
652        assert!(!cached.columns[0].nullable);
653    }
654
655    /// 测试 get_schema 缓存未命中
656    #[test]
657    fn test_get_schema_miss() {
658        let cache = make_cache();
659        let schema_cache = SchemaCache::new(cache);
660
661        let result = schema_cache.get_schema("nonexistent").unwrap();
662        assert!(result.is_none());
663    }
664
665    // ------------------------------------------------------------------------
666    // SchemaCache: remember_schema
667    // ------------------------------------------------------------------------
668
669    /// 测试 remember_schema 缓存命中(不调用 loader)
670    #[test]
671    fn test_remember_schema_cache_hit() {
672        let cache = make_cache();
673        let schema_cache = SchemaCache::new(cache);
674
675        // 预先写入缓存
676        let schema = TableSchema::new("users", make_columns());
677        schema_cache.set_schema("users", &schema, None).unwrap();
678
679        // remember_schema 应命中缓存,不调用 loader
680        let loader_called = Arc::new(AtomicBool::new(false));
681        let loader_called_clone = loader_called.clone();
682
683        let result = schema_cache
684            .remember_schema("users", |_| {
685                loader_called_clone.store(true, Ordering::SeqCst);
686                Ok(TableSchema::new("users", vec![]))
687            })
688            .unwrap();
689
690        assert!(
691            !loader_called.load(Ordering::SeqCst),
692            "loader should not be called on cache hit"
693        );
694        // 返回缓存中的 schema(3 个字段),不是 loader 的空 schema
695        assert_eq!(result.columns.len(), 3);
696    }
697
698    /// 测试 remember_schema 缓存未命中(调用 loader 并缓存)
699    #[test]
700    fn test_remember_schema_cache_miss() {
701        let cache = make_cache();
702        let schema_cache = SchemaCache::new(cache.clone());
703
704        // 缓存未命中,调用 loader
705        let result = schema_cache
706            .remember_schema("orders", |table| {
707                assert_eq!(table, "orders");
708                Ok(TableSchema::new(
709                    "orders",
710                    vec![ColumnDefinition::new("id", "bigint(20)")
711                        .primary_key(true)
712                        .auto_increment(true)],
713                ))
714            })
715            .unwrap();
716
717        assert_eq!(result.table_name, "orders");
718        assert_eq!(result.columns.len(), 1);
719
720        // 验证已写入缓存
721        let cached = schema_cache.get_schema("orders").unwrap().unwrap();
722        assert_eq!(cached.columns.len(), 1);
723        assert_eq!(cached.columns[0].name, "id");
724    }
725
726    /// 测试 remember_schema loader 错误传播
727    #[test]
728    fn test_remember_schema_loader_error() {
729        let cache = make_cache();
730        let schema_cache = SchemaCache::new(cache);
731
732        let result = schema_cache.remember_schema("broken", |_| {
733            Err(SchemaCacheError::LoaderError("数据库连接失败".to_string()))
734        });
735
736        assert!(result.is_err());
737        match result.unwrap_err() {
738            SchemaCacheError::LoaderError(msg) => assert_eq!(msg, "数据库连接失败"),
739            other => panic!("期望 LoaderError,实际: {:?}", other),
740        }
741
742        // loader 失败时不应写入缓存
743        assert!(schema_cache.get_schema("broken").unwrap().is_none());
744    }
745
746    // ------------------------------------------------------------------------
747    // SchemaCache: forget_schema
748    // ------------------------------------------------------------------------
749
750    /// 测试 forget_schema 清除单表
751    #[test]
752    fn test_forget_schema() {
753        let cache = make_cache();
754        let schema_cache = SchemaCache::new(cache);
755
756        let schema = TableSchema::new("users", make_columns());
757        schema_cache.set_schema("users", &schema, None).unwrap();
758        assert!(schema_cache.has_schema("users").unwrap());
759
760        schema_cache.forget_schema("users").unwrap();
761        assert!(!schema_cache.has_schema("users").unwrap());
762        assert!(schema_cache.get_schema("users").unwrap().is_none());
763    }
764
765    // ------------------------------------------------------------------------
766    // SchemaCache: clear
767    // ------------------------------------------------------------------------
768
769    /// 测试 clear 清除所有
770    #[test]
771    fn test_clear() {
772        let cache = make_cache();
773        let schema_cache = SchemaCache::new(cache);
774
775        // 写入多张表
776        schema_cache
777            .set_schema("users", &TableSchema::new("users", make_columns()), None)
778            .unwrap();
779        schema_cache
780            .set_schema("orders", &TableSchema::new("orders", make_columns()), None)
781            .unwrap();
782        schema_cache
783            .set_schema(
784                "products",
785                &TableSchema::new("products", make_columns()),
786                None,
787            )
788            .unwrap();
789
790        assert!(schema_cache.has_schema("users").unwrap());
791        assert!(schema_cache.has_schema("orders").unwrap());
792        assert!(schema_cache.has_schema("products").unwrap());
793
794        // 清除所有
795        schema_cache.clear().unwrap();
796
797        assert!(!schema_cache.has_schema("users").unwrap());
798        assert!(!schema_cache.has_schema("orders").unwrap());
799        assert!(!schema_cache.has_schema("products").unwrap());
800    }
801
802    /// 测试 clear 不影响其他缓存(非 schema_cache 标签的缓存)
803    #[test]
804    fn test_clear_preserves_other_caches() {
805        let cache = make_cache();
806        let schema_cache = SchemaCache::new(cache.clone());
807
808        // 写入 schema 缓存
809        schema_cache
810            .set_schema("users", &TableSchema::new("users", make_columns()), None)
811            .unwrap();
812
813        // 写入业务缓存(非 schema_cache 标签)
814        cache.set("business:config", "value", None).unwrap();
815
816        // 清除 schema 缓存
817        schema_cache.clear().unwrap();
818
819        // schema 缓存被清除
820        assert!(!schema_cache.has_schema("users").unwrap());
821        // 业务缓存不受影响
822        assert!(cache.has("business:config").unwrap());
823    }
824
825    // ------------------------------------------------------------------------
826    // SchemaCache: has_schema
827    // ------------------------------------------------------------------------
828
829    /// 测试 has_schema
830    #[test]
831    fn test_has_schema() {
832        let cache = make_cache();
833        let schema_cache = SchemaCache::new(cache);
834
835        assert!(!schema_cache.has_schema("users").unwrap());
836
837        let schema = TableSchema::new("users", make_columns());
838        schema_cache.set_schema("users", &schema, None).unwrap();
839
840        assert!(schema_cache.has_schema("users").unwrap());
841        assert!(!schema_cache.has_schema("orders").unwrap());
842    }
843
844    // ------------------------------------------------------------------------
845    // SchemaCache: 不同表名不冲突
846    // ------------------------------------------------------------------------
847
848    /// 测试不同表名不冲突
849    #[test]
850    fn test_different_tables_no_conflict() {
851        let cache = make_cache();
852        let schema_cache = SchemaCache::new(cache);
853
854        let users_schema = TableSchema::new(
855            "users",
856            vec![
857                ColumnDefinition::new("id", "int(11)").primary_key(true),
858                ColumnDefinition::new("name", "varchar(255)"),
859            ],
860        );
861        let orders_schema = TableSchema::new(
862            "orders",
863            vec![
864                ColumnDefinition::new("order_id", "bigint(20)").primary_key(true),
865                ColumnDefinition::new("amount", "decimal(10,2)"),
866            ],
867        );
868
869        schema_cache
870            .set_schema("users", &users_schema, None)
871            .unwrap();
872        schema_cache
873            .set_schema("orders", &orders_schema, None)
874            .unwrap();
875
876        let users = schema_cache.get_schema("users").unwrap().unwrap();
877        let orders = schema_cache.get_schema("orders").unwrap().unwrap();
878
879        assert_eq!(users.table_name, "users");
880        assert_eq!(users.columns[0].name, "id");
881        assert_eq!(orders.table_name, "orders");
882        assert_eq!(orders.columns[0].name, "order_id");
883
884        // 清除单表不影响另一表
885        schema_cache.forget_schema("users").unwrap();
886        assert!(schema_cache.get_schema("users").unwrap().is_none());
887        assert!(schema_cache.get_schema("orders").unwrap().is_some());
888    }
889
890    // ------------------------------------------------------------------------
891    // SchemaCache: TTL 过期
892    // ------------------------------------------------------------------------
893
894    /// 测试 TTL 过期(使用较短 TTL)
895    #[test]
896    fn test_ttl_expiry() {
897        let cache = make_cache();
898        let schema_cache = SchemaCache::new(cache);
899
900        let schema = TableSchema::new("temp_table", make_columns());
901        // 设置 50ms TTL
902        schema_cache
903            .set_schema("temp_table", &schema, Some(Duration::from_millis(50)))
904            .unwrap();
905
906        // 立即读取应命中
907        assert!(schema_cache.has_schema("temp_table").unwrap());
908        assert!(schema_cache.get_schema("temp_table").unwrap().is_some());
909
910        // 等待过期
911        std::thread::sleep(Duration::from_millis(100));
912
913        // 过期后应未命中
914        assert!(!schema_cache.has_schema("temp_table").unwrap());
915        assert!(schema_cache.get_schema("temp_table").unwrap().is_none());
916    }
917
918    /// 测试默认 TTL 为 None(永不过期)
919    #[test]
920    fn test_default_ttl_no_expiry() {
921        let cache = make_cache();
922        let schema_cache = SchemaCache::new(cache);
923
924        let schema = TableSchema::new("permanent", make_columns());
925        schema_cache.set_schema("permanent", &schema, None).unwrap();
926
927        // 等待一小段时间
928        std::thread::sleep(Duration::from_millis(50));
929
930        // 仍然存在
931        assert!(schema_cache.has_schema("permanent").unwrap());
932        assert!(schema_cache.get_schema("permanent").unwrap().is_some());
933    }
934
935    // ------------------------------------------------------------------------
936    // SchemaCache: 自定义 cache key 前缀
937    // ------------------------------------------------------------------------
938
939    /// 测试自定义 cache key 前缀
940    #[test]
941    fn test_custom_prefix() {
942        let cache = make_cache();
943        let schema_cache = SchemaCache::with_prefix(cache.clone(), "my_schema");
944
945        let schema = TableSchema::new("users", make_columns());
946        schema_cache.set_schema("users", &schema, None).unwrap();
947
948        // 验证使用了自定义前缀
949        let key = schema_cache.cache_key("users");
950        assert_eq!(key, "my_schema:users");
951
952        // 通过底层 cache 验证 key 存在
953        assert!(cache.has("my_schema:users").unwrap());
954        // 默认前缀的 key 不应存在
955        assert!(!cache.has("schema_cache:users").unwrap());
956
957        // clear 应清除自定义前缀的缓存
958        schema_cache.clear().unwrap();
959        assert!(!cache.has("my_schema:users").unwrap());
960    }
961
962    /// 测试不同前缀的 SchemaCache 实例互不干扰
963    #[test]
964    fn test_multiple_prefixes_no_conflict() {
965        let cache = make_cache();
966        let schema_cache_1 = SchemaCache::new(cache.clone());
967        let schema_cache_2 = SchemaCache::with_prefix(cache.clone(), "db2_schema");
968
969        let schema = TableSchema::new("users", make_columns());
970
971        // 两个实例缓存同名的表
972        schema_cache_1.set_schema("users", &schema, None).unwrap();
973        schema_cache_2.set_schema("users", &schema, None).unwrap();
974
975        // 两个实例都能读取
976        assert!(schema_cache_1.has_schema("users").unwrap());
977        assert!(schema_cache_2.has_schema("users").unwrap());
978
979        // 底层 cache 中有两个不同的 key
980        assert!(cache.has("schema_cache:users").unwrap());
981        assert!(cache.has("db2_schema:users").unwrap());
982
983        // 清除实例 1 不影响实例 2
984        schema_cache_1.clear().unwrap();
985        assert!(!schema_cache_1.has_schema("users").unwrap());
986        assert!(schema_cache_2.has_schema("users").unwrap());
987    }
988
989    // ------------------------------------------------------------------------
990    // SchemaCache: cache_key
991    // ------------------------------------------------------------------------
992
993    /// 测试 cache_key 格式
994    #[test]
995    fn test_cache_key_format() {
996        let cache = make_cache();
997
998        // 默认前缀
999        let sc1 = SchemaCache::new(cache.clone());
1000        assert_eq!(sc1.cache_key("users"), "schema_cache:users");
1001        assert_eq!(sc1.cache_key("orders"), "schema_cache:orders");
1002
1003        // 自定义前缀
1004        let sc2 = SchemaCache::with_prefix(cache, "custom_prefix");
1005        assert_eq!(sc2.cache_key("users"), "custom_prefix:users");
1006    }
1007}