Skip to main content

alun_cache/
lib.rs

1//! 缓存模块:本地内存缓存 + Redis 缓存
2//!
3//! 通过配置 `cache.type` 切换:
4//! - `local` → 内存缓存(默认)
5//! - `redis` → Redis 缓存(需配置 redis_url)
6
7use async_trait::async_trait;
8use serde::{Serialize, de::DeserializeOwned};
9use serde_json;
10use alun_core::Result;
11use std::collections::HashMap;
12use std::sync::Arc;
13use std::sync::atomic::{AtomicU64, Ordering};
14use parking_lot::RwLock;
15use std::time::{Instant, Duration};
16use redis::aio::ConnectionManager;
17
18// ──── 缓存 trait ────────────────────────────────────
19
20/// 统一缓存接口(本地/Redis 实现同一 trait)
21///
22/// # 示例
23///
24/// ```ignore
25/// let cache: &dyn Cache = &local_cache;
26/// cache.set::<String>("key", &"value".to_string()).await?;
27/// let val: Option<String> = cache.get("key").await?;
28/// ```
29#[async_trait]
30pub trait Cache: Send + Sync {
31    /// 读取缓存值,返回 `Ok(None)` 表示 key 不存在或已过期
32    async fn get<T: DeserializeOwned + Send>(&self, key: &str) -> Result<Option<T>>;
33
34    /// 设置缓存(永不过期),值通过 serde_json 序列化
35    async fn set<T: Serialize + Send + Sync>(&self, key: &str, value: &T) -> Result<()>;
36
37    /// 设置缓存(指定过期秒数),到期后自动不可见
38    async fn set_ex<T: Serialize + Send + Sync>(&self, key: &str, value: &T, ttl_secs: u64) -> Result<()>;
39
40    /// 删除单个 key(不存在不报错)
41    async fn del(&self, key: &str) -> Result<()>;
42
43    /// 检查 key 是否存在且未过期
44    async fn exists(&self, key: &str) -> Result<bool>;
45
46    /// 计数器递增(key 不存在则从 0 开始),返回递增后的值
47    async fn incr(&self, key: &str, delta: i64) -> Result<i64>;
48
49    /// 获取匹配模式(glob:`*`/`?`)的所有 key
50    async fn keys(&self, pattern: &str) -> Result<Vec<String>>;
51
52    /// 删除匹配模式的所有 key,返回删除数
53    async fn delete_pattern(&self, pattern: &str) -> Result<u64>;
54
55    /// 缓存统计信息(内存缓存支持,Redis 返回全零)
56    fn stats(&self) -> CacheStats { CacheStats::default() }
57}
58
59// ──── 本地缓存条目 ──────────────────────────────────
60
61struct CacheEntry {
62    value: serde_json::Value,
63    expires_at: Option<Instant>,
64}
65
66// ──── 本地内存缓存 ─────────────────────────────────
67
68/// 缓存统计指标
69#[derive(Debug, Clone, Default)]
70pub struct CacheStats {
71    /// 缓存命中次数
72    pub hits: u64,
73    /// 缓存未命中次数
74    pub misses: u64,
75    /// 设置缓存次数
76    pub sets: u64,
77    /// 删除缓存次数
78    pub deletes: u64,
79    /// 淘汰次数
80    pub evictions: u64,
81    /// 过期清理次数
82    pub expired_cleanups: u64,
83}
84
85/// 本地内存缓存(HashMap + RwLock + TTL + 统计 + 后台清理)
86#[derive(Clone)]
87pub struct LocalCache {
88    /// 缓存 key 前缀(通常为 app_name,用于多项目隔离)
89    key_prefix: String,
90    /// 缓存数据存储(key → 条目)
91    data: Arc<RwLock<HashMap<String, CacheEntry>>>,
92    /// 最大容量(超过后 LRU 淘汰)
93    max_capacity: u64,
94    /// 默认 TTL 秒数(set 时未指定 TTL 则使用此值)
95    default_ttl_secs: u64,
96    /// 缓存统计信息(原子计数器)
97    stats: Arc<AtomicCacheStats>,
98    /// 后台清理任务的间隔秒数
99    cleanup_interval_secs: u64,
100}
101
102struct AtomicCacheStats {
103    hits: AtomicU64,
104    misses: AtomicU64,
105    sets: AtomicU64,
106    deletes: AtomicU64,
107    evictions: AtomicU64,
108    expired_cleanups: AtomicU64,
109}
110
111impl Clone for AtomicCacheStats {
112    fn clone(&self) -> Self {
113        Self {
114            hits: AtomicU64::new(self.hits.load(Ordering::Relaxed)),
115            misses: AtomicU64::new(self.misses.load(Ordering::Relaxed)),
116            sets: AtomicU64::new(self.sets.load(Ordering::Relaxed)),
117            deletes: AtomicU64::new(self.deletes.load(Ordering::Relaxed)),
118            evictions: AtomicU64::new(self.evictions.load(Ordering::Relaxed)),
119            expired_cleanups: AtomicU64::new(self.expired_cleanups.load(Ordering::Relaxed)),
120        }
121    }
122}
123
124impl LocalCache {
125    /// 创建本地内存缓存
126    ///
127    /// - `key_prefix`: 缓存 key 前缀(通常为 app_name),为空时不加前缀
128    /// - `max_capacity`: 超过此容量后按 LRU 策略淘汰
129    /// - `default_ttl_secs`: 默认过期秒数(0 = 永不过期)
130    pub fn new(key_prefix: &str, max_capacity: u64, default_ttl_secs: u64) -> Self {
131        Self {
132            key_prefix: key_prefix.to_string(),
133            data: Arc::new(RwLock::new(HashMap::new())),
134            max_capacity,
135            default_ttl_secs,
136            stats: Arc::new(AtomicCacheStats {
137                hits: AtomicU64::new(0),
138                misses: AtomicU64::new(0),
139                sets: AtomicU64::new(0),
140                deletes: AtomicU64::new(0),
141                evictions: AtomicU64::new(0),
142                expired_cleanups: AtomicU64::new(0),
143            }),
144            cleanup_interval_secs: 60,
145        }
146    }
147
148    pub fn with_cleanup_interval(mut self, interval_secs: u64) -> Self {
149        self.cleanup_interval_secs = interval_secs;
150        self
151    }
152
153    /// 获取缓存统计快照
154    pub fn stats(&self) -> CacheStats {
155        CacheStats {
156            hits: self.stats.hits.load(Ordering::Relaxed),
157            misses: self.stats.misses.load(Ordering::Relaxed),
158            sets: self.stats.sets.load(Ordering::Relaxed),
159            deletes: self.stats.deletes.load(Ordering::Relaxed),
160            evictions: self.stats.evictions.load(Ordering::Relaxed),
161            expired_cleanups: self.stats.expired_cleanups.load(Ordering::Relaxed),
162        }
163    }
164
165    /// 获取当前缓存条目数量
166    pub fn len(&self) -> usize {
167        self.data.read().len()
168    }
169
170    /// 缓存是否为空
171    pub fn is_empty(&self) -> bool {
172        self.data.read().is_empty()
173    }
174
175    /// 手动清理所有过期条目,返回清理数
176    pub fn cleanup_expired(&self) -> u64 {
177        let mut guard = self.data.write();
178        let expired: Vec<String> = guard.iter()
179            .filter(|(_, entry)| entry.expires_at.map_or(false, |t| Instant::now() > t))
180            .map(|(k, _)| k.clone())
181            .collect();
182        let count = expired.len() as u64;
183        for k in &expired { guard.remove(k); }
184        self.stats.expired_cleanups.fetch_add(count, Ordering::Relaxed);
185        count
186    }
187
188    /// 启动后台过期清理任务(每 `interval_secs` 秒执行一次)
189    pub fn start_background_cleanup(&self) {
190        let data = Arc::clone(&self.data);
191        let stats = Arc::clone(&self.stats);
192        let interval = self.cleanup_interval_secs;
193
194        tokio::spawn(async move {
195            loop {
196                tokio::time::sleep(Duration::from_secs(interval)).await;
197                let mut guard = data.write();
198                let now = Instant::now();
199                let expired: Vec<String> = guard.iter()
200                    .filter(|(_, entry)| entry.expires_at.map_or(false, |t| now > t))
201                    .map(|(k, _)| k.clone())
202                    .collect();
203                let count = expired.len() as u64;
204                for k in &expired { guard.remove(k); }
205                if count > 0 {
206                    stats.expired_cleanups.fetch_add(count, Ordering::Relaxed);
207                    tracing::debug!("缓存后台清理: 移除 {} 个过期条目", count);
208                }
209            }
210        });
211    }
212
213    /// 生成带前缀的完整缓存 key
214    ///
215    /// 若 `key_prefix` 非空,返回 `"{prefix}:{key}"`,否则返回原始 key。
216    #[inline]
217    fn prefixed(&self, key: &str) -> String {
218        if self.key_prefix.is_empty() {
219            key.to_string()
220        } else {
221            format!("{}:{}", self.key_prefix, key)
222        }
223    }
224}
225
226#[async_trait]
227impl Cache for LocalCache {
228    async fn get<T: DeserializeOwned + Send>(&self, key: &str) -> Result<Option<T>> {
229        let key = self.prefixed(key);
230        let guard = self.data.read();
231        if let Some(entry) = guard.get(&key) {
232            if let Some(expires) = entry.expires_at {
233                if Instant::now() > expires {
234                    drop(guard);
235                    self.data.write().remove(&key);
236                    self.stats.misses.fetch_add(1, Ordering::Relaxed);
237                    return Ok(None);
238                }
239            }
240            self.stats.hits.fetch_add(1, Ordering::Relaxed);
241            let val: T = serde_json::from_value(entry.value.clone())
242                .map_err(|e| alun_core::Error::Msg(e.to_string()))?;
243            return Ok(Some(val));
244        }
245        self.stats.misses.fetch_add(1, Ordering::Relaxed);
246        Ok(None)
247    }
248
249    async fn set<T: Serialize + Send + Sync>(&self, key: &str, value: &T) -> Result<()> {
250        let key = self.prefixed(key);
251        let v = serde_json::to_value(value)
252            .map_err(|e| alun_core::Error::Msg(e.to_string()))?;
253        let mut guard = self.data.write();
254        if self.max_capacity > 0 && guard.len() as u64 >= self.max_capacity {
255            drop(guard);
256            return Err(alun_core::Error::Msg(format!("缓存容量已达上限: {}", self.max_capacity)));
257        }
258        self.stats.sets.fetch_add(1, Ordering::Relaxed);
259        let expires_at = if self.default_ttl_secs > 0 {
260            Some(Instant::now() + Duration::from_secs(self.default_ttl_secs))
261        } else {
262            None
263        };
264        guard.insert(key, CacheEntry { value: v, expires_at });
265        Ok(())
266    }
267
268    async fn set_ex<T: Serialize + Send + Sync>(&self, key: &str, value: &T, ttl_secs: u64) -> Result<()> {
269        let key = self.prefixed(key);
270        let v = serde_json::to_value(value)
271            .map_err(|e| alun_core::Error::Msg(e.to_string()))?;
272        self.stats.sets.fetch_add(1, Ordering::Relaxed);
273        self.data.write().insert(key, CacheEntry {
274            value: v,
275            expires_at: Some(Instant::now() + Duration::from_secs(ttl_secs)),
276        });
277        Ok(())
278    }
279
280    async fn del(&self, key: &str) -> Result<()> {
281        let key = self.prefixed(key);
282        let removed = self.data.write().remove(&key).is_some();
283        if removed { self.stats.deletes.fetch_add(1, Ordering::Relaxed); }
284        Ok(())
285    }
286
287    async fn exists(&self, key: &str) -> Result<bool> {
288        let key = self.prefixed(key);
289        let guard = self.data.read();
290        let found = guard.get(&key).map_or(false, |entry| {
291            entry.expires_at.map_or(true, |exp| Instant::now() <= exp)
292        });
293        if found { self.stats.hits.fetch_add(1, Ordering::Relaxed); }
294        else { self.stats.misses.fetch_add(1, Ordering::Relaxed); }
295        Ok(found)
296    }
297
298    async fn incr(&self, key: &str, delta: i64) -> Result<i64> {
299        let key = self.prefixed(key);
300        let mut guard = self.data.write();
301        let entry = guard.entry(key).or_insert_with(|| CacheEntry {
302            value: serde_json::Value::Number(serde_json::Number::from(0i64)),
303            expires_at: None,
304        });
305        let current = entry.value.as_i64().unwrap_or(0);
306        let new_val = current + delta;
307        entry.value = serde_json::Value::Number(serde_json::Number::from(new_val));
308        Ok(new_val)
309    }
310
311    async fn keys(&self, pattern: &str) -> Result<Vec<String>> {
312        let prefixed_pattern = self.prefixed(pattern);
313        let guard = self.data.read();
314        let prefix = &self.key_prefix;
315        let strip_len = if prefix.is_empty() { 0 } else { prefix.len() + 1 };
316        Ok(guard.keys()
317            .filter(|k| match_pattern(k, &prefixed_pattern))
318            .map(|k| if strip_len > 0 && k.len() > strip_len { k[strip_len..].to_string() } else { k.clone() })
319            .collect())
320    }
321
322    async fn delete_pattern(&self, pattern: &str) -> Result<u64> {
323        let prefixed_pattern = self.prefixed(pattern);
324        let mut guard = self.data.write();
325        let to_remove: Vec<String> = guard.keys()
326            .filter(|k| match_pattern(k, &prefixed_pattern))
327            .cloned()
328            .collect();
329        let count = to_remove.len() as u64;
330        for k in to_remove { guard.remove(&k); }
331        Ok(count)
332    }
333}
334
335// ──── Redis 缓存 ────────────────────────────────────
336
337/// Redis 缓存实现
338#[derive(Clone)]
339pub struct RedisCache {
340    /// 缓存 key 前缀(通常为 app_name,用于多项目隔离)
341    key_prefix: String,
342    /// Redis 连接管理器
343    conn: ConnectionManager,
344}
345
346impl RedisCache {
347    /// 创建 Redis 缓存(需传入已建立的连接管理器)
348    ///
349    /// - `key_prefix`: 缓存 key 前缀(通常为 app_name),为空时不加前缀
350    pub fn new(key_prefix: &str, conn: ConnectionManager) -> Self {
351        Self { key_prefix: key_prefix.to_string(), conn }
352    }
353
354    /// 从 URL 创建连接
355    ///
356    /// - `key_prefix`: 缓存 key 前缀
357    pub async fn connect(key_prefix: &str, url: &str) -> Result<Self> {
358        let client = redis::Client::open(url)
359            .map_err(|e| alun_core::Error::Config(format!("Redis URL 无效: {}", e)))?;
360        let conn = ConnectionManager::new(client).await
361            .map_err(|e| alun_core::Error::Config(format!("Redis 连接失败: {}", e)))?;
362        Ok(Self { key_prefix: key_prefix.to_string(), conn })
363    }
364
365    /// 生成带前缀的完整缓存 key
366    ///
367    /// 若 `key_prefix` 非空,返回 `"{prefix}:{key}"`,否则返回原始 key。
368    #[inline]
369    fn prefixed(&self, key: &str) -> String {
370        if self.key_prefix.is_empty() {
371            key.to_string()
372        } else {
373            format!("{}:{}", self.key_prefix, key)
374        }
375    }
376
377    fn map_err(e: redis::RedisError) -> alun_core::Error {
378        alun_core::Error::Msg(e.to_string())
379    }
380}
381
382#[async_trait]
383impl Cache for RedisCache {
384    async fn get<T: DeserializeOwned + Send>(&self, key: &str) -> Result<Option<T>> {
385        let key = self.prefixed(key);
386        let result: Option<String> = redis::cmd("GET")
387            .arg(&key)
388            .query_async(&mut self.conn.clone())
389            .await
390            .map_err(Self::map_err)?;
391
392        if let Some(json) = result {
393            let val: T = serde_json::from_str(&json)
394                .map_err(|e| alun_core::Error::Msg(e.to_string()))?;
395            Ok(Some(val))
396        } else {
397            Ok(None)
398        }
399    }
400
401    async fn set<T: Serialize + Send + Sync>(&self, key: &str, value: &T) -> Result<()> {
402        let key = self.prefixed(key);
403        let json = serde_json::to_string(value)
404            .map_err(|e| alun_core::Error::Msg(e.to_string()))?;
405        redis::cmd("SET")
406            .arg(&key).arg(&json)
407            .query_async::<()>(&mut self.conn.clone())
408            .await
409            .map_err(Self::map_err)
410    }
411
412    async fn set_ex<T: Serialize + Send + Sync>(&self, key: &str, value: &T, ttl_secs: u64) -> Result<()> {
413        let key = self.prefixed(key);
414        let json = serde_json::to_string(value)
415            .map_err(|e| alun_core::Error::Msg(e.to_string()))?;
416        redis::cmd("SETEX")
417            .arg(&key).arg(ttl_secs).arg(&json)
418            .query_async::<()>(&mut self.conn.clone())
419            .await
420            .map_err(Self::map_err)
421    }
422
423    async fn del(&self, key: &str) -> Result<()> {
424        let key = self.prefixed(key);
425        redis::cmd("DEL")
426            .arg(&key)
427            .query_async::<()>(&mut self.conn.clone())
428            .await
429            .map_err(Self::map_err)
430    }
431
432    async fn exists(&self, key: &str) -> Result<bool> {
433        let key = self.prefixed(key);
434        redis::cmd("EXISTS")
435            .arg(&key)
436            .query_async::<i32>(&mut self.conn.clone())
437            .await
438            .map_err(Self::map_err)
439            .map(|v| v > 0)
440    }
441
442    async fn incr(&self, key: &str, delta: i64) -> Result<i64> {
443        let key = self.prefixed(key);
444        let result: i64 = if delta == 1 {
445            redis::cmd("INCR")
446                .arg(&key)
447                .query_async(&mut self.conn.clone())
448                .await
449                .map_err(Self::map_err)?
450        } else {
451            redis::cmd("INCRBY")
452                .arg(&key).arg(delta)
453                .query_async(&mut self.conn.clone())
454                .await
455                .map_err(Self::map_err)?
456        };
457        Ok(result)
458    }
459
460    async fn keys(&self, pattern: &str) -> Result<Vec<String>> {
461        let prefixed_pattern = self.prefixed(pattern);
462        let prefix = &self.key_prefix;
463        let strip_len = if prefix.is_empty() { 0 } else { prefix.len() + 1 };
464        redis::cmd("KEYS")
465            .arg(&prefixed_pattern)
466            .query_async::<Vec<String>>(&mut self.conn.clone())
467            .await
468            .map_err(Self::map_err)
469            .map(|keys| {
470                keys.into_iter()
471                    .map(|k| if strip_len > 0 && k.len() > strip_len { k[strip_len..].to_string() } else { k })
472                    .collect()
473            })
474    }
475
476    async fn delete_pattern(&self, pattern: &str) -> Result<u64> {
477        let keys: Vec<String> = self.keys(pattern).await?;
478        if keys.is_empty() { return Ok(0); }
479        let mut cmd = redis::cmd("DEL");
480        for k in &keys {
481            cmd.arg(self.prefixed(k));
482        }
483        cmd.query_async::<u64>(&mut self.conn.clone())
484            .await
485            .map_err(Self::map_err)
486    }
487}
488
489// ──── 模式匹配 ──────────────────────────────────────
490
491fn match_pattern(key: &str, pattern: &str) -> bool {
492    if pattern.is_empty() { return key.is_empty(); }
493    match_pattern_rec(key.as_bytes(), 0, pattern.as_bytes(), 0)
494}
495
496fn match_pattern_rec(key: &[u8], ki: usize, pat: &[u8], pi: usize) -> bool {
497    if ki >= key.len() && pi >= pat.len() { return true; }
498    if pi >= pat.len() { return false; }
499    match pat[pi] {
500        b'*' => {
501            if pi + 1 >= pat.len() { return true; }
502            for nk in ki..=key.len() {
503                if match_pattern_rec(key, nk, pat, pi + 1) { return true; }
504            }
505            false
506        }
507        b'?' => {
508            ki < key.len() && match_pattern_rec(key, ki + 1, pat, pi + 1)
509        }
510        c => {
511            ki < key.len() && key[ki] == c && match_pattern_rec(key, ki + 1, pat, pi + 1)
512        }
513    }
514}
515
516// ──── 共享缓存(枚举消除 dyn 不兼容) ────────────────
517
518/// 共享缓存——枚举包装所有缓存实现,避免 `dyn Cache` 的对象安全问题
519#[derive(Clone)]
520pub enum SharedCache {
521    Local(LocalCache),
522    Redis(RedisCache),
523}
524
525#[async_trait]
526impl Cache for SharedCache {
527    async fn get<T: DeserializeOwned + Send>(&self, key: &str) -> Result<Option<T>> {
528        match self {
529            SharedCache::Local(c) => c.get(key).await,
530            SharedCache::Redis(c) => c.get(key).await,
531        }
532    }
533
534    async fn set<T: Serialize + Send + Sync>(&self, key: &str, value: &T) -> Result<()> {
535        match self {
536            SharedCache::Local(c) => c.set(key, value).await,
537            SharedCache::Redis(c) => c.set(key, value).await,
538        }
539    }
540
541    async fn set_ex<T: Serialize + Send + Sync>(&self, key: &str, value: &T, ttl_secs: u64) -> Result<()> {
542        match self {
543            SharedCache::Local(c) => c.set_ex(key, value, ttl_secs).await,
544            SharedCache::Redis(c) => c.set_ex(key, value, ttl_secs).await,
545        }
546    }
547
548    async fn del(&self, key: &str) -> Result<()> {
549        match self {
550            SharedCache::Local(c) => c.del(key).await,
551            SharedCache::Redis(c) => c.del(key).await,
552        }
553    }
554
555    async fn exists(&self, key: &str) -> Result<bool> {
556        match self { SharedCache::Local(c) => c.exists(key).await, SharedCache::Redis(c) => c.exists(key).await }
557    }
558
559    async fn incr(&self, key: &str, delta: i64) -> Result<i64> {
560        match self { SharedCache::Local(c) => c.incr(key, delta).await, SharedCache::Redis(c) => c.incr(key, delta).await }
561    }
562
563    async fn keys(&self, pattern: &str) -> Result<Vec<String>> {
564        match self { SharedCache::Local(c) => c.keys(pattern).await, SharedCache::Redis(c) => c.keys(pattern).await }
565    }
566
567    async fn delete_pattern(&self, pattern: &str) -> Result<u64> {
568        match self { SharedCache::Local(c) => c.delete_pattern(pattern).await, SharedCache::Redis(c) => c.delete_pattern(pattern).await }
569    }
570}
571
572// ──── 工厂函数 ──────────────────────────────────────
573
574/// 从配置创建共享缓存实例
575///
576/// `app_name` 将作为所有缓存 key 的前缀(`"{app_name}:{key}"`),
577/// 确保多项目共享同一 Redis 时 key 不冲突。
578/// 若 `app_name` 为空则不加前缀。
579pub async fn create_cache(app_name: &str, cache_config: &alun_config::CacheConfig, redis_config: &alun_config::RedisConfig) -> Result<SharedCache> {
580    match cache_config.r#type.as_str() {
581        "redis" => {
582            tracing::info!("使用 Redis 缓存 url={} app={}", redis_config.url, app_name);
583            Ok(SharedCache::Redis(RedisCache::connect(app_name, &redis_config.url).await?))
584        }
585        _ => {
586            tracing::info!("使用本地缓存 capacity={} app={}", cache_config.max_capacity, app_name);
587            Ok(SharedCache::Local(LocalCache::new(app_name, cache_config.max_capacity, cache_config.default_ttl)))
588        }
589    }
590}
591
592#[cfg(test)]
593mod tests {
594    use super::*;
595
596    #[tokio::test]
597    async fn test_local_cache_get_set() {
598        let c = LocalCache::new("test",100, 0);
599        c.set("key1", &"value1".to_string()).await.unwrap();
600        let val: Option<String> = c.get("key1").await.unwrap();
601        assert_eq!(val, Some("value1".to_string()));
602        c.del("key1").await.unwrap();
603        let val: Option<String> = c.get("key1").await.unwrap();
604        assert_eq!(val, None);
605    }
606
607    #[tokio::test]
608    async fn test_set_ex_expiration() {
609        let c = LocalCache::new("test",100, 0);
610        c.set_ex("temp", &"expire".to_string(), 1).await.unwrap();
611        let val: Option<String> = c.get("temp").await.unwrap();
612        assert_eq!(val, Some("expire".to_string()));
613        tokio::time::sleep(Duration::from_secs(2)).await;
614        let val: Option<String> = c.get("temp").await.unwrap();
615        assert_eq!(val, None);
616    }
617
618    #[tokio::test]
619    async fn test_incr() {
620        let c = LocalCache::new("test",100, 0);
621        assert_eq!(c.incr("counter", 1).await.unwrap(), 1);
622        assert_eq!(c.incr("counter", 5).await.unwrap(), 6);
623        assert_eq!(c.incr("counter", -2).await.unwrap(), 4);
624    }
625
626    #[tokio::test]
627    async fn test_keys_pattern() {
628        let c = LocalCache::new("test",100, 0);
629        c.set("user:1", &"alice").await.unwrap();
630        c.set("user:2", &"bob").await.unwrap();
631        c.set("order:1", &"o1").await.unwrap();
632        let keys = c.keys("user:*").await.unwrap();
633        assert_eq!(keys.len(), 2);
634        assert!(keys.contains(&"user:1".to_string()));
635        assert!(keys.contains(&"user:2".to_string()));
636    }
637
638    #[tokio::test]
639    async fn test_delete_pattern() {
640        let c = LocalCache::new("test",100, 0);
641        c.set("session:a", &"s1").await.unwrap();
642        c.set("session:b", &"s2").await.unwrap();
643        c.set("user:1", &"alice").await.unwrap();
644        let deleted = c.delete_pattern("session:*").await.unwrap();
645        assert_eq!(deleted, 2);
646        assert!(!c.exists("session:a").await.unwrap());
647        assert!(!c.exists("session:b").await.unwrap());
648        assert!(c.exists("user:1").await.unwrap());
649    }
650}