use std::collections::HashMap;
use std::sync::{Arc, OnceLock};
use std::time::{Duration, Instant};
use parking_lot::{Mutex, RwLock};
use serde::de::DeserializeOwned;
use serde::Serialize;
use sz_rust_orm_facade::{Cache as InnerCache, CacheError, MemoryCache};
mod memcached;
pub use memcached::{
MemcachedBackend, MemcachedCacheDriver, MemcachedConfig, MockMemcachedBackend,
};
static GLOBAL_CACHE: OnceLock<Cache> = OnceLock::new();
pub fn default_cache() -> &'static Cache {
GLOBAL_CACHE.get_or_init(Cache::new)
}
pub fn init_default_cache(driver: MemoryCacheDriver) {
let cache = default_cache();
cache.register_default(driver);
}
#[derive(Debug, Clone, PartialEq)]
pub enum CacheValue {
Number(String),
Json(String),
}
impl CacheValue {
pub fn to_bytes(&self) -> Vec<u8> {
match self {
CacheValue::Number(s) => s.as_bytes().to_vec(),
CacheValue::Json(s) => s.as_bytes().to_vec(),
}
}
pub fn from_bytes(bytes: &[u8]) -> Result<CacheValue, CacheError> {
let s = std::str::from_utf8(bytes)
.map_err(|e| CacheError::DeserializationError(e.to_string()))?;
if php_is_numeric(s) {
Ok(CacheValue::Number(s.to_string()))
} else {
Ok(CacheValue::Json(s.to_string()))
}
}
pub fn is_number(&self) -> bool {
matches!(self, CacheValue::Number(_))
}
}
pub fn php_is_numeric(s: &str) -> bool {
if s.is_empty() {
return false;
}
if s.parse::<i64>().is_ok() {
return true;
}
if s.parse::<f64>().is_ok() {
return true;
}
false
}
pub fn php_serialize<T: Serialize>(value: &T) -> Result<CacheValue, CacheError> {
let json =
serde_json::to_string(value).map_err(|e| CacheError::SerializationError(e.to_string()))?;
if php_is_numeric(&json) {
Ok(CacheValue::Number(json))
} else {
Ok(CacheValue::Json(json))
}
}
pub fn php_unserialize<T: DeserializeOwned>(value: &CacheValue) -> Result<Option<T>, CacheError> {
match value {
CacheValue::Number(s) => {
serde_json::from_str::<T>(&format!("\"{}\"", s))
.map(Some)
.map_err(|e| CacheError::DeserializationError(e.to_string()))
}
CacheValue::Json(s) => serde_json::from_str::<T>(s)
.map(Some)
.map_err(|e| CacheError::DeserializationError(e.to_string())),
}
}
pub trait CacheDriver: Send + Sync {
fn get_raw(&self, key: &str) -> Result<Option<Vec<u8>>, CacheError>;
fn set_raw(&self, key: &str, value: Vec<u8>, ttl: Option<Duration>) -> Result<(), CacheError>;
fn delete(&self, key: &str) -> Result<(), CacheError>;
fn has(&self, key: &str) -> Result<bool, CacheError>;
fn inc(&self, key: &str, step: i64) -> Result<i64, CacheError> {
let current = match self.get_raw(key)? {
Some(bytes) => {
let s = std::str::from_utf8(&bytes)
.map_err(|e| CacheError::DeserializationError(e.to_string()))?;
s.parse::<i64>().map_err(|e| {
CacheError::DeserializationError(format!("inc: parse {} failed: {}", s, e))
})?
}
None => 0,
};
let new_value = current + step;
self.set_raw(key, new_value.to_string().into_bytes(), None)?;
Ok(new_value)
}
fn dec(&self, key: &str, step: i64) -> Result<i64, CacheError> {
self.inc(key, -step)
}
fn clear(&self) -> Result<(), CacheError>;
fn get_cache_key(&self, name: &str) -> String {
name.to_string()
}
fn get_tag_key(&self, tag: &str) -> String {
format!("tag:{}", compute_md5(tag))
}
fn tag_append(&self, tag_key: &str, cache_key: &str) -> Result<(), CacheError> {
let storage_key = self.get_cache_key(tag_key);
let mut items: Vec<String> = match self.get_raw(&storage_key)? {
Some(bytes) => serde_json::from_slice(&bytes)
.map_err(|e| CacheError::DeserializationError(format!("tag_append: {}", e)))?,
None => Vec::new(),
};
items.push(cache_key.to_string());
while items.len() > 1000 {
items.remove(0);
}
let mut seen = HashSet::new();
items.retain(|item| seen.insert(item.clone()));
let serialized = serde_json::to_vec(&items)
.map_err(|e| CacheError::SerializationError(e.to_string()))?;
self.set_raw(&storage_key, serialized, None)
}
fn tag_items(&self, tag: &str) -> Result<Vec<String>, CacheError> {
let tag_key = self.get_tag_key(tag);
let storage_key = self.get_cache_key(&tag_key);
match self.get_raw(&storage_key)? {
Some(bytes) => {
let items: Vec<String> = serde_json::from_slice(&bytes)
.map_err(|e| CacheError::DeserializationError(format!("tag_items: {}", e)))?;
Ok(items)
}
None => Ok(Vec::new()),
}
}
fn tag_clear(&self, keys: &[String]) -> Result<(), CacheError> {
for key in keys {
let _ = self.delete(key);
}
Ok(())
}
}
pub struct MemoryCacheDriver {
inner: MemoryCache,
}
impl MemoryCacheDriver {
pub fn new() -> Self {
Self {
inner: MemoryCache::new(),
}
}
pub fn with_default_ttl(ttl: Duration) -> Self {
Self {
inner: MemoryCache::with_ttl(ttl),
}
}
}
impl Default for MemoryCacheDriver {
fn default() -> Self {
Self::new()
}
}
impl CacheDriver for MemoryCacheDriver {
fn get_raw(&self, key: &str) -> Result<Option<Vec<u8>>, CacheError> {
InnerCache::get(&self.inner, key)
}
fn set_raw(&self, key: &str, value: Vec<u8>, ttl: Option<Duration>) -> Result<(), CacheError> {
InnerCache::set(&self.inner, key, value, ttl)
}
fn delete(&self, key: &str) -> Result<(), CacheError> {
InnerCache::delete(&self.inner, key)
}
fn has(&self, key: &str) -> Result<bool, CacheError> {
InnerCache::exists(&self.inner, key)
}
fn clear(&self) -> Result<(), CacheError> {
InnerCache::clear(&self.inner)
}
}
pub struct CacheManager {
default: String,
stores: HashMap<String, Box<dyn CacheDriver>>,
}
impl CacheManager {
pub fn new() -> Self {
Self {
default: String::new(),
stores: HashMap::new(),
}
}
pub fn register_store(&mut self, name: impl Into<String>, driver: Box<dyn CacheDriver>) {
let name = name.into();
if self.default.is_empty() {
self.default = name.clone();
}
self.stores.insert(name, driver);
}
pub fn set_default(&mut self, name: impl Into<String>) -> Result<(), CacheError> {
let name = name.into();
if !self.stores.contains_key(&name) {
return Err(CacheError::NotFound(format!(
"cache store '{}' not registered",
name
)));
}
self.default = name;
Ok(())
}
pub fn store(&self, name: &str) -> Result<&dyn CacheDriver, CacheError> {
self.stores
.get(name)
.map(|d| d.as_ref())
.ok_or_else(|| CacheError::NotFound(format!("cache store '{}' not found", name)))
}
pub fn default_store(&self) -> Result<&dyn CacheDriver, CacheError> {
if self.default.is_empty() {
return Err(CacheError::NotFound(
"no default cache store registered".to_string(),
));
}
self.store(&self.default)
}
}
impl Default for CacheManager {
fn default() -> Self {
Self::new()
}
}
pub struct Cache {
manager: RwLock<CacheManager>,
remember_lock_poll_interval: Duration,
remember_lock_timeout: Duration,
inflight: Mutex<HashMap<String, Arc<Mutex<()>>>>,
}
impl Cache {
pub fn new() -> Self {
Self {
manager: RwLock::new(CacheManager::new()),
remember_lock_poll_interval: Duration::from_millis(200),
remember_lock_timeout: Duration::from_secs(5),
inflight: Mutex::new(HashMap::new()),
}
}
pub fn register_default(&self, driver: MemoryCacheDriver) {
let mut mgr = self.manager.write();
mgr.register_store("default", Box::new(driver));
}
pub fn register_store(&self, name: impl Into<String>, driver: Box<dyn CacheDriver>) {
let mut mgr = self.manager.write();
mgr.register_store(name, driver);
}
pub fn set_default_store(&self, name: impl Into<String>) -> Result<(), CacheError> {
let mut mgr = self.manager.write();
mgr.set_default(name)
}
#[tracing::instrument(skip(self, value))]
pub fn set<T: Serialize>(
&self,
key: &str,
value: T,
ttl: Option<Duration>,
) -> Result<(), CacheError> {
let cache_value = php_serialize(&value)?;
let bytes = cache_value.to_bytes();
let mgr = self.manager.read();
let driver = mgr.default_store()?;
driver.set_raw(key, bytes, ttl)
}
#[tracing::instrument(skip(self))]
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, CacheError> {
let mgr = self.manager.read();
let driver = mgr.default_store()?;
match driver.get_raw(key)? {
None => Ok(None),
Some(bytes) => {
let cache_value = CacheValue::from_bytes(&bytes)?;
php_unserialize(&cache_value)
}
}
}
pub fn get_or<T: DeserializeOwned>(&self, key: &str, default: T) -> Result<T, CacheError> {
match self.get::<T>(key)? {
Some(v) => Ok(v),
None => Ok(default),
}
}
fn get_weak<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, CacheError> {
match self.get::<T>(key) {
Ok(v) => Ok(v),
Err(CacheError::DeserializationError(_)) => {
match self.get::<String>(key) {
Ok(Some(s)) => serde_json::from_str::<T>(&s)
.map(Some)
.map_err(|e| CacheError::DeserializationError(e.to_string())),
Ok(None) => Ok(None),
Err(e) => Err(e),
}
}
Err(e) => Err(e),
}
}
#[tracing::instrument(skip(self))]
pub fn delete(&self, key: &str) -> Result<(), CacheError> {
let mgr = self.manager.read();
let driver = mgr.default_store()?;
driver.delete(key)
}
pub fn has(&self, key: &str) -> Result<bool, CacheError> {
let mgr = self.manager.read();
let driver = mgr.default_store()?;
driver.has(key)
}
pub fn inc(&self, key: &str, step: i64) -> Result<i64, CacheError> {
let mgr = self.manager.read();
let driver = mgr.default_store()?;
driver.inc(key, step)
}
pub fn dec(&self, key: &str, step: i64) -> Result<i64, CacheError> {
let mgr = self.manager.read();
let driver = mgr.default_store()?;
driver.dec(key, step)
}
pub fn increment(&self, key: &str) -> Result<i64, CacheError> {
self.inc(key, 1)
}
pub fn decrement(&self, key: &str) -> Result<i64, CacheError> {
self.dec(key, 1)
}
pub fn pull<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, CacheError> {
let value = self.get::<T>(key)?;
if value.is_some() {
self.delete(key)?;
}
Ok(value)
}
pub fn push<T: Serialize + DeserializeOwned + PartialEq + Clone>(
&self,
key: &str,
value: T,
ttl: Option<Duration>,
) -> Result<(), CacheError> {
let mut data: Vec<T> = match self.get::<Vec<T>>(key) {
Ok(Some(v)) => v,
Ok(None) => Vec::new(),
Err(_) => Vec::new(),
};
data.push(value);
while data.len() > 1000 {
data.remove(0);
}
let mut seen = std::collections::HashSet::new();
data.retain(|item| seen.insert(item.hashable_string()));
self.set(key, data, ttl)
}
#[tracing::instrument(skip(self, callback))]
pub async fn remember<T, F>(
&self,
key: &str,
ttl: Option<Duration>,
callback: F,
) -> Result<T, CacheError>
where
T: Serialize + DeserializeOwned,
F: FnOnce() -> T,
{
if let Some(cached) = self.get_weak::<T>(key)? {
return Ok(cached);
}
let lock_key = format!("{}_lock", key);
if self.has(&lock_key)? {
let start = Instant::now();
while self.has(&lock_key)? {
if start.elapsed() >= self.remember_lock_timeout {
return Ok(callback());
}
tokio::time::sleep(self.remember_lock_poll_interval).await;
}
if let Some(cached) = self.get_weak::<T>(key)? {
return Ok(cached);
}
}
self.set(&lock_key, 1i64, None)?;
let result = callback();
let _ = self.set(key, &result, ttl);
let _ = self.delete(&lock_key);
Ok(result)
}
pub async fn remember_async<T, F, Fut>(
&self,
key: &str,
ttl: Option<Duration>,
callback: F,
) -> Result<T, CacheError>
where
T: Serialize + DeserializeOwned + Clone,
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = T>,
{
if let Some(cached) = self.get_weak::<T>(key)? {
return Ok(cached);
}
let lock_key = format!("{}_lock", key);
if self.has(&lock_key)? {
let start = Instant::now();
while self.has(&lock_key)? {
if start.elapsed() >= self.remember_lock_timeout {
let result = callback().await;
let _ = self.set(key, &result, ttl);
return Ok(result);
}
tokio::time::sleep(self.remember_lock_poll_interval).await;
}
if let Some(cached) = self.get_weak::<T>(key)? {
return Ok(cached);
}
}
self.set(&lock_key, 1i64, None)?;
let result = callback().await;
let _ = self.set(key, &result, ttl);
let _ = self.delete(&lock_key);
Ok(result)
}
#[tracing::instrument(skip(self))]
pub fn clear(&self) -> Result<(), CacheError> {
let mgr = self.manager.read();
let driver = mgr.default_store()?;
driver.clear()
}
pub fn delete_many(&self, keys: &[&str]) -> Result<(), CacheError> {
let mgr = self.manager.read();
let driver = mgr.default_store()?;
for key in keys {
driver.delete(key)?;
}
Ok(())
}
pub fn invalidate_after_write(&self, keys: &[&str]) -> Result<(), CacheError> {
self.delete_many(keys)
}
pub fn refresh<T, F>(
&self,
key: &str,
ttl: Option<Duration>,
fetcher: F,
) -> Result<T, CacheError>
where
T: Serialize + DeserializeOwned,
F: FnOnce() -> Result<T, CacheError>,
{
self.delete(key)?;
let value = fetcher()?;
self.set(key, &value, ttl)?;
Ok(value)
}
#[tracing::instrument(skip(self, fetcher))]
pub fn fetch_singleflight<T, F>(
&self,
key: &str,
ttl: Option<Duration>,
fetcher: F,
) -> Result<T, CacheError>
where
T: Serialize + DeserializeOwned,
F: FnOnce() -> Result<T, CacheError>,
{
if let Some(cached) = self.get::<T>(key)? {
return Ok(cached);
}
let mutex = {
let mut inflight = self.inflight.lock();
inflight
.entry(key.to_string())
.or_insert_with(|| Arc::new(Mutex::new(())))
.clone()
};
let _guard = mutex.lock();
if let Some(cached) = self.get::<T>(key)? {
return Ok(cached);
}
let value = fetcher()?;
self.set(key, &value, ttl)?;
Ok(value)
}
pub fn set_with_jitter<T>(
&self,
key: &str,
value: &T,
ttl: Option<Duration>,
jitter: Duration,
) -> Result<(), CacheError>
where
T: Serialize + ?Sized,
{
let actual_ttl = match ttl {
Some(t) if !jitter.is_zero() => {
use rand::Rng;
let jitter_nanos = jitter.as_nanos() as u64;
let random_jitter =
Duration::from_nanos(rand::thread_rng().gen_range(0..jitter_nanos));
Some(t + random_jitter)
}
Some(t) => Some(t),
None => None,
};
self.set(key, value, actual_ttl)
}
#[tracing::instrument(skip(self, fetcher))]
pub fn fetch_with_protection<T, F>(
&self,
key: &str,
ttl: Option<Duration>,
jitter: Duration,
fetcher: F,
) -> Result<T, CacheError>
where
T: Serialize + DeserializeOwned,
F: FnOnce() -> Result<T, CacheError>,
{
if let Some(cached) = self.get::<T>(key)? {
return Ok(cached);
}
let mutex = {
let mut inflight = self.inflight.lock();
inflight
.entry(key.to_string())
.or_insert_with(|| Arc::new(Mutex::new(())))
.clone()
};
let _guard = mutex.lock();
if let Some(cached) = self.get::<T>(key)? {
return Ok(cached);
}
let value = fetcher()?;
self.set_with_jitter(key, &value, ttl, jitter)?;
Ok(value)
}
pub fn with_store<R, F>(&self, name: &str, f: F) -> Result<R, CacheError>
where
F: FnOnce(&dyn CacheDriver) -> Result<R, CacheError>,
{
let mgr = self.manager.read();
let driver = mgr.store(name)?;
f(driver)
}
pub fn tag(&self, name: &str) -> TagSet<'_> {
TagSet {
tags: vec![name.to_string()],
cache: self,
}
}
pub fn tag_many(&self, names: &[&str]) -> TagSet<'_> {
TagSet {
tags: names.iter().map(|s| s.to_string()).collect(),
cache: self,
}
}
}
impl Default for Cache {
fn default() -> Self {
Self::new()
}
}
trait CloneHashable: Clone {
fn hashable_string(&self) -> String;
}
impl<T> CloneHashable for T
where
T: Serialize + Clone,
{
fn hashable_string(&self) -> String {
serde_json::to_string(self).unwrap_or_default()
}
}
use md5::{Digest, Md5};
use std::collections::HashSet;
#[derive(Debug, Clone)]
pub struct RedisConfig {
pub host: String,
pub port: u16,
pub password: String,
pub select: u32,
pub timeout: Duration,
pub expire: Option<Duration>,
pub persistent: bool,
pub prefix: String,
pub tag_prefix: String,
}
impl Default for RedisConfig {
fn default() -> Self {
Self {
host: "127.0.0.1".to_string(),
port: 6379,
password: String::new(),
select: 0,
timeout: Duration::ZERO,
expire: None,
persistent: false,
prefix: String::new(),
tag_prefix: "tag:".to_string(),
}
}
}
impl RedisConfig {
pub fn with_prefix(prefix: impl Into<String>) -> Self {
Self {
prefix: prefix.into(),
..Default::default()
}
}
pub fn with_expire(expire: Duration) -> Self {
Self {
expire: Some(expire),
..Default::default()
}
}
}
pub trait RedisBackend: Send + Sync {
fn get(&self, key: &str) -> Result<Option<Vec<u8>>, CacheError>;
fn set(&self, key: &str, value: Vec<u8>) -> Result<(), CacheError>;
fn set_ex(&self, key: &str, value: Vec<u8>, ttl: Duration) -> Result<(), CacheError>;
fn del(&self, key: &str) -> Result<i64, CacheError>;
fn del_many(&self, keys: &[&str]) -> Result<i64, CacheError>;
fn exists(&self, key: &str) -> Result<bool, CacheError>;
fn incr_by(&self, key: &str, step: i64) -> Result<i64, CacheError>;
fn decr_by(&self, key: &str, step: i64) -> Result<i64, CacheError>;
fn flush_db(&self) -> Result<(), CacheError>;
fn sadd(&self, key: &str, member: &str) -> Result<i64, CacheError>;
fn smembers(&self, key: &str) -> Result<Vec<String>, CacheError>;
}
pub struct MockRedisBackend {
kv: parking_lot::RwLock<MockRedisKv>,
sets: parking_lot::RwLock<MockRedisSets>,
}
type MockRedisKv = HashMap<String, (Vec<u8>, Option<Instant>)>;
type MockRedisSets = HashMap<String, HashSet<String>>;
impl Default for MockRedisBackend {
fn default() -> Self {
Self::new()
}
}
impl MockRedisBackend {
pub fn new() -> Self {
Self {
kv: parking_lot::RwLock::new(HashMap::new()),
sets: parking_lot::RwLock::new(HashMap::new()),
}
}
fn is_expired(kv: &MockRedisKv, key: &str) -> bool {
if let Some((_, Some(expires_at))) = kv.get(key) {
return *expires_at <= Instant::now();
}
false
}
}
impl RedisBackend for MockRedisBackend {
fn get(&self, key: &str) -> Result<Option<Vec<u8>>, CacheError> {
let kv = self.kv.read();
if Self::is_expired(&kv, key) {
return Ok(None);
}
Ok(kv.get(key).map(|(v, _)| v.clone()))
}
fn set(&self, key: &str, value: Vec<u8>) -> Result<(), CacheError> {
let mut kv = self.kv.write();
kv.insert(key.to_string(), (value, None));
Ok(())
}
fn set_ex(&self, key: &str, value: Vec<u8>, ttl: Duration) -> Result<(), CacheError> {
let mut kv = self.kv.write();
let expires_at = Some(Instant::now() + ttl);
kv.insert(key.to_string(), (value, expires_at));
Ok(())
}
fn del(&self, key: &str) -> Result<i64, CacheError> {
let mut kv = self.kv.write();
let removed = kv.remove(key).is_some() as i64;
let mut sets = self.sets.write();
if sets.remove(key).is_some() && removed == 0 {
return Ok(1);
}
Ok(removed)
}
fn del_many(&self, keys: &[&str]) -> Result<i64, CacheError> {
let mut count = 0i64;
for key in keys {
count += self.del(key)?;
}
Ok(count)
}
fn exists(&self, key: &str) -> Result<bool, CacheError> {
let kv = self.kv.read();
if Self::is_expired(&kv, key) {
return Ok(false);
}
if kv.contains_key(key) {
return Ok(true);
}
let sets = self.sets.read();
Ok(sets.contains_key(key))
}
fn incr_by(&self, key: &str, step: i64) -> Result<i64, CacheError> {
let mut kv = self.kv.write();
if Self::is_expired(&kv, key) {
kv.remove(key);
}
let current = match kv.get(key) {
Some((bytes, _)) => {
let s = std::str::from_utf8(bytes)
.map_err(|e| CacheError::DeserializationError(e.to_string()))?;
s.parse::<i64>().map_err(|e| {
CacheError::Internal(format!("INCRBY failed: '{}' is not an integer: {}", s, e))
})?
}
None => 0, };
let new_value = current + step;
kv.insert(key.to_string(), (new_value.to_string().into_bytes(), None));
Ok(new_value)
}
fn decr_by(&self, key: &str, step: i64) -> Result<i64, CacheError> {
self.incr_by(key, -step)
}
fn flush_db(&self) -> Result<(), CacheError> {
let mut kv = self.kv.write();
kv.clear();
let mut sets = self.sets.write();
sets.clear();
Ok(())
}
fn sadd(&self, key: &str, member: &str) -> Result<i64, CacheError> {
let mut sets = self.sets.write();
let set = sets.entry(key.to_string()).or_default();
let added = set.insert(member.to_string()) as i64;
Ok(added)
}
fn smembers(&self, key: &str) -> Result<Vec<String>, CacheError> {
let sets = self.sets.read();
Ok(sets
.get(key)
.map(|s| s.iter().cloned().collect())
.unwrap_or_default())
}
}
pub struct RedisCacheDriver {
backend: Box<dyn RedisBackend>,
config: RedisConfig,
}
impl RedisCacheDriver {
pub fn new(config: RedisConfig) -> Self {
Self::with_backend(config, Box::new(MockRedisBackend::new()))
}
pub fn with_backend(config: RedisConfig, backend: Box<dyn RedisBackend>) -> Self {
Self { backend, config }
}
pub fn config(&self) -> &RedisConfig {
&self.config
}
pub fn backend(&self) -> &dyn RedisBackend {
self.backend.as_ref()
}
pub fn append(&self, name: &str, value: &str) -> Result<(), CacheError> {
let key = self.get_cache_key(name);
self.backend.sadd(&key, value)?;
Ok(())
}
pub fn get_tag_items(&self, tag: &str) -> Result<Vec<String>, CacheError> {
let name = self.get_tag_key(tag);
let key = self.get_cache_key(&name);
self.backend.smembers(&key)
}
pub fn clear_tag(&self, keys: &[&str]) -> Result<(), CacheError> {
self.backend.del_many(keys)?;
Ok(())
}
pub fn tag_key(&self, tag: &str) -> String {
self.get_tag_key(tag)
}
pub fn cache_key(&self, name: &str) -> String {
self.get_cache_key(name)
}
}
impl CacheDriver for RedisCacheDriver {
fn get_raw(&self, key: &str) -> Result<Option<Vec<u8>>, CacheError> {
let cache_key = self.get_cache_key(key);
self.backend.get(&cache_key)
}
fn set_raw(&self, key: &str, value: Vec<u8>, ttl: Option<Duration>) -> Result<(), CacheError> {
let cache_key = self.get_cache_key(key);
let effective_ttl = ttl.or(self.config.expire);
match effective_ttl {
Some(t) if t > Duration::ZERO => self.backend.set_ex(&cache_key, value, t),
_ => self.backend.set(&cache_key, value),
}
}
fn delete(&self, key: &str) -> Result<(), CacheError> {
let cache_key = self.get_cache_key(key);
self.backend.del(&cache_key)?;
Ok(())
}
fn has(&self, key: &str) -> Result<bool, CacheError> {
let cache_key = self.get_cache_key(key);
self.backend.exists(&cache_key)
}
fn inc(&self, key: &str, step: i64) -> Result<i64, CacheError> {
let cache_key = self.get_cache_key(key);
self.backend.incr_by(&cache_key, step)
}
fn dec(&self, key: &str, step: i64) -> Result<i64, CacheError> {
let cache_key = self.get_cache_key(key);
self.backend.decr_by(&cache_key, step)
}
fn clear(&self) -> Result<(), CacheError> {
self.backend.flush_db()
}
fn get_cache_key(&self, name: &str) -> String {
format!("{}{}", self.config.prefix, name)
}
fn get_tag_key(&self, tag: &str) -> String {
let md5_hex = compute_md5(tag);
format!("{}{}", self.config.tag_prefix, md5_hex)
}
fn tag_append(&self, tag_key: &str, cache_key: &str) -> Result<(), CacheError> {
let key = self.get_cache_key(tag_key);
self.backend.sadd(&key, cache_key)?;
Ok(())
}
fn tag_items(&self, tag: &str) -> Result<Vec<String>, CacheError> {
let name = self.get_tag_key(tag);
let key = self.get_cache_key(&name);
self.backend.smembers(&key)
}
fn tag_clear(&self, keys: &[String]) -> Result<(), CacheError> {
let key_refs: Vec<&str> = keys.iter().map(|s| s.as_str()).collect();
self.backend.del_many(&key_refs)?;
Ok(())
}
}
pub(crate) fn compute_md5(s: &str) -> String {
let mut hasher = Md5::new();
hasher.update(s.as_bytes());
let result = hasher.finalize();
hex::encode(result)
}
pub struct MultiLevelCacheDriver {
inner: sz_rust_orm_facade::MultiLevelCache,
}
impl Default for MultiLevelCacheDriver {
fn default() -> Self {
Self::new()
}
}
impl MultiLevelCacheDriver {
pub fn new() -> Self {
Self {
inner: sz_rust_orm_facade::MultiLevelCache::new(),
}
}
pub fn add_level(mut self, cache: Box<dyn InnerCache>) -> Self {
self.inner = self.inner.add_cache(cache);
self
}
pub fn inner(&self) -> &sz_rust_orm_facade::MultiLevelCache {
&self.inner
}
}
impl CacheDriver for MultiLevelCacheDriver {
fn get_raw(&self, key: &str) -> Result<Option<Vec<u8>>, CacheError> {
self.inner.get(key)
}
fn set_raw(&self, key: &str, value: Vec<u8>, ttl: Option<Duration>) -> Result<(), CacheError> {
self.inner.set(key, value, ttl)
}
fn delete(&self, key: &str) -> Result<(), CacheError> {
self.inner.delete(key)
}
fn has(&self, key: &str) -> Result<bool, CacheError> {
self.inner.exists(key)
}
fn inc(&self, key: &str, step: i64) -> Result<i64, CacheError> {
let current = match self.inner.get(key)? {
Some(bytes) => String::from_utf8(bytes)
.map_err(|e| CacheError::DeserializationError(e.to_string()))?
.parse::<i64>()
.unwrap_or(0),
None => 0,
};
let new_value = current + step;
let new_bytes = new_value.to_string().into_bytes();
let ttl = self.inner.ttl(key).ok().flatten();
self.inner.set(key, new_bytes, ttl)?;
Ok(new_value)
}
fn dec(&self, key: &str, step: i64) -> Result<i64, CacheError> {
let current = match self.inner.get(key)? {
Some(bytes) => String::from_utf8(bytes)
.map_err(|e| CacheError::DeserializationError(e.to_string()))?
.parse::<i64>()
.unwrap_or(0),
None => 0,
};
let new_value = current - step;
let new_bytes = new_value.to_string().into_bytes();
let ttl = self.inner.ttl(key).ok().flatten();
self.inner.set(key, new_bytes, ttl)?;
Ok(new_value)
}
fn clear(&self) -> Result<(), CacheError> {
self.inner.clear()
}
}
pub struct TagSet<'a> {
tags: Vec<String>,
cache: &'a Cache,
}
impl<'a> TagSet<'a> {
pub fn set<T: Serialize>(
&self,
key: &str,
value: T,
ttl: Option<Duration>,
) -> Result<(), CacheError> {
self.cache.set(key, value, ttl)?;
self.append(key)
}
pub fn append(&self, key: &str) -> Result<(), CacheError> {
let mgr = self.cache.manager.read();
let driver = mgr.default_store()?;
let cache_key = driver.get_cache_key(key);
for tag in &self.tags {
let tag_key = driver.get_tag_key(tag);
driver.tag_append(&tag_key, &cache_key)?;
}
Ok(())
}
pub fn clear(&self) -> Result<(), CacheError> {
let mgr = self.cache.manager.read();
let driver = mgr.default_store()?;
for tag in &self.tags {
let items = driver.tag_items(tag)?;
driver.tag_clear(&items)?;
let tag_key = driver.get_tag_key(tag);
driver.delete(&tag_key)?;
}
Ok(())
}
pub fn tags(&self) -> &[String] {
&self.tags
}
}
#[cfg(test)]
mod tests {
use crate::*;
use serde::Deserialize;
use std::sync::Barrier;
fn make_cache() -> Cache {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache
}
#[test]
fn test_php_is_numeric_integer() {
assert!(php_is_numeric("42"));
assert!(php_is_numeric("-42"));
assert!(php_is_numeric("+42"));
assert!(php_is_numeric("0"));
}
#[test]
fn test_php_is_numeric_float() {
assert!(php_is_numeric("3.14"));
assert!(php_is_numeric("-3.14"));
assert!(php_is_numeric("+3.14"));
assert!(php_is_numeric("0.0"));
}
#[test]
fn test_php_is_numeric_scientific_notation() {
assert!(php_is_numeric("1e10"));
assert!(php_is_numeric("1.5E-3"));
}
#[test]
fn test_php_is_numeric_non_numeric() {
assert!(!php_is_numeric("abc"));
assert!(!php_is_numeric("12abc"));
assert!(!php_is_numeric(""));
assert!(!php_is_numeric("0x1A")); assert!(!php_is_numeric("null"));
assert!(!php_is_numeric("true"));
}
#[test]
fn test_php_serialize_integer_to_number() {
let v = php_serialize(&42i64).unwrap();
assert!(matches!(v, CacheValue::Number(_)));
if let CacheValue::Number(s) = v {
assert_eq!(s, "42");
}
}
#[test]
fn test_php_serialize_float_to_number() {
let v = php_serialize(&2.5f64).unwrap();
assert!(matches!(v, CacheValue::Number(_)));
if let CacheValue::Number(s) = v {
assert_eq!(s, "2.5");
}
}
#[test]
fn test_php_serialize_string_to_json() {
let v = php_serialize(&"Alice".to_string()).unwrap();
assert!(matches!(v, CacheValue::Json(_)));
if let CacheValue::Json(s) = v {
assert_eq!(s, "\"Alice\"");
}
}
#[test]
fn test_php_serialize_numeric_string_to_number() {
let v = php_serialize(&"42".to_string()).unwrap();
assert!(matches!(v, CacheValue::Json(_))); }
#[test]
fn test_php_serialize_array_to_json() {
let v = php_serialize(&vec![1, 2, 3]).unwrap();
assert!(matches!(v, CacheValue::Json(_)));
if let CacheValue::Json(s) = v {
assert_eq!(s, "[1,2,3]");
}
}
#[test]
fn test_php_unserialize_number_returns_string() {
let v = CacheValue::Number("42".to_string());
let result: Option<String> = php_unserialize(&v).unwrap();
assert_eq!(result, Some("42".to_string()));
}
#[test]
fn test_php_unserialize_json_returns_struct() {
let v = CacheValue::Json("\"Alice\"".to_string());
let result: Option<String> = php_unserialize(&v).unwrap();
assert_eq!(result, Some("Alice".to_string()));
let v = CacheValue::Json("[1,2,3]".to_string());
let result: Option<Vec<i64>> = php_unserialize(&v).unwrap();
assert_eq!(result, Some(vec![1, 2, 3]));
}
#[test]
fn test_php_unserialize_number_to_int_via_parse() {
let v = CacheValue::Number("42".to_string());
let s: String = php_unserialize(&v).unwrap().unwrap();
let n: i64 = s.parse().unwrap();
assert_eq!(n, 42);
}
#[test]
fn test_cache_value_number_roundtrip() {
let v = CacheValue::Number("42".to_string());
let bytes = v.to_bytes();
let restored = CacheValue::from_bytes(&bytes).unwrap();
assert_eq!(v, restored);
}
#[test]
fn test_cache_value_json_roundtrip() {
let v = CacheValue::Json("\"Alice\"".to_string());
let bytes = v.to_bytes();
let restored = CacheValue::from_bytes(&bytes).unwrap();
assert_eq!(v, restored);
}
#[test]
fn test_cache_value_array_roundtrip() {
let v = CacheValue::Json("[1,2,3]".to_string());
let bytes = v.to_bytes();
let restored = CacheValue::from_bytes(&bytes).unwrap();
assert_eq!(v, restored);
}
#[test]
fn test_cache_value_from_bytes_numeric_string_becomes_number() {
let bytes = b"42".to_vec();
let v = CacheValue::from_bytes(&bytes).unwrap();
assert!(matches!(v, CacheValue::Number(_)));
}
#[test]
fn test_cache_value_from_bytes_json_string_becomes_json() {
let bytes = b"\"Alice\"".to_vec();
let v = CacheValue::from_bytes(&bytes).unwrap();
assert!(matches!(v, CacheValue::Json(_)));
}
#[test]
fn test_memory_driver_set_get_raw() {
let driver = MemoryCacheDriver::new();
driver.set_raw("key", b"value".to_vec(), None).unwrap();
let val = driver.get_raw("key").unwrap();
assert_eq!(val, Some(b"value".to_vec()));
}
#[test]
fn test_memory_driver_delete() {
let driver = MemoryCacheDriver::new();
driver.set_raw("key", b"value".to_vec(), None).unwrap();
driver.delete("key").unwrap();
let val = driver.get_raw("key").unwrap();
assert_eq!(val, None);
}
#[test]
fn test_memory_driver_has() {
let driver = MemoryCacheDriver::new();
driver.set_raw("key", b"value".to_vec(), None).unwrap();
assert!(driver.has("key").unwrap());
assert!(!driver.has("nonexistent").unwrap());
}
#[test]
fn test_memory_driver_clear() {
let driver = MemoryCacheDriver::new();
driver.set_raw("key1", b"value1".to_vec(), None).unwrap();
driver.set_raw("key2", b"value2".to_vec(), None).unwrap();
driver.clear().unwrap();
assert!(!driver.has("key1").unwrap());
assert!(!driver.has("key2").unwrap());
}
#[test]
fn test_memory_driver_ttl_expiration() {
let driver = MemoryCacheDriver::new();
driver
.set_raw("key", b"value".to_vec(), Some(Duration::from_millis(50)))
.unwrap();
assert!(driver.get_raw("key").unwrap().is_some());
std::thread::sleep(Duration::from_millis(100));
assert!(driver.get_raw("key").unwrap().is_none());
}
#[test]
fn test_memory_driver_inc_default_implementation() {
let driver = MemoryCacheDriver::new();
let v = driver.inc("counter", 5).unwrap();
assert_eq!(v, 5);
let v = driver.inc("counter", 3).unwrap();
assert_eq!(v, 8);
}
#[test]
fn test_memory_driver_dec_default_implementation() {
let driver = MemoryCacheDriver::new();
let v = driver.dec("counter", 3).unwrap();
assert_eq!(v, -3);
let v = driver.dec("counter", 2).unwrap();
assert_eq!(v, -5);
}
#[test]
fn test_cache_manager_register_and_get_default() {
let mut mgr = CacheManager::new();
mgr.register_store("default", Box::new(MemoryCacheDriver::new()));
let driver = mgr.default_store().unwrap();
driver.set_raw("key", b"value".to_vec(), None).unwrap();
assert_eq!(driver.get_raw("key").unwrap(), Some(b"value".to_vec()));
}
#[test]
fn test_cache_manager_multiple_stores_isolation() {
let mut mgr = CacheManager::new();
mgr.register_store("file", Box::new(MemoryCacheDriver::new()));
mgr.register_store("redis", Box::new(MemoryCacheDriver::new()));
let file_driver = mgr.store("file").unwrap();
let redis_driver = mgr.store("redis").unwrap();
file_driver
.set_raw("key", b"file_value".to_vec(), None)
.unwrap();
redis_driver
.set_raw("key", b"redis_value".to_vec(), None)
.unwrap();
assert_eq!(
file_driver.get_raw("key").unwrap(),
Some(b"file_value".to_vec())
);
assert_eq!(
redis_driver.get_raw("key").unwrap(),
Some(b"redis_value".to_vec())
);
}
#[test]
fn test_cache_manager_set_default() {
let mut mgr = CacheManager::new();
mgr.register_store("file", Box::new(MemoryCacheDriver::new()));
mgr.register_store("redis", Box::new(MemoryCacheDriver::new()));
let driver = mgr.default_store().unwrap();
driver.set_raw("file_key", b"file".to_vec(), None).unwrap();
assert_eq!(driver.get_raw("file_key").unwrap(), Some(b"file".to_vec()));
mgr.set_default("redis").unwrap();
let driver = mgr.default_store().unwrap();
driver
.set_raw("redis_key", b"redis".to_vec(), None)
.unwrap();
assert_eq!(
driver.get_raw("redis_key").unwrap(),
Some(b"redis".to_vec())
);
}
#[test]
fn test_cache_manager_default_store_not_registered_error() {
let mgr = CacheManager::new();
let result = mgr.default_store();
assert!(matches!(result, Err(CacheError::NotFound(_))));
}
#[test]
fn test_cache_manager_store_not_found_error() {
let mgr = CacheManager::new();
let result = mgr.store("nonexistent");
assert!(matches!(result, Err(CacheError::NotFound(_))));
}
#[test]
fn test_cache_set_get_string() {
let cache = make_cache();
cache.set("name", "Alice", None).unwrap();
let val: Option<String> = cache.get("name").unwrap();
assert_eq!(val, Some("Alice".to_string()));
}
#[test]
fn test_cache_set_get_int_as_string_php_bug() {
let cache = make_cache();
cache.set("count", 42i64, None).unwrap();
let s: Option<String> = cache.get("count").unwrap();
assert_eq!(s, Some("42".to_string()));
let n: i64 = s.unwrap().parse().unwrap();
assert_eq!(n, 42);
}
#[test]
fn test_cache_set_get_struct() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct User {
name: String,
age: u32,
}
let cache = make_cache();
let user = User {
name: "Alice".to_string(),
age: 30,
};
cache.set("user:1", &user, None).unwrap();
let val: Option<User> = cache.get("user:1").unwrap();
assert_eq!(val, Some(user));
}
#[test]
fn test_cache_set_get_vec() {
let cache = make_cache();
let list = vec![1, 2, 3];
cache.set("list", &list, None).unwrap();
let val: Option<Vec<i64>> = cache.get("list").unwrap();
assert_eq!(val, Some(vec![1, 2, 3]));
}
#[test]
fn test_cache_get_miss_returns_none() {
let cache = make_cache();
let val: Option<String> = cache.get("nonexistent").unwrap();
assert_eq!(val, None);
}
#[test]
fn test_cache_get_or_default_value() {
let cache = make_cache();
let val: String = cache.get_or("nonexistent", "default".to_string()).unwrap();
assert_eq!(val, "default");
}
#[test]
fn test_cache_set_with_ttl_expires() {
let cache = make_cache();
cache
.set("key", "value", Some(Duration::from_millis(50)))
.unwrap();
assert!(cache.get::<String>("key").unwrap().is_some());
std::thread::sleep(Duration::from_millis(100));
assert!(cache.get::<String>("key").unwrap().is_none());
}
#[test]
fn test_cache_delete() {
let cache = make_cache();
cache.set("key", "value", None).unwrap();
assert!(cache.has("key").unwrap());
cache.delete("key").unwrap();
assert!(!cache.has("key").unwrap());
cache.delete("nonexistent").unwrap();
}
#[test]
fn test_cache_has_checks_ttl() {
let cache = make_cache();
cache
.set("key", "value", Some(Duration::from_millis(50)))
.unwrap();
assert!(cache.has("key").unwrap());
std::thread::sleep(Duration::from_millis(100));
assert!(!cache.has("key").unwrap());
}
#[test]
fn test_cache_clear() {
let cache = make_cache();
cache.set("key1", "value1", None).unwrap();
cache.set("key2", "value2", None).unwrap();
cache.clear().unwrap();
assert!(!cache.has("key1").unwrap());
assert!(!cache.has("key2").unwrap());
}
#[test]
fn test_cache_inc_initial_value() {
let cache = make_cache();
let v = cache.inc("counter", 5).unwrap();
assert_eq!(v, 5);
let s: String = cache.get("counter").unwrap().unwrap();
assert_eq!(s, "5");
}
#[test]
fn test_cache_inc_accumulate() {
let cache = make_cache();
cache.inc("counter", 5).unwrap();
cache.inc("counter", 3).unwrap();
let v = cache.inc("counter", 2).unwrap();
assert_eq!(v, 10);
}
#[test]
fn test_cache_dec_initial_value() {
let cache = make_cache();
let v = cache.dec("counter", 3).unwrap();
assert_eq!(v, -3);
}
#[test]
fn test_cache_dec_accumulate() {
let cache = make_cache();
cache.set("counter", 100i64, None).unwrap();
cache.dec("counter", 30).unwrap();
let v = cache.dec("counter", 20).unwrap();
assert_eq!(v, 50);
}
#[test]
fn test_cache_increment_default_step_1() {
let cache = make_cache();
let v = cache.increment("counter").unwrap();
assert_eq!(v, 1);
let v = cache.increment("counter").unwrap();
assert_eq!(v, 2);
}
#[test]
fn test_cache_decrement_default_step_1() {
let cache = make_cache();
let v = cache.decrement("counter").unwrap();
assert_eq!(v, -1);
let v = cache.decrement("counter").unwrap();
assert_eq!(v, -2);
}
#[test]
fn test_cache_pull_existing_key() {
let cache = make_cache();
cache.set("key", "value", None).unwrap();
let val: Option<String> = cache.pull("key").unwrap();
assert_eq!(val, Some("value".to_string()));
assert!(!cache.has("key").unwrap());
}
#[test]
fn test_cache_pull_missing_key_returns_none() {
let cache = make_cache();
let val: Option<String> = cache.pull("nonexistent").unwrap();
assert_eq!(val, None);
}
#[test]
fn test_cache_push_initial_array() {
let cache = make_cache();
cache.push("list", "a".to_string(), None).unwrap();
let val: Option<Vec<String>> = cache.get("list").unwrap();
assert_eq!(val, Some(vec!["a".to_string()]));
}
#[test]
fn test_cache_push_appends() {
let cache = make_cache();
cache.push("list", "a".to_string(), None).unwrap();
cache.push("list", "b".to_string(), None).unwrap();
cache.push("list", "c".to_string(), None).unwrap();
let val: Option<Vec<String>> = cache.get("list").unwrap();
assert_eq!(
val,
Some(vec!["a".to_string(), "b".to_string(), "c".to_string()])
);
}
#[test]
fn test_cache_push_deduplication() {
let cache = make_cache();
cache.push("list", "a".to_string(), None).unwrap();
cache.push("list", "b".to_string(), None).unwrap();
cache.push("list", "a".to_string(), None).unwrap(); cache.push("list", "c".to_string(), None).unwrap();
cache.push("list", "b".to_string(), None).unwrap();
let val: Option<Vec<String>> = cache.get("list").unwrap();
assert_eq!(
val,
Some(vec!["a".to_string(), "b".to_string(), "c".to_string()])
);
}
#[test]
fn test_cache_push_max_1000_fifo() {
let cache = make_cache();
for i in 0..1001i64 {
cache.push("list", i, None).unwrap();
}
let val: Option<Vec<i64>> = cache.get("list").unwrap();
let list = val.unwrap();
assert_eq!(list.len(), 1000);
assert_eq!(list[0], 1);
assert_eq!(list[999], 1000);
}
#[test]
fn test_cache_push_non_array_becomes_array() {
let cache = make_cache();
cache.set("key", "not_an_array".to_string(), None).unwrap();
cache.push("key", "first".to_string(), None).unwrap();
let val: Option<Vec<String>> = cache.get("key").unwrap();
assert_eq!(val, Some(vec!["first".to_string()]));
}
#[tokio::test]
async fn test_cache_remember_cache_miss() {
let cache = make_cache();
let counter = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
let counter_clone = counter.clone();
let val: i64 = cache
.remember("expensive", None, || {
counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst) as i64 + 100
})
.await
.unwrap();
assert_eq!(val, 100);
let val: i64 = cache
.remember("expensive", None, || {
counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst) as i64 + 200
})
.await
.unwrap();
assert_eq!(val, 100);
assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
}
#[tokio::test]
async fn test_cache_remember_cache_hit_returns_cached() {
let cache = make_cache();
cache.set("predefined", 42i64, None).unwrap();
let val: i64 = cache
.remember("predefined", None, || {
panic!("callback should not be called on cache hit");
})
.await
.unwrap();
assert_eq!(val, 42);
}
#[tokio::test]
async fn test_cache_remember_writes_with_ttl() {
let cache = make_cache();
cache
.remember("key", Some(Duration::from_millis(50)), || 42i64)
.await
.unwrap();
assert_eq!(cache.get::<String>("key").unwrap(), Some("42".to_string()));
tokio::time::sleep(Duration::from_millis(100)).await;
assert!(cache.get::<String>("key").unwrap().is_none());
}
#[tokio::test]
async fn test_cache_remember_releases_lock_on_success() {
let cache = make_cache();
cache.remember("key", None, || 42i64).await.unwrap();
assert!(!cache.has("key_lock").unwrap());
}
#[tokio::test]
async fn test_cache_remember_releases_lock_on_panic() {
let cache = make_cache();
let _ = cache.remember("key", None, || 42i64).await;
assert!(!cache.has("key_lock").unwrap());
}
#[tokio::test]
async fn test_cache_remember_lock_has_no_ttl_php_bug() {
let cache = make_cache();
cache.remember("key", None, || 42i64).await.unwrap();
assert!(!cache.has("key_lock").unwrap());
}
#[tokio::test]
async fn test_cache_remember_async_cache_miss() {
let cache = make_cache();
let counter = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
let counter_clone = counter.clone();
let val: i64 = cache
.remember_async("expensive_async", None, || {
let counter_clone = counter_clone.clone();
async move {
counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst) as i64 + 100
}
})
.await
.unwrap();
assert_eq!(val, 100);
let val: i64 = cache
.remember_async("expensive_async", None, || {
let counter_clone = counter_clone.clone();
async move {
counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst) as i64 + 200
}
})
.await
.unwrap();
assert_eq!(val, 100);
assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
}
#[tokio::test]
async fn test_cache_remember_async_cache_hit_returns_cached() {
let cache = make_cache();
cache.set("predefined_async", 42i64, None).unwrap();
let val: i64 = cache
.remember_async("predefined_async", None, || async {
panic!("callback should not be called on cache hit");
})
.await
.unwrap();
assert_eq!(val, 42);
}
#[tokio::test]
async fn test_cache_remember_async_writes_with_ttl() {
let cache = make_cache();
cache
.remember_async("key_async", Some(Duration::from_millis(50)), || async {
42i64
})
.await
.unwrap();
assert_eq!(
cache.get::<String>("key_async").unwrap(),
Some("42".to_string())
);
tokio::time::sleep(Duration::from_millis(100)).await;
assert!(cache.get::<String>("key_async").unwrap().is_none());
}
#[tokio::test]
async fn test_cache_remember_async_releases_lock_on_success() {
let cache = make_cache();
cache
.remember_async("key_async", None, || async { 42i64 })
.await
.unwrap();
assert!(!cache.has("key_async_lock").unwrap());
}
#[tokio::test]
async fn test_cache_remember_async_lock_has_no_ttl_php_bug() {
let cache = make_cache();
cache
.remember_async("key_async", None, || async { 42i64 })
.await
.unwrap();
assert!(!cache.has("key_async_lock").unwrap());
}
#[test]
fn test_cache_with_store() {
let cache = Cache::new();
cache.register_store("redis", Box::new(MemoryCacheDriver::new()));
let result = cache
.with_store("redis", |driver| {
driver.set_raw("key", b"value".to_vec(), None)?;
driver.get_raw("key")
})
.unwrap();
assert_eq!(result, Some(b"value".to_vec()));
}
#[test]
fn test_cache_with_store_not_found() {
let cache = Cache::new();
let result: Result<Option<Vec<u8>>, CacheError> =
cache.with_store("nonexistent", |driver| driver.get_raw("key"));
assert!(matches!(result, Err(CacheError::NotFound(_))));
}
#[test]
fn test_default_cache_singleton() {
let c1 = default_cache();
let c2 = default_cache();
assert!(std::ptr::eq(c1, c2));
}
#[test]
fn test_r5_php_set_get_basic_alignment() {
let cache = make_cache();
cache.set("name", "Alice", None).unwrap();
let val: String = cache.get("name").unwrap().unwrap();
assert_eq!(val, "Alice");
}
#[test]
fn test_r5_php_is_numeric_short_circuit() {
let cache = make_cache();
cache.set("count", 42i64, None).unwrap();
let s: String = cache.get("count").unwrap().unwrap();
assert_eq!(s, "42");
}
#[test]
fn test_r5_php_inc_no_serialize() {
let cache = make_cache();
cache.set("counter", 100i64, None).unwrap();
let new_val = cache.inc("counter", 50).unwrap();
assert_eq!(new_val, 150);
let s: String = cache.get("counter").unwrap().unwrap();
assert_eq!(s, "150");
}
#[test]
fn test_r5_php_dec_no_serialize() {
let cache = make_cache();
cache.set("counter", 100i64, None).unwrap();
let new_val = cache.dec("counter", 30).unwrap();
assert_eq!(new_val, 70);
}
#[tokio::test]
async fn test_r5_php_remember_lock_mechanism() {
let cache = make_cache();
let val: i64 = cache.remember("key", None, || 42).await.unwrap();
assert_eq!(val, 42);
assert_eq!(cache.get::<String>("key").unwrap(), Some("42".to_string()));
assert!(!cache.has("key_lock").unwrap());
}
#[test]
fn test_r5_php_push_max_1000_array_shift() {
let cache = make_cache();
for i in 0..1001i64 {
cache.push("list", i, None).unwrap();
}
let list: Vec<i64> = cache.get("list").unwrap().unwrap();
assert_eq!(list.len(), 1000);
assert_eq!(list[0], 1); assert_eq!(list[999], 1000);
}
#[test]
fn test_r5_php_push_array_unique() {
let cache = make_cache();
cache.push("list", "a".to_string(), None).unwrap();
cache.push("list", "a".to_string(), None).unwrap();
cache.push("list", "b".to_string(), None).unwrap();
cache.push("list", "a".to_string(), None).unwrap();
let list: Vec<String> = cache.get("list").unwrap().unwrap();
assert_eq!(list, vec!["a".to_string(), "b".to_string()]);
}
#[test]
fn test_r5_php_pull_get_then_delete() {
let cache = make_cache();
cache.set("key", "value", None).unwrap();
let val: Option<String> = cache.pull("key").unwrap();
assert_eq!(val, Some("value".to_string()));
assert!(!cache.has("key").unwrap());
}
#[test]
fn test_r5_php_delete_nonexistent_no_error() {
let cache = make_cache();
let result = cache.delete("nonexistent");
assert!(result.is_ok());
}
#[test]
fn test_r5_php_has_ttl_expiration() {
let cache = make_cache();
cache
.set("key", "value", Some(Duration::from_millis(50)))
.unwrap();
assert!(cache.has("key").unwrap());
std::thread::sleep(Duration::from_millis(100));
assert!(!cache.has("key").unwrap());
}
#[test]
fn test_r5_php_clear_all_keys() {
let cache = make_cache();
cache.set("key1", "value1", None).unwrap();
cache.set("key2", "value2", None).unwrap();
cache.set("key3", "value3", None).unwrap();
cache.clear().unwrap();
assert!(!cache.has("key1").unwrap());
assert!(!cache.has("key2").unwrap());
assert!(!cache.has("key3").unwrap());
}
#[test]
fn test_php_bug_unserialize_numeric_returns_string() {
let cache = make_cache();
cache.set("count", 42i64, None).unwrap();
let s: String = cache.get("count").unwrap().unwrap();
assert_eq!(s, "42");
let n: i64 = s.parse().unwrap();
assert_eq!(n, 42);
}
#[tokio::test]
async fn test_php_bug_remember_lock_no_ttl() {
let cache = make_cache();
cache.remember("key", None, || 42i64).await.unwrap();
assert!(!cache.has("key_lock").unwrap());
}
#[tokio::test]
async fn test_php_bug_remember_has_get_double_check() {
let cache = make_cache();
let val: i64 = cache.remember("key", None, || 42).await.unwrap();
assert_eq!(val, 42);
}
#[test]
fn test_php_behavior_set_overwrite() {
let cache = make_cache();
cache.set("key", "first", None).unwrap();
cache.set("key", "second", None).unwrap();
let val: String = cache.get("key").unwrap().unwrap();
assert_eq!(val, "second");
}
#[test]
fn test_php_behavior_ttl_permanent() {
let cache = make_cache();
cache.set("key", "value", None).unwrap();
assert!(cache.has("key").unwrap());
std::thread::sleep(Duration::from_millis(50));
assert!(cache.has("key").unwrap());
}
#[test]
fn test_redis_config_default() {
let config = RedisConfig::default();
assert_eq!(config.host, "127.0.0.1");
assert_eq!(config.port, 6379);
assert_eq!(config.password, "");
assert_eq!(config.select, 0);
assert_eq!(config.timeout, Duration::ZERO);
assert_eq!(config.expire, None);
assert!(!config.persistent);
assert_eq!(config.prefix, "");
assert_eq!(config.tag_prefix, "tag:");
}
#[test]
fn test_redis_config_with_prefix() {
let config = RedisConfig::with_prefix("myapp:");
assert_eq!(config.prefix, "myapp:");
assert_eq!(config.host, "127.0.0.1");
assert_eq!(config.tag_prefix, "tag:");
}
#[test]
fn test_redis_config_with_expire() {
let config = RedisConfig::with_expire(Duration::from_secs(3600));
assert_eq!(config.expire, Some(Duration::from_secs(3600)));
assert_eq!(config.prefix, "");
}
#[test]
fn test_mock_redis_set_get_roundtrip() {
let backend = MockRedisBackend::new();
backend.set("key1", b"value1".to_vec()).unwrap();
let val = backend.get("key1").unwrap();
assert_eq!(val, Some(b"value1".to_vec()));
}
#[test]
fn test_mock_redis_del() {
let backend = MockRedisBackend::new();
backend.set("key1", b"value1".to_vec()).unwrap();
let removed = backend.del("key1").unwrap();
assert_eq!(removed, 1);
assert_eq!(backend.get("key1").unwrap(), None);
assert_eq!(backend.del("key1").unwrap(), 0);
}
#[test]
fn test_mock_redis_exists() {
let backend = MockRedisBackend::new();
assert!(!backend.exists("key1").unwrap());
backend.set("key1", b"value1".to_vec()).unwrap();
assert!(backend.exists("key1").unwrap());
}
#[test]
fn test_mock_redis_incr_by_new_key() {
let backend = MockRedisBackend::new();
let result = backend.incr_by("counter", 5).unwrap();
assert_eq!(result, 5);
let val = backend.get("counter").unwrap();
assert_eq!(val, Some(b"5".to_vec()));
}
#[test]
fn test_mock_redis_incr_by_existing_key() {
let backend = MockRedisBackend::new();
backend.set("counter", b"10".to_vec()).unwrap();
let result = backend.incr_by("counter", 5).unwrap();
assert_eq!(result, 15);
let val = backend.get("counter").unwrap();
assert_eq!(val, Some(b"15".to_vec()));
}
#[test]
fn test_mock_redis_incr_by_non_integer_error() {
let backend = MockRedisBackend::new();
backend.set("key", b"not_a_number".to_vec()).unwrap();
let result = backend.incr_by("key", 1);
assert!(result.is_err());
}
#[test]
fn test_mock_redis_decr_by() {
let backend = MockRedisBackend::new();
let result = backend.decr_by("counter", 3).unwrap();
assert_eq!(result, -3);
}
#[test]
fn test_mock_redis_set_ex_and_expire() {
let backend = MockRedisBackend::new();
backend
.set_ex("key1", b"value1".to_vec(), Duration::from_millis(50))
.unwrap();
assert!(backend.get("key1").unwrap().is_some());
std::thread::sleep(Duration::from_millis(80));
assert_eq!(backend.get("key1").unwrap(), None);
}
#[test]
fn test_mock_redis_expired_key_exists_false() {
let backend = MockRedisBackend::new();
backend
.set_ex("key1", b"value1".to_vec(), Duration::from_millis(50))
.unwrap();
assert!(backend.exists("key1").unwrap());
std::thread::sleep(Duration::from_millis(80));
assert!(!backend.exists("key1").unwrap());
}
#[test]
fn test_mock_redis_sadd_smembers() {
let backend = MockRedisBackend::new();
backend.sadd("tag:users", "user:1").unwrap();
backend.sadd("tag:users", "user:2").unwrap();
backend.sadd("tag:users", "user:3").unwrap();
let members = backend.smembers("tag:users").unwrap();
assert_eq!(members.len(), 3);
assert!(members.contains(&"user:1".to_string()));
assert!(members.contains(&"user:2".to_string()));
assert!(members.contains(&"user:3".to_string()));
}
#[test]
fn test_mock_redis_sadd_dedup() {
let backend = MockRedisBackend::new();
let added1 = backend.sadd("tag:users", "user:1").unwrap();
assert_eq!(added1, 1);
let added2 = backend.sadd("tag:users", "user:1").unwrap();
assert_eq!(added2, 0);
let members = backend.smembers("tag:users").unwrap();
assert_eq!(members.len(), 1);
}
#[test]
fn test_mock_redis_smembers_nonexistent_key() {
let backend = MockRedisBackend::new();
let members = backend.smembers("nonexistent").unwrap();
assert!(members.is_empty());
}
#[test]
fn test_mock_redis_flush_db() {
let backend = MockRedisBackend::new();
backend.set("key1", b"v1".to_vec()).unwrap();
backend.set("key2", b"v2".to_vec()).unwrap();
backend.sadd("tag:1", "m1").unwrap();
backend.flush_db().unwrap();
assert_eq!(backend.get("key1").unwrap(), None);
assert_eq!(backend.get("key2").unwrap(), None);
assert!(backend.smembers("tag:1").unwrap().is_empty());
}
#[test]
fn test_mock_redis_del_many() {
let backend = MockRedisBackend::new();
backend.set("key1", b"v1".to_vec()).unwrap();
backend.set("key2", b"v2".to_vec()).unwrap();
backend.set("key3", b"v3".to_vec()).unwrap();
let removed = backend.del_many(&["key1", "key2", "nonexistent"]).unwrap();
assert_eq!(removed, 2);
assert_eq!(backend.get("key1").unwrap(), None);
assert_eq!(backend.get("key2").unwrap(), None);
assert!(backend.get("key3").unwrap().is_some());
}
fn make_redis_driver() -> RedisCacheDriver {
RedisCacheDriver::new(RedisConfig::default())
}
#[test]
fn test_redis_driver_set_get_roundtrip() {
let driver = make_redis_driver();
driver.set_raw("key1", b"value1".to_vec(), None).unwrap();
let val = driver.get_raw("key1").unwrap();
assert_eq!(val, Some(b"value1".to_vec()));
}
#[test]
fn test_redis_driver_delete() {
let driver = make_redis_driver();
driver.set_raw("key1", b"value1".to_vec(), None).unwrap();
driver.delete("key1").unwrap();
assert_eq!(driver.get_raw("key1").unwrap(), None);
}
#[test]
fn test_redis_driver_has() {
let driver = make_redis_driver();
assert!(!driver.has("key1").unwrap());
driver.set_raw("key1", b"value1".to_vec(), None).unwrap();
assert!(driver.has("key1").unwrap());
}
#[test]
fn test_redis_driver_clear() {
let driver = make_redis_driver();
driver.set_raw("key1", b"v1".to_vec(), None).unwrap();
driver.set_raw("key2", b"v2".to_vec(), None).unwrap();
driver.clear().unwrap();
assert_eq!(driver.get_raw("key1").unwrap(), None);
assert_eq!(driver.get_raw("key2").unwrap(), None);
}
#[test]
fn test_redis_driver_inc_dec() {
let driver = make_redis_driver();
let result = driver.inc("counter", 5).unwrap();
assert_eq!(result, 5);
let result = driver.inc("counter", 3).unwrap();
assert_eq!(result, 8);
let result = driver.dec("counter", 2).unwrap();
assert_eq!(result, 6);
}
#[test]
fn test_redis_driver_cache_key_with_prefix() {
let driver = RedisCacheDriver::new(RedisConfig::with_prefix("myapp:"));
assert_eq!(driver.cache_key("user:1"), "myapp:user:1");
}
#[test]
fn test_redis_driver_cache_key_no_prefix() {
let driver = RedisCacheDriver::new(RedisConfig::default());
assert_eq!(driver.cache_key("user:1"), "user:1");
}
#[test]
fn test_redis_driver_tag_key_md5() {
let driver = RedisCacheDriver::new(RedisConfig::default());
let tag_key = driver.tag_key("users");
let expected_md5 = compute_md5("users");
assert_eq!(tag_key, format!("tag:{}", expected_md5));
}
#[test]
fn test_redis_driver_tag_key_custom_prefix() {
let config = RedisConfig {
tag_prefix: "t:".to_string(),
..RedisConfig::default()
};
let driver = RedisCacheDriver::new(config);
let tag_key = driver.tag_key("users");
let expected_md5 = compute_md5("users");
assert_eq!(tag_key, format!("t:{}", expected_md5));
}
#[test]
fn test_redis_driver_prefix_applied_to_set() {
let driver = RedisCacheDriver::new(RedisConfig::with_prefix("myapp:"));
driver.set_raw("key1", b"value1".to_vec(), None).unwrap();
let val = driver.backend().get("myapp:key1").unwrap();
assert_eq!(val, Some(b"value1".to_vec()));
assert_eq!(driver.backend().get("key1").unwrap(), None);
}
#[test]
fn test_redis_driver_prefix_applied_to_get() {
let driver = RedisCacheDriver::new(RedisConfig::with_prefix("myapp:"));
driver.set_raw("key1", b"value1".to_vec(), None).unwrap();
let val = driver.get_raw("key1").unwrap();
assert_eq!(val, Some(b"value1".to_vec()));
}
#[test]
fn test_redis_driver_prefix_applied_to_delete() {
let driver = RedisCacheDriver::new(RedisConfig::with_prefix("myapp:"));
driver.set_raw("key1", b"value1".to_vec(), None).unwrap();
driver.delete("key1").unwrap();
assert_eq!(driver.backend().get("myapp:key1").unwrap(), None);
}
#[test]
fn test_redis_driver_prefix_applied_to_has() {
let driver = RedisCacheDriver::new(RedisConfig::with_prefix("myapp:"));
driver.set_raw("key1", b"value1".to_vec(), None).unwrap();
assert!(driver.has("key1").unwrap());
assert!(driver.backend().exists("myapp:key1").unwrap());
}
#[test]
fn test_redis_driver_prefix_applied_to_inc() {
let driver = RedisCacheDriver::new(RedisConfig::with_prefix("myapp:"));
let result = driver.inc("counter", 5).unwrap();
assert_eq!(result, 5);
let val = driver.backend().get("myapp:counter").unwrap();
assert_eq!(val, Some(b"5".to_vec()));
}
#[test]
fn test_redis_driver_append_and_get_tag_items() {
let driver = make_redis_driver();
driver.append("tag:users", "user:1").unwrap();
driver.append("tag:users", "user:2").unwrap();
driver.append("tag:users", "user:3").unwrap();
let members = driver.backend().smembers("tag:users").unwrap();
assert_eq!(members.len(), 3);
}
#[test]
fn test_redis_driver_get_tag_items_with_tag_key() {
let driver = make_redis_driver();
let tag_name = driver.tag_key("users");
driver.append(&tag_name, "user:1").unwrap();
driver.append(&tag_name, "user:2").unwrap();
let members = driver.get_tag_items("users").unwrap();
assert_eq!(members.len(), 2);
assert!(members.contains(&"user:1".to_string()));
assert!(members.contains(&"user:2".to_string()));
}
#[test]
fn test_redis_driver_clear_tag() {
let driver = make_redis_driver();
driver.set_raw("key1", b"v1".to_vec(), None).unwrap();
driver.set_raw("key2", b"v2".to_vec(), None).unwrap();
driver.clear_tag(&["key1", "key2"]).unwrap();
assert_eq!(driver.get_raw("key1").unwrap(), None);
assert_eq!(driver.get_raw("key2").unwrap(), None);
}
#[test]
fn test_redis_driver_set_with_ttl_uses_setex() {
let driver = make_redis_driver();
driver
.set_raw("key1", b"value1".to_vec(), Some(Duration::from_millis(100)))
.unwrap();
assert!(driver.get_raw("key1").unwrap().is_some());
std::thread::sleep(Duration::from_millis(150));
assert_eq!(driver.get_raw("key1").unwrap(), None);
}
#[test]
fn test_redis_driver_set_without_ttl_uses_set() {
let driver = make_redis_driver();
driver.set_raw("key1", b"value1".to_vec(), None).unwrap();
std::thread::sleep(Duration::from_millis(50));
assert!(driver.get_raw("key1").unwrap().is_some());
}
#[test]
fn test_redis_driver_set_with_config_expire() {
let config = RedisConfig::with_expire(Duration::from_millis(100));
let driver = RedisCacheDriver::new(config);
driver.set_raw("key1", b"value1".to_vec(), None).unwrap();
assert!(driver.get_raw("key1").unwrap().is_some());
std::thread::sleep(Duration::from_millis(150));
assert_eq!(driver.get_raw("key1").unwrap(), None);
}
#[test]
fn test_redis_driver_set_ttl_overrides_config_expire() {
let config = RedisConfig::with_expire(Duration::from_secs(3600));
let driver = RedisCacheDriver::new(config);
driver
.set_raw("key1", b"value1".to_vec(), Some(Duration::from_millis(50)))
.unwrap();
std::thread::sleep(Duration::from_millis(80));
assert_eq!(driver.get_raw("key1").unwrap(), None);
}
#[test]
fn test_redis_driver_with_cache_facade() {
let cache = Cache::new();
let driver = RedisCacheDriver::new(RedisConfig::default());
cache.register_store("redis", Box::new(driver));
cache.set_default_store("redis").unwrap();
cache.set("key", "value", None).unwrap();
let val: String = cache.get("key").unwrap().unwrap();
assert_eq!(val, "value");
}
#[test]
fn test_redis_driver_with_cache_facade_inc() {
let cache = Cache::new();
let driver = RedisCacheDriver::new(RedisConfig::default());
cache.register_store("redis", Box::new(driver));
cache.set_default_store("redis").unwrap();
let result = cache.inc("counter", 5).unwrap();
assert_eq!(result, 5);
let result = cache.inc("counter", 3).unwrap();
assert_eq!(result, 8);
}
#[test]
fn test_php_redis_inc_not_through_serialize() {
let driver = make_redis_driver();
driver.inc("counter", 5).unwrap();
let val = driver.backend().get(&driver.cache_key("counter")).unwrap();
assert_eq!(val, Some(b"5".to_vec())); assert_ne!(val, Some(b"i:5;".to_vec())); }
#[test]
fn test_php_redis_set_with_ttl_expires() {
let driver = make_redis_driver();
driver
.set_raw("key", b"value".to_vec(), Some(Duration::from_millis(50)))
.unwrap();
assert!(driver.get_raw("key").unwrap().is_some());
std::thread::sleep(Duration::from_millis(80));
assert_eq!(driver.get_raw("key").unwrap(), None);
}
#[test]
fn test_php_redis_set_without_ttl_permanent() {
let driver = make_redis_driver();
driver.set_raw("key", b"value".to_vec(), None).unwrap();
std::thread::sleep(Duration::from_millis(50));
assert!(driver.get_raw("key").unwrap().is_some());
}
#[test]
fn test_php_redis_tag_key_format() {
let driver = make_redis_driver();
let tag_key = driver.tag_key("users");
let expected = format!("tag:{}", compute_md5("users"));
assert_eq!(tag_key, expected);
assert_eq!(compute_md5("users").len(), 32);
}
#[test]
fn test_php_redis_clear_uses_flushdb() {
let driver = make_redis_driver();
driver.set_raw("key1", b"v1".to_vec(), None).unwrap();
driver.set_raw("key2", b"v2".to_vec(), None).unwrap();
driver.append("tag:1", "m1").unwrap();
driver.clear().unwrap();
assert_eq!(driver.get_raw("key1").unwrap(), None);
assert_eq!(driver.get_raw("key2").unwrap(), None);
assert!(driver.backend().smembers("tag:1").unwrap().is_empty());
}
#[test]
fn test_php_redis_inc_returns_new_value() {
let driver = make_redis_driver();
let r1 = driver.inc("c", 1).unwrap();
assert_eq!(r1, 1);
let r2 = driver.inc("c", 1).unwrap();
assert_eq!(r2, 2);
let r3 = driver.inc("c", 10).unwrap();
assert_eq!(r3, 12);
let r4 = driver.dec("c", 5).unwrap();
assert_eq!(r4, 7);
}
#[test]
fn test_php_redis_delete_nonexistent_returns_ok() {
let driver = make_redis_driver();
driver.delete("nonexistent").unwrap();
}
#[test]
fn test_php_redis_md5_alignment() {
assert_eq!(compute_md5("hello"), "5d41402abc4b2a76b9719d911017c592");
assert_eq!(compute_md5(""), "d41d8cd98f00b204e9800998ecf8427e");
assert_eq!(compute_md5("users").len(), 32);
}
#[test]
fn test_php_redis_append_uses_sadd() {
let driver = make_redis_driver();
driver.append("tag:1", "m1").unwrap();
driver.append("tag:1", "m1").unwrap(); driver.append("tag:1", "m2").unwrap();
let members = driver.backend().smembers("tag:1").unwrap();
assert_eq!(members.len(), 2);
}
#[test]
fn test_php_redis_config_precedence_ttl() {
let config = RedisConfig::with_expire(Duration::from_secs(3600));
let driver = RedisCacheDriver::new(config);
driver
.set_raw("key1", b"v1".to_vec(), Some(Duration::from_millis(50)))
.unwrap();
std::thread::sleep(Duration::from_millis(80));
assert_eq!(driver.get_raw("key1").unwrap(), None);
let config = RedisConfig::with_expire(Duration::from_millis(50));
let driver = RedisCacheDriver::new(config);
driver.set_raw("key2", b"v2".to_vec(), None).unwrap();
std::thread::sleep(Duration::from_millis(80));
assert_eq!(driver.get_raw("key2").unwrap(), None);
let driver = make_redis_driver();
driver.set_raw("key3", b"v3".to_vec(), None).unwrap();
std::thread::sleep(Duration::from_millis(50));
assert!(driver.get_raw("key3").unwrap().is_some()); }
#[test]
fn test_multi_level_driver_set_get() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new().add_level(Box::new(l1));
driver.set_raw("key1", b"value1".to_vec(), None).unwrap();
let val = driver.get_raw("key1").unwrap();
assert_eq!(val, Some(b"value1".to_vec()));
}
#[test]
fn test_multi_level_driver_delete() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new().add_level(Box::new(l1));
driver.set_raw("key1", b"value1".to_vec(), None).unwrap();
driver.delete("key1").unwrap();
assert_eq!(driver.get_raw("key1").unwrap(), None);
}
#[test]
fn test_multi_level_driver_has() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new().add_level(Box::new(l1));
assert!(!driver.has("key1").unwrap());
driver.set_raw("key1", b"value1".to_vec(), None).unwrap();
assert!(driver.has("key1").unwrap());
}
#[test]
fn test_multi_level_driver_clear() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new().add_level(Box::new(l1));
driver.set_raw("key1", b"v1".to_vec(), None).unwrap();
driver.set_raw("key2", b"v2".to_vec(), None).unwrap();
driver.clear().unwrap();
assert_eq!(driver.get_raw("key1").unwrap(), None);
assert_eq!(driver.get_raw("key2").unwrap(), None);
}
#[test]
fn test_multi_level_two_levels_cascade_get() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let l2 = sz_rust_orm_facade::MemoryCache::new();
l2.set("key", b"from_l2".to_vec(), None).unwrap();
let driver = MultiLevelCacheDriver::new()
.add_level(Box::new(l1.clone()))
.add_level(Box::new(l2));
let val = driver.get_raw("key").unwrap();
assert_eq!(val, Some(b"from_l2".to_vec()));
let l1_val = l1.get("key").unwrap();
assert_eq!(l1_val, Some(b"from_l2".to_vec()));
}
#[test]
fn test_multi_level_set_writes_all_levels() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let l2 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new()
.add_level(Box::new(l1.clone()))
.add_level(Box::new(l2.clone()));
driver.set_raw("key", b"value".to_vec(), None).unwrap();
assert_eq!(l1.get("key").unwrap(), Some(b"value".to_vec()));
assert_eq!(l2.get("key").unwrap(), Some(b"value".to_vec()));
}
#[test]
fn test_multi_level_delete_removes_all_levels() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let l2 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new()
.add_level(Box::new(l1.clone()))
.add_level(Box::new(l2.clone()));
driver.set_raw("key", b"value".to_vec(), None).unwrap();
driver.delete("key").unwrap();
assert_eq!(l1.get("key").unwrap(), None);
assert_eq!(l2.get("key").unwrap(), None);
}
#[test]
fn test_multi_level_l1_hit_skips_l2() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let l2 = sz_rust_orm_facade::MemoryCache::new();
l1.set("key", b"from_l1".to_vec(), None).unwrap();
l2.set("key", b"from_l2".to_vec(), None).unwrap();
let driver = MultiLevelCacheDriver::new()
.add_level(Box::new(l1))
.add_level(Box::new(l2));
let val = driver.get_raw("key").unwrap();
assert_eq!(val, Some(b"from_l1".to_vec()));
}
#[test]
fn test_multi_level_driver_inc_initial_value() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new().add_level(Box::new(l1));
let new_val = driver.inc("counter", 1).unwrap();
assert_eq!(new_val, 1);
let val = driver.get_raw("counter").unwrap();
assert_eq!(val, Some(b"1".to_vec()));
}
#[test]
fn test_multi_level_driver_inc_accumulate() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new().add_level(Box::new(l1));
driver.inc("counter", 5).unwrap();
driver.inc("counter", 3).unwrap();
driver.inc("counter", 1).unwrap();
let val = driver.get_raw("counter").unwrap();
assert_eq!(val, Some(b"9".to_vec()));
}
#[test]
fn test_multi_level_driver_dec() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new().add_level(Box::new(l1));
driver.set_raw("counter", b"10".to_vec(), None).unwrap();
let new_val = driver.dec("counter", 3).unwrap();
assert_eq!(new_val, 7);
let val = driver.get_raw("counter").unwrap();
assert_eq!(val, Some(b"7".to_vec()));
}
#[test]
fn test_multi_level_driver_inc_preserves_ttl() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new().add_level(Box::new(l1));
driver
.set_raw("counter", b"5".to_vec(), Some(Duration::from_millis(200)))
.unwrap();
let ttl_before = driver.inner().ttl("counter").unwrap();
assert!(ttl_before.is_some());
driver.inc("counter", 1).unwrap();
let ttl_after = driver.inner().ttl("counter").unwrap();
assert!(ttl_after.is_some());
}
#[test]
fn test_multi_level_driver_ttl_expiration() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new().add_level(Box::new(l1));
driver
.set_raw("key", b"value".to_vec(), Some(Duration::from_millis(50)))
.unwrap();
assert!(driver.get_raw("key").unwrap().is_some());
std::thread::sleep(Duration::from_millis(80));
assert_eq!(driver.get_raw("key").unwrap(), None);
}
#[test]
fn test_multi_level_driver_has_checks_ttl() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new().add_level(Box::new(l1));
driver
.set_raw("key", b"value".to_vec(), Some(Duration::from_millis(50)))
.unwrap();
assert!(driver.has("key").unwrap());
std::thread::sleep(Duration::from_millis(80));
assert!(!driver.has("key").unwrap());
}
#[test]
fn test_multi_level_driver_with_cache_facade() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let l2 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new()
.add_level(Box::new(l1))
.add_level(Box::new(l2));
let cache = Cache::new();
cache.register_store("default", Box::new(driver));
cache.set("user:1", "Alice", None).unwrap();
assert_eq!(
cache.get::<String>("user:1").unwrap(),
Some("Alice".to_string())
);
cache.delete("user:1").unwrap();
assert_eq!(cache.get::<String>("user:1").unwrap(), None);
}
#[test]
fn test_multi_level_driver_with_cache_facade_inc() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new().add_level(Box::new(l1));
let cache = Cache::new();
cache.register_store("default", Box::new(driver));
cache.inc("counter", 5).unwrap();
cache.inc("counter", 3).unwrap();
let val = cache.get::<String>("counter").unwrap();
assert_eq!(val, Some("8".to_string()));
}
#[test]
fn test_multi_level_driver_empty_levels_get_returns_none() {
let driver = MultiLevelCacheDriver::new();
assert_eq!(driver.get_raw("key").unwrap(), None);
}
#[test]
fn test_multi_level_driver_empty_levels_has_returns_false() {
let driver = MultiLevelCacheDriver::new();
assert!(!driver.has("key").unwrap());
}
#[test]
fn test_multi_level_driver_inc_non_numeric_value_resets_to_step() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new().add_level(Box::new(l1));
driver
.set_raw("counter", b"not_a_number".to_vec(), None)
.unwrap();
let new_val = driver.inc("counter", 5).unwrap();
assert_eq!(new_val, 5);
}
#[test]
fn test_multi_level_driver_default_impl() {
let driver = MultiLevelCacheDriver::default();
assert_eq!(driver.get_raw("key").unwrap(), None);
}
#[test]
fn test_r5_multi_level_get_set_basic() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new().add_level(Box::new(l1));
driver.set_raw("key", b"value".to_vec(), None).unwrap();
assert_eq!(driver.get_raw("key").unwrap(), Some(b"value".to_vec()));
}
#[test]
fn test_r5_multi_level_delete_nonexistent_no_error() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new().add_level(Box::new(l1));
assert!(driver.delete("nonexistent").is_ok());
}
#[test]
fn test_r5_multi_level_clear_empties_all() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let l2 = sz_rust_orm_facade::MemoryCache::new();
let driver = MultiLevelCacheDriver::new()
.add_level(Box::new(l1))
.add_level(Box::new(l2));
driver.set_raw("k1", b"v1".to_vec(), None).unwrap();
driver.set_raw("k2", b"v2".to_vec(), None).unwrap();
driver.clear().unwrap();
assert_eq!(driver.get_raw("k1").unwrap(), None);
assert_eq!(driver.get_raw("k2").unwrap(), None);
}
#[test]
fn test_r5_multi_level_cascade_fill_back() {
let l1 = sz_rust_orm_facade::MemoryCache::new();
let l2 = sz_rust_orm_facade::MemoryCache::new();
l2.set("key", b"from_l2".to_vec(), None).unwrap();
let driver = MultiLevelCacheDriver::new()
.add_level(Box::new(l1.clone()))
.add_level(Box::new(l2));
let val = driver.get_raw("key").unwrap();
assert_eq!(val, Some(b"from_l2".to_vec()));
assert_eq!(l1.get("key").unwrap(), Some(b"from_l2".to_vec()));
}
#[test]
fn test_tag_get_cache_key_default_no_prefix() {
let driver = MemoryCacheDriver::new();
assert_eq!(driver.get_cache_key("user:1"), "user:1");
assert_eq!(driver.get_cache_key("hello"), "hello");
}
#[test]
fn test_tag_get_tag_key_default_format() {
let driver = MemoryCacheDriver::new();
let tag_key = driver.get_tag_key("user");
assert_eq!(tag_key, "tag:ee11cbb19052e40b07aac0ca060c23ee");
}
#[test]
fn test_tag_append_creates_new_tag_set() {
let driver = MemoryCacheDriver::new();
driver.tag_append("tag:abc123", "user:1").unwrap();
let storage_key = "tag:abc123"; let raw = driver.get_raw(storage_key).unwrap();
let stored: Vec<String> = serde_json::from_slice(&raw.unwrap()).unwrap();
assert_eq!(stored, vec!["user:1"]);
}
#[test]
fn test_tag_append_appends_to_existing() {
let driver = MemoryCacheDriver::new();
driver.tag_append("tag:abc", "key1").unwrap();
driver.tag_append("tag:abc", "key2").unwrap();
let storage_key = "tag:abc";
let raw = driver.get_raw(storage_key).unwrap();
let stored: Vec<String> = serde_json::from_slice(&raw.unwrap()).unwrap();
assert_eq!(stored, vec!["key1", "key2"]);
}
#[test]
fn test_tag_append_dedup() {
let driver = MemoryCacheDriver::new();
driver.tag_append("tag:abc", "key1").unwrap();
driver.tag_append("tag:abc", "key1").unwrap(); let storage_key = "tag:abc";
let raw = driver.get_raw(storage_key).unwrap();
let stored: Vec<String> = serde_json::from_slice(&raw.unwrap()).unwrap();
assert_eq!(stored, vec!["key1"]);
}
#[test]
fn test_tag_append_max_1000_cap() {
let driver = MemoryCacheDriver::new();
for i in 0..1001i64 {
driver.tag_append("tag:abc", &format!("key{}", i)).unwrap();
}
let storage_key = "tag:abc";
let raw = driver.get_raw(storage_key).unwrap();
let stored: Vec<String> = serde_json::from_slice(&raw.unwrap()).unwrap();
assert_eq!(stored.len(), 1000);
assert!(!stored.contains(&"key0".to_string()));
assert!(stored.contains(&"key1".to_string()));
assert!(stored.contains(&"key1000".to_string()));
}
#[test]
fn test_tag_items_empty_returns_empty() {
let driver = MemoryCacheDriver::new();
let items = driver.tag_items("nonexistent_tag").unwrap();
assert!(items.is_empty());
}
#[test]
fn test_tag_items_returns_stored_keys() {
let driver = MemoryCacheDriver::new();
let tag_key = driver.get_tag_key("mytag");
driver.tag_append(&tag_key, "key1").unwrap();
driver.tag_append(&tag_key, "key2").unwrap();
let items = driver.tag_items("mytag").unwrap();
assert_eq!(items, vec!["key1", "key2"]);
}
#[test]
fn test_tag_clear_deletes_keys() {
let driver = MemoryCacheDriver::new();
driver.set_raw("key1", b"v1".to_vec(), None).unwrap();
driver.set_raw("key2", b"v2".to_vec(), None).unwrap();
driver
.tag_clear(&["key1".to_string(), "key2".to_string()])
.unwrap();
assert!(!driver.has("key1").unwrap());
assert!(!driver.has("key2").unwrap());
}
#[test]
fn test_tag_clear_empty_no_error() {
let driver = MemoryCacheDriver::new();
driver.tag_clear(&[]).unwrap();
}
#[test]
fn test_redis_tag_get_cache_key_with_prefix() {
let config = RedisConfig {
prefix: "myapp:".to_string(),
..RedisConfig::default()
};
let driver = RedisCacheDriver::new(config);
assert_eq!(driver.get_cache_key("user:1"), "myapp:user:1");
}
#[test]
fn test_redis_tag_get_tag_key_with_tag_prefix() {
let config = RedisConfig {
tag_prefix: "tag:".to_string(),
..RedisConfig::default()
};
let driver = RedisCacheDriver::new(config);
let tag_key = driver.get_tag_key("user");
assert_eq!(tag_key, "tag:ee11cbb19052e40b07aac0ca060c23ee");
}
#[test]
fn test_redis_tag_get_tag_key_custom_prefix() {
let config = RedisConfig {
tag_prefix: "t:".to_string(),
..RedisConfig::default()
};
let driver = RedisCacheDriver::new(config);
let tag_key = driver.get_tag_key("user");
assert_eq!(tag_key, "t:ee11cbb19052e40b07aac0ca060c23ee");
}
#[test]
fn test_redis_tag_append_uses_sadd() {
let driver = RedisCacheDriver::new(RedisConfig::default());
let tag_key = driver.get_tag_key("mytag");
driver.tag_append(&tag_key, "key1").unwrap();
driver.tag_append(&tag_key, "key2").unwrap();
driver.tag_append(&tag_key, "key1").unwrap(); let items = driver.tag_items("mytag").unwrap();
assert_eq!(items.len(), 2);
assert!(items.contains(&"key1".to_string()));
assert!(items.contains(&"key2".to_string()));
}
#[test]
fn test_redis_tag_items_uses_smembers() {
let driver = RedisCacheDriver::new(RedisConfig::default());
let tag_key = driver.get_tag_key("mytag");
driver.tag_append(&tag_key, "a").unwrap();
driver.tag_append(&tag_key, "b").unwrap();
driver.tag_append(&tag_key, "c").unwrap();
let items = driver.tag_items("mytag").unwrap();
assert_eq!(items.len(), 3);
}
#[test]
fn test_redis_tag_clear_does_not_double_prefix() {
let config = RedisConfig {
prefix: "app:".to_string(),
..RedisConfig::default()
};
let driver = RedisCacheDriver::new(config);
driver.set_raw("key1", b"v1".to_vec(), None).unwrap();
let tag_key = driver.get_tag_key("mytag");
driver.tag_append(&tag_key, "app:key1").unwrap();
let items = driver.tag_items("mytag").unwrap();
assert_eq!(items, vec!["app:key1"]);
driver.tag_clear(&items).unwrap();
assert!(!driver.has("key1").unwrap());
}
#[test]
fn test_redis_tag_clear_empty_no_error() {
let driver = RedisCacheDriver::new(RedisConfig::default());
driver.tag_clear(&[]).unwrap();
}
#[test]
fn test_tagset_set_stores_value_and_appends_tag() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.tag("user").set("user:1", "Alice", None).unwrap();
assert_eq!(
cache.get::<String>("user:1").unwrap(),
Some("Alice".to_string())
);
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
let items = driver.tag_items("user").unwrap();
assert_eq!(items, vec!["user:1"]);
}
#[test]
fn test_tagset_set_multiple_keys_same_tag() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.tag("user").set("user:1", "Alice", None).unwrap();
cache.tag("user").set("user:2", "Bob", None).unwrap();
cache.tag("user").set("user:3", "Carol", None).unwrap();
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
let items = driver.tag_items("user").unwrap();
assert_eq!(items, vec!["user:1", "user:2", "user:3"]);
}
#[test]
fn test_tagset_clear_deletes_all_tagged_keys() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.tag("user").set("user:1", "Alice", None).unwrap();
cache.tag("user").set("user:2", "Bob", None).unwrap();
cache.tag("user").set("user:3", "Carol", None).unwrap();
cache.tag("user").clear().unwrap();
assert!(cache.get::<String>("user:1").unwrap().is_none());
assert!(cache.get::<String>("user:2").unwrap().is_none());
assert!(cache.get::<String>("user:3").unwrap().is_none());
}
#[test]
fn test_tagset_clear_deletes_tag_key() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.tag("user").set("user:1", "Alice", None).unwrap();
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
let tag_key = driver.get_tag_key("user");
assert!(driver.has(&tag_key).unwrap());
drop(mgr);
cache.tag("user").clear().unwrap();
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
assert!(!driver.has(&tag_key).unwrap());
}
#[test]
fn test_tagset_clear_empty_tag_no_error() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.tag("empty").clear().unwrap();
}
#[test]
fn test_tagset_append_adds_key_to_tag() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("user:1", "Alice", None).unwrap();
cache.tag("user").append("user:1").unwrap();
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
let items = driver.tag_items("user").unwrap();
assert_eq!(items, vec!["user:1"]);
}
#[test]
fn test_tagset_many_tags_single_key() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache
.tag_many(&["user", "admin"])
.set("key1", "val", None)
.unwrap();
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
let user_items = driver.tag_items("user").unwrap();
let admin_items = driver.tag_items("admin").unwrap();
assert_eq!(user_items, vec!["key1"]);
assert_eq!(admin_items, vec!["key1"]);
}
#[test]
fn test_tagset_many_tags_clear_one() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache
.tag_many(&["user", "admin"])
.set("key1", "val", None)
.unwrap();
cache.tag("user").clear().unwrap();
assert!(cache.get::<String>("key1").unwrap().is_none());
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
let admin_items = driver.tag_items("admin").unwrap();
assert_eq!(admin_items, vec!["key1"]); let user_tag_key = driver.get_tag_key("user");
assert!(!driver.has(&user_tag_key).unwrap());
}
#[test]
fn test_tagset_tags_getter() {
let cache = Cache::new();
let ts = cache.tag_many(&["a", "b", "c"]);
assert_eq!(ts.tags(), &["a", "b", "c"]);
}
#[test]
fn test_redis_tagset_set_stores_value_and_appends_tag() {
let cache = Cache::new();
let driver = RedisCacheDriver::new(RedisConfig::default());
cache.register_store("redis", Box::new(driver));
cache.tag("user").set("user:1", "Alice", None).unwrap();
assert_eq!(
cache.get::<String>("user:1").unwrap(),
Some("Alice".to_string())
);
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
let items = driver.tag_items("user").unwrap();
assert_eq!(items, vec!["user:1"]);
}
#[test]
fn test_redis_tagset_set_with_prefix() {
let cache = Cache::new();
let config = RedisConfig {
prefix: "app:".to_string(),
..RedisConfig::default()
};
let driver = RedisCacheDriver::new(config);
cache.register_store("redis", Box::new(driver));
cache.tag("user").set("user:1", "Alice", None).unwrap();
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
let items = driver.tag_items("user").unwrap();
assert_eq!(items, vec!["app:user:1"]);
}
#[test]
fn test_redis_tagset_clear_deletes_all_tagged_keys() {
let cache = Cache::new();
let driver = RedisCacheDriver::new(RedisConfig::default());
cache.register_store("redis", Box::new(driver));
cache.tag("user").set("user:1", "Alice", None).unwrap();
cache.tag("user").set("user:2", "Bob", None).unwrap();
cache.tag("user").set("user:3", "Carol", None).unwrap();
cache.tag("user").clear().unwrap();
assert!(cache.get::<String>("user:1").unwrap().is_none());
assert!(cache.get::<String>("user:2").unwrap().is_none());
assert!(cache.get::<String>("user:3").unwrap().is_none());
}
#[test]
fn test_redis_tagset_clear_deletes_tag_key() {
let cache = Cache::new();
let driver = RedisCacheDriver::new(RedisConfig::default());
cache.register_store("redis", Box::new(driver));
cache.tag("user").set("user:1", "Alice", None).unwrap();
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
let tag_key = driver.get_tag_key("user");
assert!(driver.has(&tag_key).unwrap());
drop(mgr);
cache.tag("user").clear().unwrap();
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
assert!(!driver.has(&tag_key).unwrap());
}
#[test]
fn test_redis_tagset_many_tags() {
let cache = Cache::new();
let driver = RedisCacheDriver::new(RedisConfig::default());
cache.register_store("redis", Box::new(driver));
cache
.tag_many(&["user", "admin"])
.set("key1", "val", None)
.unwrap();
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
let user_items = driver.tag_items("user").unwrap();
let admin_items = driver.tag_items("admin").unwrap();
assert_eq!(user_items, vec!["key1"]);
assert_eq!(admin_items, vec!["key1"]);
}
#[test]
fn test_r5_php_tag_set_then_clear() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.tag("user").set("u1", "Alice", None).unwrap();
cache.tag("user").set("u2", "Bob", None).unwrap();
cache.set("other", "data", None).unwrap();
cache.tag("user").clear().unwrap();
assert!(cache.get::<String>("u1").unwrap().is_none());
assert!(cache.get::<String>("u2").unwrap().is_none());
assert_eq!(
cache.get::<String>("other").unwrap(),
Some("data".to_string())
);
}
#[test]
fn test_r5_php_tag_multiple_tags_clear() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache
.tag_many(&["user", "admin"])
.set("key1", "v1", None)
.unwrap();
cache.tag("user").set("key2", "v2", None).unwrap();
cache.tag("user").clear().unwrap();
assert!(cache.get::<String>("key1").unwrap().is_none());
assert!(cache.get::<String>("key2").unwrap().is_none());
}
#[test]
fn test_r5_php_tag_get_cache_key_prefix() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
assert_eq!(driver.get_cache_key("test"), "test");
}
#[test]
fn test_r5_php_tag_get_tag_key_md5() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
assert_eq!(
driver.get_tag_key("hello"),
"tag:5d41402abc4b2a76b9719d911017c592"
);
}
#[test]
fn test_r5_php_tag_push_max_1000_array_shift() {
let driver = MemoryCacheDriver::new();
for i in 0..1005i64 {
driver.tag_append("tag:test", &format!("key{}", i)).unwrap();
}
let storage_key = "tag:test";
let raw = driver.get_raw(storage_key).unwrap();
let stored: Vec<String> = serde_json::from_slice(&raw.unwrap()).unwrap();
assert_eq!(stored.len(), 1000);
assert!(!stored.contains(&"key0".to_string()));
assert!(!stored.contains(&"key4".to_string()));
assert!(stored.contains(&"key5".to_string()));
assert!(stored.contains(&"key1004".to_string()));
}
#[test]
fn test_r5_php_tag_push_array_unique() {
let driver = MemoryCacheDriver::new();
driver.tag_append("tag:u", "a").unwrap();
driver.tag_append("tag:u", "b").unwrap();
driver.tag_append("tag:u", "a").unwrap(); driver.tag_append("tag:u", "c").unwrap();
driver.tag_append("tag:u", "b").unwrap();
let storage_key = "tag:u";
let raw = driver.get_raw(storage_key).unwrap();
let stored: Vec<String> = serde_json::from_slice(&raw.unwrap()).unwrap();
assert_eq!(stored, vec!["a", "b", "c"]);
}
#[test]
fn test_r5_php_tag_singleton_equivalent() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.tag("user").set("u1", "Alice", None).unwrap();
cache.tag("user").set("u2", "Bob", None).unwrap();
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
let items = driver.tag_items("user").unwrap();
assert_eq!(items, vec!["u1", "u2"]);
cache.tag("user").clear().unwrap();
assert!(cache.get::<String>("u1").unwrap().is_none());
assert!(cache.get::<String>("u2").unwrap().is_none());
}
#[test]
fn test_r5_php_tag_clear_then_set_again() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.tag("user").set("u1", "Alice", None).unwrap();
cache.tag("user").clear().unwrap();
assert!(cache.get::<String>("u1").unwrap().is_none());
cache.tag("user").set("u1", "Alice2", None).unwrap();
assert_eq!(
cache.get::<String>("u1").unwrap(),
Some("Alice2".to_string())
);
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
let items = driver.tag_items("user").unwrap();
assert_eq!(items, vec!["u1"]);
}
#[test]
fn test_r5_php_tag_redis_set_then_clear() {
let cache = Cache::new();
let driver = RedisCacheDriver::new(RedisConfig::default());
cache.register_store("redis", Box::new(driver));
cache.tag("article").set("a:1", "Hello", None).unwrap();
cache.tag("article").set("a:2", "World", None).unwrap();
cache.set("untagged", "data", None).unwrap();
cache.tag("article").clear().unwrap();
assert!(cache.get::<String>("a:1").unwrap().is_none());
assert!(cache.get::<String>("a:2").unwrap().is_none());
assert_eq!(
cache.get::<String>("untagged").unwrap(),
Some("data".to_string())
);
}
#[test]
fn test_r5_php_tag_redis_with_prefix() {
let cache = Cache::new();
let config = RedisConfig {
prefix: "myapp:".to_string(),
tag_prefix: "tag:".to_string(),
..RedisConfig::default()
};
let driver = RedisCacheDriver::new(config);
cache.register_store("redis", Box::new(driver));
cache.tag("user").set("u1", "Alice", None).unwrap();
let mgr = cache.manager.read();
let driver = mgr.default_store().unwrap();
let items = driver.tag_items("user").unwrap();
assert_eq!(items, vec!["myapp:u1"]);
drop(mgr);
cache.tag("user").clear().unwrap();
assert!(cache.get::<String>("u1").unwrap().is_none());
}
#[test]
fn test_r5_php_tag_different_tags_isolation() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.tag("user").set("u1", "Alice", None).unwrap();
cache.tag("article").set("a1", "Hello", None).unwrap();
cache.tag("user").clear().unwrap();
assert!(cache.get::<String>("u1").unwrap().is_none());
assert_eq!(
cache.get::<String>("a1").unwrap(),
Some("Hello".to_string())
);
}
#[test]
fn test_r5_php_tag_set_with_ttl() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache
.tag("user")
.set("u1", "Alice", Some(Duration::from_millis(50)))
.unwrap();
assert_eq!(
cache.get::<String>("u1").unwrap(),
Some("Alice".to_string())
);
std::thread::sleep(Duration::from_millis(60));
assert!(cache.get::<String>("u1").unwrap().is_none());
}
#[test]
fn test_delete_many_multiple_keys() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("k1", "v1", None).unwrap();
cache.set("k2", "v2", None).unwrap();
cache.set("k3", "v3", None).unwrap();
cache.delete_many(&["k1", "k2", "k3"]).unwrap();
assert!(cache.get::<String>("k1").unwrap().is_none());
assert!(cache.get::<String>("k2").unwrap().is_none());
assert!(cache.get::<String>("k3").unwrap().is_none());
}
#[test]
fn test_delete_many_nonexistent_keys_ok() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("exists", "v", None).unwrap();
let result = cache.delete_many(&["exists", "nonexistent"]);
assert!(result.is_ok());
}
#[test]
fn test_delete_many_empty_slice() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
let result = cache.delete_many(&[]);
assert!(result.is_ok());
}
#[test]
fn test_delete_many_partial_delete_before_failure() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("a", "1", None).unwrap();
cache.set("b", "2", None).unwrap();
cache.delete_many(&["a", "b"]).unwrap();
assert!(cache.get::<String>("a").unwrap().is_none());
assert!(cache.get::<String>("b").unwrap().is_none());
}
#[test]
fn test_invalidate_after_write_basic() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("user:1", "Alice", None).unwrap();
assert_eq!(
cache.get::<String>("user:1").unwrap(),
Some("Alice".to_string())
);
cache.invalidate_after_write(&["user:1"]).unwrap();
assert!(cache.get::<String>("user:1").unwrap().is_none());
}
#[test]
fn test_invalidate_after_write_multiple_keys() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("sdp_category_tree", "t1", None).unwrap();
cache.set("sdp_category_select", "s1", None).unwrap();
cache.set("sdp_category_child", "c1", None).unwrap();
cache
.invalidate_after_write(&[
"sdp_category_tree",
"sdp_category_select",
"sdp_category_child",
])
.unwrap();
assert!(cache.get::<String>("sdp_category_tree").unwrap().is_none());
assert!(cache
.get::<String>("sdp_category_select")
.unwrap()
.is_none());
assert!(cache.get::<String>("sdp_category_child").unwrap().is_none());
}
#[test]
fn test_invalidate_after_write_fire_and_forget() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("clerk:1", "data", None).unwrap();
let _ = cache.invalidate_after_write(&["clerk:1"]);
assert!(cache.get::<String>("clerk:1").unwrap().is_none());
}
#[test]
fn test_refresh_force_update() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("store:1", "old_data", None).unwrap();
let result: String = cache
.refresh("store:1", None, || Ok("new_data".to_string()))
.unwrap();
assert_eq!(result, "new_data");
assert_eq!(
cache.get::<String>("store:1").unwrap(),
Some("new_data".to_string())
);
}
#[test]
fn test_refresh_fetcher_error_no_write() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("key", "original", None).unwrap();
let result: Result<String, CacheError> = cache.refresh("key", None, || {
Err(CacheError::SerializationError("fetch failed".to_string()))
});
assert!(result.is_err());
assert!(cache.get::<String>("key").unwrap().is_none());
}
#[test]
fn test_refresh_ttl_propagation() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
let _result: String = cache
.refresh("ttl_key", Some(Duration::from_millis(50)), || {
Ok("value".to_string())
})
.unwrap();
assert_eq!(
cache.get::<String>("ttl_key").unwrap(),
Some("value".to_string())
);
std::thread::sleep(Duration::from_millis(60));
assert!(cache.get::<String>("ttl_key").unwrap().is_none());
}
#[test]
fn test_refresh_returns_fetcher_value() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("counter", "old_value", None).unwrap();
let result: String = cache
.refresh("counter", None, || Ok("new_value".to_string()))
.unwrap();
assert_eq!(result, "new_value");
assert_eq!(
cache.get::<String>("counter").unwrap(),
Some("new_value".to_string())
);
}
#[test]
fn test_r5_php_delete_multiple_semantics() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("a", "1", None).unwrap();
cache.set("b", "2", None).unwrap();
cache.set("c", "3", None).unwrap();
let result = cache.delete_many(&["a", "b", "c"]);
assert!(result.is_ok());
assert!(cache.get::<String>("a").unwrap().is_none());
assert!(cache.get::<String>("b").unwrap().is_none());
assert!(cache.get::<String>("c").unwrap().is_none());
}
#[test]
fn test_r5_php_invalidate_after_write_pattern() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache
.set("foodCashierClerkAll_1", vec!["clerk1"], None)
.unwrap();
let write_success = true;
if write_success {
cache
.invalidate_after_write(&["foodCashierClerkAll_1"])
.unwrap();
}
assert!(cache
.get::<Vec<String>>("foodCashierClerkAll_1")
.unwrap()
.is_none());
}
#[test]
fn test_r5_php_refresh_pattern() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache
.set("wmall_store_info_1", "old_store_data", None)
.unwrap();
let result: String = cache
.refresh("wmall_store_info_1", None, || {
Ok("fresh_store_data".to_string())
})
.unwrap();
assert_eq!(result, "fresh_store_data");
assert_eq!(
cache.get::<String>("wmall_store_info_1").unwrap(),
Some("fresh_store_data".to_string())
);
}
#[test]
fn test_fetch_singleflight_cache_hit() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("hot", "cached_value", None).unwrap();
let called = Arc::new(Mutex::new(false));
let called_clone = called.clone();
let result: String = cache
.fetch_singleflight("hot", None, || {
*called_clone.lock() = true;
Ok("fetcher_value".to_string())
})
.unwrap();
assert_eq!(result, "cached_value");
assert!(!*called.lock(), "fetcher 不应被调用(缓存命中)");
}
#[test]
fn test_fetch_singleflight_cache_miss_invokes_fetcher() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
let result: String = cache
.fetch_singleflight("miss_key", None, || Ok("fetched".to_string()))
.unwrap();
assert_eq!(result, "fetched");
assert_eq!(
cache.get::<String>("miss_key").unwrap(),
Some("fetched".to_string())
);
}
#[test]
fn test_fetch_singleflight_concurrent_only_one_fetcher_call() {
let cache = Arc::new(Cache::new());
cache.register_default(MemoryCacheDriver::new());
let fetcher_call_count = Arc::new(Mutex::new(0u32));
let barrier = Arc::new(Barrier::new(4));
let results = Arc::new(Mutex::new(Vec::<String>::new()));
let mut handles = Vec::new();
for _ in 0..4 {
let cache_clone = Arc::clone(&cache);
let count_clone = Arc::clone(&fetcher_call_count);
let barrier_clone = Arc::clone(&barrier);
let results_clone = Arc::clone(&results);
handles.push(std::thread::spawn(move || {
barrier_clone.wait();
let value: String = cache_clone
.fetch_singleflight("concurrent_key", None, || {
std::thread::sleep(Duration::from_millis(50));
let mut count = count_clone.lock();
*count += 1;
Ok(format!("fetched_{}", *count))
})
.unwrap();
results_clone.lock().push(value);
}));
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(
*fetcher_call_count.lock(),
1,
"fetcher 应只调用一次(singleflight)"
);
let results = results.lock();
assert_eq!(results.len(), 4);
for value in results.iter() {
assert_eq!(value, "fetched_1");
}
}
#[test]
fn test_fetch_singleflight_fetcher_error_propagates() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
let result: Result<String, CacheError> = cache.fetch_singleflight("err_key", None, || {
Err(CacheError::SerializationError("fetcher failed".to_string()))
});
assert!(result.is_err());
assert!(cache.get::<String>("err_key").unwrap().is_none());
}
#[test]
fn test_set_with_jitter_basic() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache
.set_with_jitter(
"jitter_key",
"value",
Some(Duration::from_secs(60)),
Duration::from_secs(10),
)
.unwrap();
assert_eq!(
cache.get::<String>("jitter_key").unwrap(),
Some("value".to_string())
);
}
#[test]
fn test_set_with_jitter_zero_jitter_equivalent_to_set() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache
.set_with_jitter(
"no_jitter",
"value",
Some(Duration::from_secs(60)),
Duration::ZERO,
)
.unwrap();
assert_eq!(
cache.get::<String>("no_jitter").unwrap(),
Some("value".to_string())
);
}
#[test]
fn test_set_with_jitter_none_ttl_no_jitter() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache
.set_with_jitter("permanent", "value", None, Duration::from_secs(10))
.unwrap();
assert_eq!(
cache.get::<String>("permanent").unwrap(),
Some("value".to_string())
);
}
#[test]
fn test_set_with_jitter_ttl_in_expected_range() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
let base_ttl = Duration::from_millis(50);
let jitter = Duration::from_millis(100);
cache
.set_with_jitter("range_key", "value", Some(base_ttl), jitter)
.unwrap();
assert!(cache.get::<String>("range_key").unwrap().is_some());
std::thread::sleep(base_ttl + jitter + Duration::from_millis(20));
assert!(
cache.get::<String>("range_key").unwrap().is_none(),
"TTL 应在 [{:?}, {:?}] 范围内,已过期",
base_ttl,
base_ttl + jitter
);
}
#[test]
fn test_fetch_with_protection_cache_hit() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("protected", "cached", None).unwrap();
let called = Arc::new(Mutex::new(false));
let called_clone = called.clone();
let result: String = cache
.fetch_with_protection(
"protected",
Some(Duration::from_secs(60)),
Duration::from_secs(10),
|| {
*called_clone.lock() = true;
Ok("fetched".to_string())
},
)
.unwrap();
assert_eq!(result, "cached");
assert!(!*called.lock());
}
#[test]
fn test_fetch_with_protection_cache_miss_invokes_fetcher() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
let result: String = cache
.fetch_with_protection(
"miss_protected",
Some(Duration::from_secs(60)),
Duration::from_secs(10),
|| Ok("fetched_protected".to_string()),
)
.unwrap();
assert_eq!(result, "fetched_protected");
assert_eq!(
cache.get::<String>("miss_protected").unwrap(),
Some("fetched_protected".to_string())
);
}
#[test]
fn test_fetch_with_protection_concurrent_single_flight() {
let cache = Arc::new(Cache::new());
cache.register_default(MemoryCacheDriver::new());
let fetcher_call_count = Arc::new(Mutex::new(0u32));
let barrier = Arc::new(Barrier::new(4));
let results = Arc::new(Mutex::new(Vec::<String>::new()));
let mut handles = Vec::new();
for _ in 0..4 {
let cache_clone = Arc::clone(&cache);
let count_clone = Arc::clone(&fetcher_call_count);
let barrier_clone = Arc::clone(&barrier);
let results_clone = Arc::clone(&results);
handles.push(std::thread::spawn(move || {
barrier_clone.wait();
let value: String = cache_clone
.fetch_with_protection(
"concurrent_protected",
Some(Duration::from_secs(60)),
Duration::from_secs(10),
|| {
std::thread::sleep(Duration::from_millis(50));
let mut count = count_clone.lock();
*count += 1;
Ok(format!("value_{}", *count))
},
)
.unwrap();
results_clone.lock().push(value);
}));
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(*fetcher_call_count.lock(), 1, "fetcher 应只调用一次");
let results = results.lock();
assert_eq!(results.len(), 4);
for value in results.iter() {
assert_eq!(value, "value_1");
}
}
#[test]
fn test_fetch_with_protection_fetcher_error_propagates() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
let result: Result<String, CacheError> = cache.fetch_with_protection(
"err_protected",
Some(Duration::from_secs(60)),
Duration::from_secs(10),
|| Err(CacheError::SerializationError("failed".to_string())),
);
assert!(result.is_err());
assert!(cache.get::<String>("err_protected").unwrap().is_none());
}
#[test]
fn test_r5_php_remember_lock_vs_rust_singleflight() {
let cache = Arc::new(Cache::new());
cache.register_default(MemoryCacheDriver::new());
let call_count = Arc::new(Mutex::new(0u32));
let barrier = Arc::new(Barrier::new(3));
let results = Arc::new(Mutex::new(Vec::<String>::new()));
let mut handles = Vec::new();
for _ in 0..3 {
let cache_clone = Arc::clone(&cache);
let count_clone = Arc::clone(&call_count);
let barrier_clone = Arc::clone(&barrier);
let results_clone = Arc::clone(&results);
handles.push(std::thread::spawn(move || {
barrier_clone.wait();
let value: String = cache_clone
.fetch_singleflight("r5_compare_key", None, || {
std::thread::sleep(Duration::from_millis(30));
let mut count = count_clone.lock();
*count += 1;
Ok(format!("v_{}", *count))
})
.unwrap();
results_clone.lock().push(value);
}));
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(
*call_count.lock(),
1,
"Rust singleflight fetcher 应只调用一次"
);
let results = results.lock();
assert_eq!(results.len(), 3);
for value in results.iter() {
assert_eq!(value, "v_1");
}
}
#[test]
fn test_r5_php_no_jitter_vs_rust_jitter() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
let mut ttl_samples = Vec::new();
for i in 0..10 {
let key = format!("jitter_sample_{}", i);
cache
.set_with_jitter(
&key,
"value",
Some(Duration::from_secs(60)),
Duration::from_secs(10),
)
.unwrap();
let _ = cache.get::<String>(&key).unwrap();
ttl_samples.push(key);
}
for key in &ttl_samples {
assert_eq!(
cache.get::<String>(key).unwrap(),
Some("value".to_string()),
"所有带抖动 TTL 的 key 都应写入成功"
);
}
}
#[test]
fn test_r5_php_remember_no_double_check_vs_rust_double_check() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("double_check_key", "pre_cached", None).unwrap();
let called = Arc::new(Mutex::new(false));
let called_clone = called.clone();
let result: String = cache
.fetch_singleflight("double_check_key", None, || {
*called_clone.lock() = true;
Ok("fetched".to_string())
})
.unwrap();
assert_eq!(result, "pre_cached");
assert!(
!*called.lock(),
"double-check 应命中预缓存,fetcher 不被调用"
);
}
#[test]
fn test_cache_thundering_herd_singleflight_collapses() {
let cache = Arc::new(Cache::new());
cache.register_default(MemoryCacheDriver::new());
let call_count = Arc::new(Mutex::new(0u32));
let num_tasks = 50;
let barrier = Arc::new(Barrier::new(num_tasks));
let mut handles = Vec::new();
for _ in 0..num_tasks {
let cache = Arc::clone(&cache);
let cc = Arc::clone(&call_count);
let barrier = Arc::clone(&barrier);
handles.push(std::thread::spawn(move || {
barrier.wait(); let result: String = cache
.fetch_singleflight("herd_key", None, || {
*cc.lock() += 1;
std::thread::sleep(Duration::from_millis(10));
Ok("single_source".to_string())
})
.unwrap();
result
}));
}
let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
for r in &results {
assert_eq!(r, "single_source");
}
let calls = *call_count.lock();
assert_eq!(
calls, 1,
"P3-CHAOS-01: 50 并发请求应合并为 1 次实际回源,实际 {} 次",
calls
);
}
#[test]
fn test_cache_slow_backend_timeout_degradation() {
let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
let (tx, rx) = std::sync::mpsc::channel::<String>();
let handle = std::thread::spawn(move || {
let result: String = cache
.fetch_singleflight("slow_key", None, || {
std::thread::sleep(Duration::from_millis(500));
Ok("slow_value".to_string())
})
.unwrap();
let _ = tx.send(result);
});
let early = rx.recv_timeout(Duration::from_millis(200));
assert!(
early.is_err(),
"P3-CHAOS-02: 慢回源(500ms)在 200ms 内不应完成,调用方应已触发超时降级"
);
handle.join().unwrap();
let final_val = rx.recv_timeout(Duration::from_secs(2)).unwrap();
assert_eq!(
final_val, "slow_value",
"P3-CHAOS-02: 慢回源完成后结果应正确传播"
);
}
#[test]
fn test_cache_ttl_eviction_under_concurrent_load() {
use std::time::Duration;
let cache = Arc::new(Cache::new());
cache.register_default(MemoryCacheDriver::new());
let num_writers = 16;
let writes_per_writer = 100;
let ttl = Duration::from_millis(50);
let barrier = Arc::new(Barrier::new(num_writers));
let mut handles = Vec::new();
for w in 0..num_writers {
let cache = Arc::clone(&cache);
let barrier = Arc::clone(&barrier);
handles.push(std::thread::spawn(move || {
barrier.wait(); for i in 0..writes_per_writer {
let key = format!("ttl_key_{}", i % 20); let val = format!("writer{}_val{}", w, i);
let _ = cache.set(&key, &val, Some(ttl));
}
}));
}
for h in handles {
h.join().unwrap();
}
std::thread::sleep(ttl * 3);
for i in 0..20 {
let key = format!("ttl_key_{}", i);
let val: Option<String> = cache.get(&key).unwrap();
assert!(
val.is_none(),
"P3-CHAOS-03: key={} 应在 TTL 过期后为 None,实际 {:?}",
key,
val
);
}
cache.set("post_eviction_key", "fresh", None).unwrap();
let val: Option<String> = cache.get("post_eviction_key").unwrap();
assert_eq!(val, Some("fresh".to_string()));
}
}