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}