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}