Skip to main content

sz_rust_orm_ext_facade/relation/
cache.rs

1//! 关联缓存 — 对齐 PHP `withCache()` / `Cache::clear($tag)` 行为
2//!
3//! 本模块 re-export sz-orm-core `l2_cache` 模块的类型,
4//! 并提供 PHP 命名约定辅助函数对齐 PHP `withCache()` / `Cache::clear($tag)` 行为。
5//!
6//! ## PHP 端关联缓存机制
7//!
8//! PHP think-orm 2.0.x 通过 `withCache()` 方法为关联预载入启用缓存:
9//!
10//! ```php
11//! // 全部关联缓存
12//! User::with(['orders', 'profile'])->withCache(true)->select();
13//!
14//! // 指定关联缓存
15//! User::with(['orders', 'profile'])
16//!     ->withCache('orders', true, 3600, null)
17//!     ->select();
18//! ```
19//!
20//! ### PHP `withCache()` 源码(ModelRelationQuery.php 第 310-340 行)
21//!
22//! ```php
23//! public function withCache($relation = true, $key = true, $expire = null, string $tag = null)
24//! {
25//!     if (false === $relation || false === $key || !$this->getConnection()->getCache()) {
26//!         return $this;
27//!     }
28//!
29//!     if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) {
30//!         $expire = $key;
31//!         $key    = true;
32//!     }
33//!
34//!     if (true === $relation || is_numeric($relation)) {
35//!         $this->options['with_cache'] = $relation;  // 全部关联缓存
36//!         return $this;
37//!     }
38//!
39//!     $relations = (array) $relation;
40//!     foreach ($relations as $name => $relation) {
41//!         if (!is_numeric($name)) {
42//!             $this->options['with_cache'][$name] = is_array($relation) ? $relation : [$key, $relation, $tag];
43//!         } else {
44//!             $this->options['with_cache'][$relation] = [$key, $expire, $tag];
45//!         }
46//!     }
47//!
48//!     return $this;
49//! }
50//! ```
51//!
52//! ### PHP 关联缓存使用流程
53//!
54//! 1. `withCache()` 设置 `options['with_cache']`
55//! 2. `resultSetToModelCollection()` 第 487 行传递 `$with_cache` 到 `eagerlyResultSet()`
56//! 3. `HasMany::eagerlyOneToMany()` 第 215 行调用 `$this->query->cache($cache[0], $cache[1], $cache[2])`
57//! 4. `cache()` 方法(BaseQuery.php 第 775 行)设置 `options['cache'] = [$key, $expire, $tag ?: $this->getTable()]`
58//! 5. 查询执行时通过 `getCacheKey()` 生成缓存键,命中则直接返回,未命中则查询后写入
59//!
60//! ### PHP 缓存键生成(Connection.php 第 290-299 行)
61//!
62//! ```php
63//! protected function getCacheKey(BaseQuery $query, string $method = ''): string
64//! {
65//!     if (!empty($query->getOptions('key')) && empty($method)) {
66//!         $key = 'think_' . $this->getConfig('database') . '.' . $query->getTable() . '|' . $query->getOptions('key');
67//!     } else {
68//!         $key = $query->getQueryGuid();  // SQL + bind 的 hash
69//!     }
70//!     return $key;
71//! }
72//! ```
73//!
74//! ### PHP 缓存写入(Connection.php 第 274-281 行)
75//!
76//! ```php
77//! protected function cacheData(CacheItem $cacheItem)
78//! {
79//!     if ($cacheItem->getTag() && method_exists($this->cache, 'tag')) {
80//!         $this->cache->tag($cacheItem->getTag())->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire());
81//!     } else {
82//!         $this->cache->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire());
83//!     }
84//! }
85//! ```
86//!
87//! ### PHP 缓存失效机制
88//!
89//! - `Cache::delete($key)`:单键失效
90//! - `Cache::clear($tag)` 或 `Cache::tag($tag)->clear()`:tag 维度失效(默认 tag 为表名)
91//!
92//! ## sz-orm-core 缓存能力
93//!
94//! sz-orm-core 提供两级缓存:
95//!
96//! | 模块 | 类型 | 值类型 | 公开方式 |
97//! |------|------|-------|---------|
98//! | `cache`(私有) | `Cache` trait / `MemoryCache` / `MultiLevelCache` | `Vec<u8>` | `pub use cache::*;` |
99//! | `l2_cache`(公开) | `L2Cache` / `CacheKey` / `CacheKeyKind` / `L2CacheStats` | `Value` | `pub mod l2_cache;` |
100//!
101//! `L2Cache` 提供:
102//!
103//! - `put(&key, value, ttl)`:写入缓存
104//! - `get(&key)`:读取缓存
105//! - `invalidate(&key)`:单键失效(对齐 PHP `Cache::delete($key)`)
106//! - `invalidate_table(&table)`:表级失效(对齐 PHP `Cache::clear($tag)`,因为 PHP tag 默认为表名)
107//! - `stats()`:命中率统计
108//!
109//! ## PHP tag 与 sz-orm-core CacheKey.table 的映射
110//!
111//! PHP 中 `tag` 是独立于 `key` 的概念(通过 `CacheItem` 对象传递),默认值为表名:
112//!
113//! ```php
114//! $this->options['cache'] = [$key, $expire, $tag ?: $this->getTable()];
115//! ```
116//!
117//! sz-orm-core `CacheKey` 通过 `table` 字段实现表级索引,等价于 PHP tag。
118//! 因此本模块假设 **tag 默认等于表名**(PHP 默认行为)。如果使用自定义 tag,
119//! 调用方需通过 `CacheKey { table: <tag_value>, ... }` 自行管理映射关系。
120//!
121//! ## 本模块提供的函数
122//!
123//! ### 1. re-export sz-orm-core l2_cache 类型
124//!
125//! - [`L2Cache`]:跨 Session 共享的二级缓存
126//! - [`CacheKey`]:统一缓存键(table + kind + identifier)
127//! - [`CacheKeyKind`]:缓存键类型(ByPk / ByQuery / ByRelation)
128//! - [`L2CacheStats`]:命中率统计
129//!
130//! ### 2. PHP 命名约定辅助类型
131//!
132//! - [`WithCacheConfig`]:对齐 PHP `[$key, $expire, $tag]` 三元组
133//! - [`WithCacheOption`]:对齐 PHP `options['with_cache']` 的三种形态
134//!
135//! ### 3. PHP 命名约定辅助函数
136//!
137//! - [`php_with_cache_config`]:构造关联缓存配置(对齐 PHP `withCache()` 入口)
138//! - [`php_relation_cache_key`]:生成 PHP 关联缓存键(对齐 PHP `getCacheKey()`)
139//! - [`php_relation_cache_tag`]:生成 PHP 关联缓存 tag(对齐 `$tag ?: $this->getTable()`)
140//! - [`php_relation_cache_remember`]:缓存关联查询结果(对齐 PHP `cacheData()`)
141//! - [`php_relation_cache_fetch`]:读取关联缓存(对齐 `$this->cache->get()`)
142//! - [`php_relation_cache_invalidate`]:失效整表关联缓存(对齐 `Cache::clear($tag)`)
143//! - [`php_relation_cache_delete`]:失效单个关联缓存(对齐 `Cache::delete($key)`)
144//!
145//! ## 架构说明
146//!
147//! 沿用既有的 sz-orm-core::model 模块私有约束统一处理模式:
148//!
149//! - **re-export sz-orm-core l2_cache 类型**:`L2Cache` / `CacheKey` / `CacheKeyKind` / `L2CacheStats`
150//! - **PHP 命名约定辅助类型**:`WithCacheConfig` / `WithCacheOption`
151//! - **PHP 命名约定辅助函数**:`php_with_cache_config` / `php_relation_cache_key` /
152//!   `php_relation_cache_tag` / `php_relation_cache_remember` / `php_relation_cache_fetch` /
153//!   `php_relation_cache_invalidate` / `php_relation_cache_delete`
154//!
155//! 端到端关联缓存由 sz-orm-core `L2Cache` 内部实现,sz-rust 端通过辅助函数验证
156//! 缓存行为对齐 PHP。
157
158// re-export sz-orm-core l2_cache 类型
159pub use sz_rust_orm_facade::l2_cache::{CacheKey, CacheKeyKind, L2Cache, L2CacheStats};
160
161use std::collections::HashMap;
162use std::time::Duration;
163use sz_rust_orm_facade::Value;
164
165// ============================================================================
166// WithCacheConfig — PHP [$key, $expire, $tag] 三元组
167// ============================================================================
168
169/// PHP 关联缓存配置(对齐 `[$key, $expire, $tag]` 三元组)
170///
171/// 对齐 PHP `withCache($relation, $key, $expire, $tag)` 中每个关联的配置三元组:
172///
173/// ```php
174/// $this->options['with_cache'][$relation] = [$key, $expire, $tag];
175/// ```
176///
177/// ## 字段说明
178///
179/// - `key`:缓存键(`None` 对齐 PHP `$key = true` 自动生成;`Some(s)` 对齐自定义 key)
180/// - `expire`:过期时间(`None` 对齐 PHP `$expire = null` 永不过期)
181/// - `tag`:缓存标签(`None` 对齐 PHP `$tag = null`,使用默认表名)
182///
183/// ## 示例
184///
185/// ```ignore
186/// use sz_rust_core::relation::cache::{WithCacheConfig, php_with_cache_config};
187/// use std::time::Duration;
188///
189/// // 等价 PHP: withCache('orders', true, 3600, null)
190/// let config = php_with_cache_config(None, Some(Duration::from_secs(3600)), None);
191/// assert_eq!(config.key, None);
192/// assert_eq!(config.expire, Some(Duration::from_secs(3600)));
193/// assert_eq!(config.tag, None);
194/// ```
195#[derive(Debug, Clone, PartialEq, Eq, Default)]
196pub struct WithCacheConfig {
197    /// 缓存键(None = 自动生成,对齐 PHP `$key = true`)
198    pub key: Option<String>,
199    /// 过期时间(None = 永不过期,对齐 PHP `$expire = null`)
200    pub expire: Option<Duration>,
201    /// 缓存标签(None = 使用默认表名,对齐 PHP `$tag = null`)
202    pub tag: Option<String>,
203}
204
205impl WithCacheConfig {
206    /// 创建新的缓存配置
207    ///
208    /// ## 参数
209    ///
210    /// - `key`:缓存键(`None` = 自动生成)
211    /// - `expire`:过期时间(`None` = 永不过期)
212    /// - `tag`:缓存标签(`None` = 使用默认表名)
213    pub fn new(key: Option<String>, expire: Option<Duration>, tag: Option<String>) -> Self {
214        Self { key, expire, tag }
215    }
216
217    /// 是否使用自动生成的缓存键(对齐 PHP `$key = true`)
218    pub fn is_auto_key(&self) -> bool {
219        self.key.is_none()
220    }
221
222    /// 是否永不过期(对齐 PHP `$expire = null`)
223    pub fn is_permanent(&self) -> bool {
224        self.expire.is_none()
225    }
226
227    /// 是否使用默认 tag 即表名(对齐 PHP `$tag = null`)
228    pub fn is_default_tag(&self) -> bool {
229        self.tag.is_none()
230    }
231}
232
233// ============================================================================
234// WithCacheOption — PHP options['with_cache'] 三种形态
235// ============================================================================
236
237/// PHP `options['with_cache']` 的三种形态
238///
239/// 对齐 PHP `withCache($relation, $key, $expire, $tag)` 行为:
240///
241/// ```php
242/// // 1. 不缓存(false 或未设置)
243/// $this->options['with_cache'] = false;
244///
245/// // 2. 全部关联缓存(true)
246/// if (true === $relation || is_numeric($relation)) {
247///     $this->options['with_cache'] = $relation;
248/// }
249///
250/// // 3. 指定关联缓存
251/// $this->options['with_cache'][$relation] = [$key, $expire, $tag];
252/// ```
253///
254/// ## 变体
255///
256/// - [`WithCacheOption::None`]:对齐 PHP `with_cache = false` 或未设置(不缓存)
257/// - [`WithCacheOption::All`]:对齐 PHP `with_cache = true`(全部关联都缓存)
258/// - [`WithCacheOption::Specific`]:对齐 PHP `with_cache = [$name => [$key, $expire, $tag]]`
259///
260/// ## 示例
261///
262/// ```ignore
263/// use sz_rust_core::relation::cache::{WithCacheOption, WithCacheConfig};
264///
265/// // 全部关联缓存(对齐 PHP withCache(true))
266/// let opt = WithCacheOption::All;
267/// assert!(opt.is_enabled());
268///
269/// // 指定关联缓存(对齐 PHP withCache('orders', true, 3600, null))
270/// let mut specific = std::collections::HashMap::new();
271/// specific.insert("orders".to_string(), WithCacheConfig::default());
272/// let opt = WithCacheOption::Specific(specific);
273/// assert!(opt.is_enabled());
274///
275/// // 不缓存(对齐 PHP withCache(false))
276/// let opt = WithCacheOption::None;
277/// assert!(!opt.is_enabled());
278/// ```
279#[derive(Debug, Clone, PartialEq, Eq, Default)]
280pub enum WithCacheOption {
281    /// 不缓存(对齐 PHP `with_cache = false` 或未设置)
282    #[default]
283    None,
284    /// 全部关联都缓存(对齐 PHP `with_cache = true`)
285    All,
286    /// 指定关联缓存(对齐 PHP `with_cache = [$name => [$key, $expire, $tag]]`)
287    Specific(HashMap<String, WithCacheConfig>),
288}
289
290impl WithCacheOption {
291    /// 是否启用缓存
292    pub fn is_enabled(&self) -> bool {
293        !matches!(self, WithCacheOption::None)
294    }
295
296    /// 是否为全部关联缓存
297    pub fn is_all(&self) -> bool {
298        matches!(self, WithCacheOption::All)
299    }
300
301    /// 是否为指定关联缓存
302    pub fn is_specific(&self) -> bool {
303        matches!(self, WithCacheOption::Specific(_))
304    }
305
306    /// 获取指定关联的缓存配置
307    ///
308    /// 返回 `Some(&WithCacheConfig)` 如果:
309    /// - 当前为 `All`(返回一个默认配置,对齐 PHP 全部关联使用相同默认配置)
310    /// - 当前为 `Specific` 且包含指定关联名
311    ///
312    /// 返回 `None` 如果:
313    /// - 当前为 `None`
314    /// - 当前为 `Specific` 但不包含指定关联名
315    pub fn get_config(&self, relation_name: &str) -> Option<&WithCacheConfig> {
316        match self {
317            WithCacheOption::All => Some(&DEFAULT_ALL_CONFIG),
318            WithCacheOption::Specific(map) => map.get(relation_name),
319            WithCacheOption::None => None,
320        }
321    }
322}
323
324/// `WithCacheOption::All` 的默认配置
325///
326/// 对齐 PHP `withCache(true)` 时所有关联使用默认 `[$key=true, $expire=null, $tag=null]` 配置。
327const DEFAULT_ALL_CONFIG: WithCacheConfig = WithCacheConfig {
328    key: None,
329    expire: None,
330    tag: None,
331};
332
333// ============================================================================
334// php_with_cache_config — 构造关联缓存配置
335// ============================================================================
336
337/// 构造 PHP 关联缓存配置
338///
339/// 对齐 PHP `withCache($relation, $key, $expire, $tag)` 的入口:
340///
341/// ```php
342/// public function withCache($relation = true, $key = true, $expire = null, string $tag = null)
343/// ```
344///
345/// ## 参数
346///
347/// - `key`:缓存键(`None` 对齐 PHP `$key = true` 自动生成)
348/// - `expire`:过期时间(`None` 对齐 PHP `$expire = null` 永不过期)
349/// - `tag`:缓存标签(`None` 对齐 PHP `$tag = null` 使用默认表名)
350///
351/// ## 示例
352///
353/// ```ignore
354/// use sz_rust_core::relation::cache::php_with_cache_config;
355/// use std::time::Duration;
356///
357/// // 等价 PHP: withCache('orders', true, 3600, null)
358/// let config = php_with_cache_config(None, Some(Duration::from_secs(3600)), None);
359/// assert!(config.is_auto_key());
360/// assert!(!config.is_permanent());
361/// assert!(config.is_default_tag());
362/// ```
363pub fn php_with_cache_config(
364    key: Option<&str>,
365    expire: Option<Duration>,
366    tag: Option<&str>,
367) -> WithCacheConfig {
368    WithCacheConfig {
369        key: key.map(|s| s.to_string()),
370        expire,
371        tag: tag.map(|s| s.to_string()),
372    }
373}
374
375// ============================================================================
376// php_relation_cache_key — 生成 PHP 关联缓存键
377// ============================================================================
378
379/// 生成 PHP 关联缓存键
380///
381/// 对齐 PHP `Connection::getCacheKey()` 第 293 行:
382///
383/// ```php
384/// $key = 'think_' . $this->getConfig('database') . '.' . $query->getTable() . '|' . $query->getOptions('key');
385/// ```
386///
387/// ## 参数
388///
389/// - `database`:数据库名(如 `"shop"`)
390/// - `table`:表名(如 `"users"`)
391/// - `key`:缓存键标识(如 `"1"` 或 `"orders:1"`)
392///
393/// ## 生成规则
394///
395/// ```text
396/// think_{database}.{table}|{key}
397/// ```
398///
399/// ## 示例
400///
401/// ```ignore
402/// use sz_rust_core::relation::cache::php_relation_cache_key;
403///
404/// // 对齐 PHP: 'think_shop.users|1'
405/// let key = php_relation_cache_key("shop", "users", "1");
406/// assert_eq!(key, "think_shop.users|1");
407/// ```
408pub fn php_relation_cache_key(database: &str, table: &str, key: &str) -> String {
409    format!("think_{}.{}|{}", database, table, key)
410}
411
412// ============================================================================
413// php_relation_cache_tag — 生成 PHP 关联缓存 tag
414// ============================================================================
415
416/// 生成 PHP 关联缓存 tag
417///
418/// 对齐 PHP `BaseQuery::cache()` 第 786 行:
419///
420/// ```php
421/// $this->options['cache'] = [$key, $expire, $tag ?: $this->getTable()];
422/// ```
423///
424/// 默认 tag 为表名(`$this->getTable()`),如果传入自定义 tag 则使用自定义值。
425///
426/// ## 参数
427///
428/// - `table`:表名(作为默认 tag)
429/// - `custom_tag`:自定义 tag(`None` 或空字符串使用表名)
430///
431/// ## 示例
432///
433/// ```ignore
434/// use sz_rust_core::relation::cache::php_relation_cache_tag;
435///
436/// // 无自定义 tag → 使用表名
437/// let tag = php_relation_cache_tag("users", None);
438/// assert_eq!(tag, "users");
439///
440/// // 有自定义 tag → 使用自定义值
441/// let tag = php_relation_cache_tag("users", Some("user_cache"));
442/// assert_eq!(tag, "user_cache");
443/// ```
444pub fn php_relation_cache_tag(table: &str, custom_tag: Option<&str>) -> String {
445    match custom_tag {
446        Some(t) if !t.is_empty() => t.to_string(),
447        _ => table.to_string(),
448    }
449}
450
451// ============================================================================
452// php_relation_cache_remember — 缓存关联查询结果
453// ============================================================================
454
455/// 缓存关联查询结果
456///
457/// 对齐 PHP `Connection::cacheData()` 第 274-281 行:
458///
459/// ```php
460/// protected function cacheData(CacheItem $cacheItem)
461/// {
462///     if ($cacheItem->getTag() && method_exists($this->cache, 'tag')) {
463///         $this->cache->tag($cacheItem->getTag())->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire());
464///     } else {
465///         $this->cache->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire());
466///     }
467/// }
468/// ```
469///
470/// ## PHP tag 与 sz-orm-core CacheKey.table 的映射
471///
472/// PHP 中 `tag` 通过 `CacheItem` 独立传递,sz-orm-core `L2Cache` 通过 `CacheKey.table`
473/// 字段实现表级索引(等价于 PHP tag)。因此本函数假设 `key.table` 已设置为正确的
474/// tag 值(默认为表名,对齐 PHP `$tag ?: $this->getTable()`)。
475///
476/// ## 参数
477///
478/// - `cache`:L2Cache 实例
479/// - `key`:缓存键(`key.table` 字段作为 tag,对齐 PHP `Cache::tag($tag)->set()`)
480/// - `value`:缓存值
481/// - `ttl`:过期时间(`None` 永不过期,对齐 PHP `$expire = null`)
482///
483/// ## 示例
484///
485/// ```ignore
486/// use sz_rust_core::relation::cache::*;
487/// use sz_orm_core::Value;
488///
489/// let cache = L2Cache::new();
490/// let key = CacheKey::by_relation("users", "orders:1");
491/// php_relation_cache_remember(&cache, &key, Value::I64(42), None);
492/// assert_eq!(cache.get(&key), Some(Value::I64(42)));
493/// ```
494pub fn php_relation_cache_remember(
495    cache: &L2Cache,
496    key: &CacheKey,
497    value: Value,
498    ttl: Option<Duration>,
499) {
500    cache.put(key, value, ttl);
501}
502
503// ============================================================================
504// php_relation_cache_fetch — 读取关联缓存
505// ============================================================================
506
507/// 读取关联缓存
508///
509/// 对齐 PHP `$this->cache->get($key)` 行为。
510///
511/// ## 参数
512///
513/// - `cache`:L2Cache 实例
514/// - `key`:缓存键
515///
516/// ## 返回值
517///
518/// - `Some(value)`:缓存命中
519/// - `None`:缓存未命中或已过期
520///
521/// ## 示例
522///
523/// ```ignore
524/// use sz_rust_core::relation::cache::*;
525/// use sz_orm_core::Value;
526///
527/// let cache = L2Cache::new();
528/// let key = CacheKey::by_relation("users", "orders:1");
529/// cache.put(&key, Value::I64(42), None);
530/// assert_eq!(php_relation_cache_fetch(&cache, &key), Some(Value::I64(42)));
531/// ```
532pub fn php_relation_cache_fetch(cache: &L2Cache, key: &CacheKey) -> Option<Value> {
533    cache.get(key)
534}
535
536// ============================================================================
537// php_relation_cache_invalidate — 失效整表关联缓存
538// ============================================================================
539
540/// 失效整表关联缓存
541///
542/// 对齐 PHP `Cache::clear($tag)` 或 `Cache::tag($tag)->clear()` 行为。
543///
544/// PHP 端 `cache()` 方法第 786 行默认 tag 为表名:
545///
546/// ```php
547/// $this->options['cache'] = [$key, $expire, $tag ?: $this->getTable()];
548/// ```
549///
550/// 因此 `Cache::clear($tag)` 实际是按表名失效所有缓存项,等价于
551/// sz-orm-core `L2Cache::invalidate_table(table)`。
552///
553/// ## 参数
554///
555/// - `cache`:L2Cache 实例
556/// - `table`:表名(对齐 PHP tag,默认为表名)
557///
558/// ## 示例
559///
560/// ```ignore
561/// use sz_rust_core::relation::cache::*;
562/// use sz_orm_core::Value;
563///
564/// let cache = L2Cache::new();
565/// let key = CacheKey::by_relation("users", "orders:1");
566/// cache.put(&key, Value::I64(42), None);
567///
568/// php_relation_cache_invalidate(&cache, "users");
569/// assert_eq!(cache.get(&key), None);
570/// ```
571pub fn php_relation_cache_invalidate(cache: &L2Cache, table: &str) {
572    cache.invalidate_table(table);
573}
574
575// ============================================================================
576// php_relation_cache_delete — 失效单个关联缓存
577// ============================================================================
578
579/// 失效单个关联缓存
580///
581/// 对齐 PHP `Cache::delete($key)` 行为。
582///
583/// ## 参数
584///
585/// - `cache`:L2Cache 实例
586/// - `key`:缓存键
587///
588/// ## 示例
589///
590/// ```ignore
591/// use sz_rust_core::relation::cache::*;
592/// use sz_orm_core::Value;
593///
594/// let cache = L2Cache::new();
595/// let key = CacheKey::by_relation("users", "orders:1");
596/// cache.put(&key, Value::I64(42), None);
597///
598/// php_relation_cache_delete(&cache, &key);
599/// assert_eq!(cache.get(&key), None);
600/// ```
601pub fn php_relation_cache_delete(cache: &L2Cache, key: &CacheKey) {
602    cache.invalidate(key);
603}
604
605// ============================================================================
606// 单元测试
607// ============================================================================
608
609#[cfg(test)]
610mod tests {
611    use super::*;
612    use sz_rust_orm_facade::Value;
613
614    // ====================================================================
615    // 组 1:WithCacheConfig 结构体(5 个测试)
616    // ====================================================================
617
618    #[test]
619    fn test_with_cache_config_default() {
620        // 对齐 PHP 默认值:$key = true, $expire = null, $tag = null
621        let config = WithCacheConfig::default();
622        assert_eq!(config.key, None);
623        assert_eq!(config.expire, None);
624        assert_eq!(config.tag, None);
625    }
626
627    #[test]
628    fn test_with_cache_config_new() {
629        let config = WithCacheConfig::new(
630            Some("custom_key".to_string()),
631            Some(Duration::from_secs(3600)),
632            Some("custom_tag".to_string()),
633        );
634        assert_eq!(config.key, Some("custom_key".to_string()));
635        assert_eq!(config.expire, Some(Duration::from_secs(3600)));
636        assert_eq!(config.tag, Some("custom_tag".to_string()));
637    }
638
639    #[test]
640    fn test_with_cache_config_is_auto_key() {
641        // None 对齐 PHP $key = true(自动生成)
642        let config = WithCacheConfig::default();
643        assert!(config.is_auto_key());
644
645        let config = WithCacheConfig::new(Some("custom".to_string()), None, None);
646        assert!(!config.is_auto_key());
647    }
648
649    #[test]
650    fn test_with_cache_config_is_permanent() {
651        // None 对齐 PHP $expire = null(永不过期)
652        let config = WithCacheConfig::default();
653        assert!(config.is_permanent());
654
655        let config = WithCacheConfig::new(None, Some(Duration::from_secs(60)), None);
656        assert!(!config.is_permanent());
657    }
658
659    #[test]
660    fn test_with_cache_config_is_default_tag() {
661        // None 对齐 PHP $tag = null(使用默认表名)
662        let config = WithCacheConfig::default();
663        assert!(config.is_default_tag());
664
665        let config = WithCacheConfig::new(None, None, Some("custom_tag".to_string()));
666        assert!(!config.is_default_tag());
667    }
668
669    // ====================================================================
670    // 组 2:WithCacheOption 枚举(7 个测试)
671    // ====================================================================
672
673    #[test]
674    fn test_with_cache_option_default_is_none() {
675        // 对齐 PHP 默认值:未设置 with_cache
676        let opt = WithCacheOption::default();
677        assert!(matches!(opt, WithCacheOption::None));
678    }
679
680    #[test]
681    fn test_with_cache_option_all_is_enabled() {
682        // 对齐 PHP withCache(true)
683        let opt = WithCacheOption::All;
684        assert!(opt.is_enabled());
685        assert!(opt.is_all());
686        assert!(!opt.is_specific());
687    }
688
689    #[test]
690    fn test_with_cache_option_specific_is_enabled() {
691        // 对齐 PHP withCache('orders', true, null, null)
692        let mut map = HashMap::new();
693        map.insert("orders".to_string(), WithCacheConfig::default());
694        let opt = WithCacheOption::Specific(map);
695        assert!(opt.is_enabled());
696        assert!(!opt.is_all());
697        assert!(opt.is_specific());
698    }
699
700    #[test]
701    fn test_with_cache_option_none_is_not_enabled() {
702        // 对齐 PHP withCache(false)
703        let opt = WithCacheOption::None;
704        assert!(!opt.is_enabled());
705        assert!(!opt.is_all());
706        assert!(!opt.is_specific());
707    }
708
709    #[test]
710    fn test_with_cache_option_get_config_all() {
711        // All 变体:返回默认配置(对齐 PHP withCache(true) 全部关联使用默认配置)
712        let opt = WithCacheOption::All;
713        let config = opt.get_config("any_relation").unwrap();
714        assert_eq!(config.key, None);
715        assert_eq!(config.expire, None);
716        assert_eq!(config.tag, None);
717    }
718
719    #[test]
720    fn test_with_cache_option_get_config_specific_hit() {
721        // Specific 变体:命中指定关联
722        let mut map = HashMap::new();
723        map.insert(
724            "orders".to_string(),
725            WithCacheConfig::new(None, Some(Duration::from_secs(3600)), None),
726        );
727        let opt = WithCacheOption::Specific(map);
728
729        let config = opt.get_config("orders").unwrap();
730        assert_eq!(config.expire, Some(Duration::from_secs(3600)));
731    }
732
733    #[test]
734    fn test_with_cache_option_get_config_specific_miss_and_none() {
735        // Specific 变体:未命中指定关联
736        let mut map = HashMap::new();
737        map.insert("orders".to_string(), WithCacheConfig::default());
738        let opt = WithCacheOption::Specific(map);
739        assert!(opt.get_config("nonexistent").is_none());
740
741        // None 变体:始终返回 None
742        let opt = WithCacheOption::None;
743        assert!(opt.get_config("any").is_none());
744    }
745
746    // ====================================================================
747    // 组 3:php_with_cache_config()(5 个测试)
748    // ====================================================================
749
750    #[test]
751    fn test_php_with_cache_config_default() {
752        // 对齐 PHP withCache($relation, true, null, null) 的配置三元组
753        let config = php_with_cache_config(None, None, None);
754        assert_eq!(config.key, None);
755        assert_eq!(config.expire, None);
756        assert_eq!(config.tag, None);
757    }
758
759    #[test]
760    fn test_php_with_cache_config_with_expire() {
761        // 对齐 PHP withCache('orders', true, 3600, null)
762        let config = php_with_cache_config(None, Some(Duration::from_secs(3600)), None);
763        assert_eq!(config.key, None);
764        assert_eq!(config.expire, Some(Duration::from_secs(3600)));
765        assert_eq!(config.tag, None);
766    }
767
768    #[test]
769    fn test_php_with_cache_config_with_custom_key() {
770        // 对齐 PHP withCache('orders', 'custom_key', null, null)
771        let config = php_with_cache_config(Some("custom_key"), None, None);
772        assert_eq!(config.key, Some("custom_key".to_string()));
773    }
774
775    #[test]
776    fn test_php_with_cache_config_with_custom_tag() {
777        // 对齐 PHP withCache('orders', true, null, 'user_cache')
778        let config = php_with_cache_config(None, None, Some("user_cache"));
779        assert_eq!(config.tag, Some("user_cache".to_string()));
780    }
781
782    #[test]
783    fn test_php_with_cache_config_full() {
784        // 对齐 PHP withCache('orders', 'key1', 3600, 'tag1')
785        let config =
786            php_with_cache_config(Some("key1"), Some(Duration::from_secs(3600)), Some("tag1"));
787        assert_eq!(config.key, Some("key1".to_string()));
788        assert_eq!(config.expire, Some(Duration::from_secs(3600)));
789        assert_eq!(config.tag, Some("tag1".to_string()));
790    }
791
792    // ====================================================================
793    // 组 4:php_relation_cache_key()(5 个测试)
794    // ====================================================================
795
796    #[test]
797    fn test_php_relation_cache_key_basic() {
798        // 对齐 PHP: 'think_shop.users|1'
799        let key = php_relation_cache_key("shop", "users", "1");
800        assert_eq!(key, "think_shop.users|1");
801    }
802
803    #[test]
804    fn test_php_relation_cache_key_different_databases() {
805        // 不同数据库生成不同 key
806        let key1 = php_relation_cache_key("shop", "users", "1");
807        let key2 = php_relation_cache_key("admin", "users", "1");
808        assert_ne!(key1, key2);
809    }
810
811    #[test]
812    fn test_php_relation_cache_key_different_tables() {
813        // 不同表生成不同 key
814        let key1 = php_relation_cache_key("shop", "users", "1");
815        let key2 = php_relation_cache_key("shop", "orders", "1");
816        assert_ne!(key1, key2);
817    }
818
819    #[test]
820    fn test_php_relation_cache_key_different_pk() {
821        // 不同主键生成不同 key
822        let key1 = php_relation_cache_key("shop", "users", "1");
823        let key2 = php_relation_cache_key("shop", "users", "2");
824        assert_ne!(key1, key2);
825    }
826
827    #[test]
828    fn test_php_relation_cache_key_format() {
829        // 验证完整格式:think_{database}.{table}|{key}
830        // 对齐 PHP Connection::getCacheKey() 第 293 行
831        let key = php_relation_cache_key("my_db", "my_table", "my_key");
832        assert_eq!(key, "think_my_db.my_table|my_key");
833        // 验证 PHP 格式中的分隔符
834        assert!(key.starts_with("think_"));
835        assert!(key.contains("."));
836        assert!(key.contains("|"));
837    }
838
839    // ====================================================================
840    // 组 5:php_relation_cache_tag()(4 个测试)
841    // ====================================================================
842
843    #[test]
844    fn test_php_relation_cache_tag_default() {
845        // 对齐 PHP $tag ?: $this->getTable() — 无自定义 tag 时使用表名
846        let tag = php_relation_cache_tag("users", None);
847        assert_eq!(tag, "users");
848    }
849
850    #[test]
851    fn test_php_relation_cache_tag_custom() {
852        // 有自定义 tag 时使用自定义值
853        let tag = php_relation_cache_tag("users", Some("user_cache"));
854        assert_eq!(tag, "user_cache");
855    }
856
857    #[test]
858    fn test_php_relation_cache_tag_empty_string_uses_table() {
859        // 空字符串视为无 tag,使用表名(对齐 PHP $tag ?: $table 中 ?: 的 falsy 语义)
860        let tag = php_relation_cache_tag("users", Some(""));
861        assert_eq!(tag, "users");
862    }
863
864    #[test]
865    fn test_php_relation_cache_tag_different_tables() {
866        // 不同表生成不同 tag
867        let tag1 = php_relation_cache_tag("users", None);
868        let tag2 = php_relation_cache_tag("orders", None);
869        assert_ne!(tag1, tag2);
870    }
871
872    // ====================================================================
873    // 组 6:L2Cache 集成测试(10 个测试)
874    // ====================================================================
875
876    #[test]
877    fn test_php_relation_cache_remember_and_fetch_hit() {
878        // 对齐 PHP cacheData() + $this->cache->get() — 缓存命中
879        let cache = L2Cache::new();
880        let key = CacheKey::by_relation("users", "orders:1");
881        php_relation_cache_remember(&cache, &key, Value::I64(42), None);
882
883        let val = php_relation_cache_fetch(&cache, &key);
884        assert_eq!(val, Some(Value::I64(42)));
885    }
886
887    #[test]
888    fn test_php_relation_cache_fetch_miss() {
889        // 缓存未命中
890        let cache = L2Cache::new();
891        let key = CacheKey::by_relation("users", "orders:1");
892        let val = php_relation_cache_fetch(&cache, &key);
893        assert_eq!(val, None);
894    }
895
896    #[test]
897    fn test_php_relation_cache_invalidate_table() {
898        // 对齐 PHP Cache::clear($tag) — 表级失效
899        let cache = L2Cache::new();
900
901        let key1 = CacheKey::by_relation("users", "orders:1");
902        let key2 = CacheKey::by_relation("users", "orders:2");
903        let key3 = CacheKey::by_relation("orders", "items:1"); // 不同表
904
905        php_relation_cache_remember(&cache, &key1, Value::I64(1), None);
906        php_relation_cache_remember(&cache, &key2, Value::I64(2), None);
907        php_relation_cache_remember(&cache, &key3, Value::I64(3), None);
908
909        // 失效 users 表
910        php_relation_cache_invalidate(&cache, "users");
911
912        // users 表的缓存项应被失效
913        assert_eq!(php_relation_cache_fetch(&cache, &key1), None);
914        assert_eq!(php_relation_cache_fetch(&cache, &key2), None);
915        // orders 表的缓存项应保留
916        assert_eq!(php_relation_cache_fetch(&cache, &key3), Some(Value::I64(3)));
917    }
918
919    #[test]
920    fn test_php_relation_cache_delete_single() {
921        // 对齐 PHP Cache::delete($key) — 单键失效
922        let cache = L2Cache::new();
923        let key1 = CacheKey::by_relation("users", "orders:1");
924        let key2 = CacheKey::by_relation("users", "orders:2");
925
926        php_relation_cache_remember(&cache, &key1, Value::I64(1), None);
927        php_relation_cache_remember(&cache, &key2, Value::I64(2), None);
928
929        // 仅删除 key1
930        php_relation_cache_delete(&cache, &key1);
931
932        assert_eq!(php_relation_cache_fetch(&cache, &key1), None);
933        assert_eq!(php_relation_cache_fetch(&cache, &key2), Some(Value::I64(2)));
934    }
935
936    #[test]
937    fn test_php_relation_cache_ttl_expiration() {
938        // 对齐 PHP $expire 参数 — TTL 过期
939        let cache = L2Cache::new();
940        let key = CacheKey::by_relation("users", "orders:1");
941
942        php_relation_cache_remember(
943            &cache,
944            &key,
945            Value::I64(42),
946            Some(Duration::from_millis(50)),
947        );
948
949        // 立即读取应命中
950        assert_eq!(php_relation_cache_fetch(&cache, &key), Some(Value::I64(42)));
951
952        // 等待过期
953        std::thread::sleep(Duration::from_millis(100));
954        assert_eq!(php_relation_cache_fetch(&cache, &key), None);
955    }
956
957    #[test]
958    fn test_php_relation_cache_multiple_relations() {
959        // 多关联缓存共存
960        let cache = L2Cache::new();
961
962        let orders_key = CacheKey::by_relation("users", "orders:1");
963        let profile_key = CacheKey::by_relation("users", "profile:1");
964
965        php_relation_cache_remember(&cache, &orders_key, Value::I64(10), None);
966        php_relation_cache_remember(
967            &cache,
968            &profile_key,
969            Value::String("Alice".to_string()),
970            None,
971        );
972
973        assert_eq!(
974            php_relation_cache_fetch(&cache, &orders_key),
975            Some(Value::I64(10))
976        );
977        assert_eq!(
978            php_relation_cache_fetch(&cache, &profile_key),
979            Some(Value::String("Alice".to_string()))
980        );
981    }
982
983    #[test]
984    fn test_php_relation_cache_table_isolation() {
985        // 不同表缓存隔离
986        let cache = L2Cache::new();
987
988        let users_key = CacheKey::by_relation("users", "pk:1");
989        let orders_key = CacheKey::by_relation("orders", "pk:1");
990
991        php_relation_cache_remember(&cache, &users_key, Value::I64(1), None);
992        php_relation_cache_remember(&cache, &orders_key, Value::I64(2), None);
993
994        // 失效 users 表不影响 orders 表
995        php_relation_cache_invalidate(&cache, "users");
996        assert_eq!(php_relation_cache_fetch(&cache, &users_key), None);
997        assert_eq!(
998            php_relation_cache_fetch(&cache, &orders_key),
999            Some(Value::I64(2))
1000        );
1001    }
1002
1003    #[test]
1004    fn test_with_cache_option_all_integration() {
1005        // 集成测试:WithCacheOption::All 全部关联缓存
1006        let opt = WithCacheOption::All;
1007        let cache = L2Cache::new();
1008
1009        // 对所有关联应用缓存配置(对齐 PHP withCache(true))
1010        for relation_name in &["orders", "profile", "comments"] {
1011            let config = opt.get_config(relation_name).unwrap();
1012            let key = CacheKey::by_relation("users", format!("{}:1", relation_name));
1013            let ttl = config.expire;
1014            php_relation_cache_remember(&cache, &key, Value::I64(1), ttl);
1015        }
1016
1017        // 所有关联都应命中
1018        for relation_name in &["orders", "profile", "comments"] {
1019            let key = CacheKey::by_relation("users", format!("{}:1", relation_name));
1020            assert!(php_relation_cache_fetch(&cache, &key).is_some());
1021        }
1022    }
1023
1024    #[test]
1025    fn test_with_cache_option_specific_integration() {
1026        // 集成测试:WithCacheOption::Specific 仅指定关联缓存
1027        let mut map = HashMap::new();
1028        map.insert(
1029            "orders".to_string(),
1030            WithCacheConfig::new(None, Some(Duration::from_secs(3600)), None),
1031        );
1032        // profile 未在 map 中,不应缓存
1033        let opt = WithCacheOption::Specific(map);
1034
1035        assert!(opt.get_config("orders").is_some());
1036        assert!(opt.get_config("profile").is_none());
1037
1038        // 仅 orders 关联应用缓存
1039        let cache = L2Cache::new();
1040        if let Some(config) = opt.get_config("orders") {
1041            let key = CacheKey::by_relation("users", "orders:1");
1042            php_relation_cache_remember(&cache, &key, Value::I64(1), config.expire);
1043        }
1044
1045        let orders_key = CacheKey::by_relation("users", "orders:1");
1046        assert!(php_relation_cache_fetch(&cache, &orders_key).is_some());
1047    }
1048
1049    #[test]
1050    fn test_php_relation_cache_overwrite() {
1051        // 对齐 PHP 同一 key 重复写入 — 覆盖旧值
1052        let cache = L2Cache::new();
1053        let key = CacheKey::by_relation("users", "orders:1");
1054
1055        php_relation_cache_remember(&cache, &key, Value::I64(1), None);
1056        php_relation_cache_remember(&cache, &key, Value::I64(2), None);
1057
1058        assert_eq!(php_relation_cache_fetch(&cache, &key), Some(Value::I64(2)));
1059    }
1060
1061    // ====================================================================
1062    // 组 7:R5 PHP 行为对齐验证(7 个测试)
1063    // ====================================================================
1064
1065    #[test]
1066    fn test_r5_php_with_cache_true_to_all() {
1067        // R5: PHP withCache(true) → WithCacheOption::All
1068        // PHP 源码 ModelRelationQuery.php 第 325-328 行:
1069        //   if (true === $relation || is_numeric($relation)) {
1070        //       $this->options['with_cache'] = $relation;
1071        //       return $this;
1072        //   }
1073        let opt = WithCacheOption::All;
1074        assert!(opt.is_enabled());
1075        assert!(opt.is_all());
1076        // 全部关联都应能获取到默认配置
1077        assert!(opt.get_config("orders").is_some());
1078        assert!(opt.get_config("profile").is_some());
1079    }
1080
1081    #[test]
1082    fn test_r5_php_with_cache_named_to_specific() {
1083        // R5: PHP withCache('orders', true, 3600, null) → WithCacheOption::Specific
1084        // PHP 源码 ModelRelationQuery.php 第 330-337 行:
1085        //   $relations = (array) $relation;
1086        //   foreach ($relations as $name => $relation) {
1087        //       $this->options['with_cache'][$relation] = [$key, $expire, $tag];
1088        //   }
1089        let mut map = HashMap::new();
1090        map.insert(
1091            "orders".to_string(),
1092            php_with_cache_config(None, Some(Duration::from_secs(3600)), None),
1093        );
1094        let opt = WithCacheOption::Specific(map);
1095
1096        assert!(opt.is_enabled());
1097        assert!(opt.is_specific());
1098
1099        // 指定关联应能获取到配置
1100        let config = opt.get_config("orders").unwrap();
1101        assert_eq!(config.expire, Some(Duration::from_secs(3600)));
1102
1103        // 未指定关联应获取不到配置
1104        assert!(opt.get_config("profile").is_none());
1105    }
1106
1107    #[test]
1108    fn test_r5_php_with_cache_false_to_none() {
1109        // R5: PHP withCache(false) → WithCacheOption::None
1110        // PHP 源码 ModelRelationQuery.php 第 316-318 行:
1111        //   if (false === $relation || false === $key || !$this->getConnection()->getCache()) {
1112        //       return $this;
1113        //   }
1114        let opt = WithCacheOption::None;
1115        assert!(!opt.is_enabled());
1116        assert!(opt.get_config("any").is_none());
1117    }
1118
1119    #[test]
1120    fn test_r5_php_tag_default_to_table() {
1121        // R5: PHP $tag ?: $this->getTable() → php_relation_cache_tag() 默认表名
1122        // PHP 源码 BaseQuery.php 第 786 行:
1123        //   $this->options['cache'] = [$key, $expire, $tag ?: $this->getTable()];
1124        let tag = php_relation_cache_tag("users", None);
1125        assert_eq!(tag, "users"); // 默认 tag = 表名
1126    }
1127
1128    #[test]
1129    fn test_r5_php_cache_clear_to_invalidate_table() {
1130        // R5: PHP Cache::clear($tag) → php_relation_cache_invalidate() 表级失效
1131        // PHP 源码:tag 默认为表名,clear($tag) 失效该 tag 下的所有缓存
1132        let cache = L2Cache::new();
1133
1134        let key1 = CacheKey::by_relation("users", "orders:1");
1135        let key2 = CacheKey::by_relation("users", "profile:1");
1136        let key3 = CacheKey::by_relation("orders", "items:1");
1137
1138        php_relation_cache_remember(&cache, &key1, Value::I64(1), None);
1139        php_relation_cache_remember(&cache, &key2, Value::I64(2), None);
1140        php_relation_cache_remember(&cache, &key3, Value::I64(3), None);
1141
1142        // Cache::clear('users') — 失效 users 表所有缓存
1143        php_relation_cache_invalidate(&cache, "users");
1144
1145        // users 表缓存全部失效
1146        assert_eq!(php_relation_cache_fetch(&cache, &key1), None);
1147        assert_eq!(php_relation_cache_fetch(&cache, &key2), None);
1148        // orders 表缓存保留
1149        assert_eq!(php_relation_cache_fetch(&cache, &key3), Some(Value::I64(3)));
1150    }
1151
1152    #[test]
1153    fn test_r5_php_cache_delete_to_invalidate_key() {
1154        // R5: PHP Cache::delete($key) → php_relation_cache_delete() 单键失效
1155        let cache = L2Cache::new();
1156        let key = CacheKey::by_relation("users", "orders:1");
1157
1158        php_relation_cache_remember(&cache, &key, Value::I64(42), None);
1159        assert!(php_relation_cache_fetch(&cache, &key).is_some());
1160
1161        php_relation_cache_delete(&cache, &key);
1162        assert!(php_relation_cache_fetch(&cache, &key).is_none());
1163    }
1164
1165    #[test]
1166    fn test_r5_php_get_cache_key_format() {
1167        // R5: PHP getCacheKey() 格式 → php_relation_cache_key() 生成
1168        // PHP 源码 Connection.php 第 293 行:
1169        //   $key = 'think_' . $this->getConfig('database') . '.' . $query->getTable() . '|' . $query->getOptions('key');
1170        let key = php_relation_cache_key("shop", "users", "1");
1171        assert_eq!(key, "think_shop.users|1");
1172
1173        // 验证 PHP 格式中的分隔符
1174        assert!(key.starts_with("think_"));
1175        assert!(key.contains("."));
1176        assert!(key.contains("|"));
1177    }
1178}