#![cfg(feature = "redis")]
#![allow(non_snake_case)]
use redis::{AsyncCommands, Client};
use redis::aio::ConnectionManager;
use std::sync::Arc;
use std::ops::AddAssign;
use crate::cache::{RiCache, RiCacheStats};
use crate::core::RiResult;
pub struct RiRedisCache {
connection: Arc<ConnectionManager>,
stats: Arc<dashmap::DashMap<&'static str, u64>>,
}
impl RiRedisCache {
pub async fn new(redis_url: &str) -> crate::core::RiResult<Self> {
let client = Client::open(redis_url)
.map_err(|e| crate::core::RiError::Other(format!("Redis client error: {e}")))?;
let connection = ConnectionManager::new(client).await
.map_err(|e| crate::core::RiError::Other(format!("Redis connection error: {e}")))?;
let stats = dashmap::DashMap::new();
stats.insert("hit_count", 0);
stats.insert("miss_count", 0);
stats.insert("error_count", 0);
Ok(Self {
connection: Arc::new(connection),
stats: Arc::new(stats),
})
}
}
#[async_trait::async_trait]
impl RiCache for RiRedisCache {
async fn get(&self, key: &str) -> RiResult<Option<String>> {
let mut conn = (*self.connection).clone();
let result: redis::RedisResult<String> = conn.get(key).await;
match result {
Ok(json_str) => {
let json_str_owned = json_str.to_owned();
if let Ok(value) = serde_json::from_str::<serde_json::Value>(&json_str_owned) {
if let Some(str_value) = value.as_str() {
if let Some(mut hit_count) = self.stats.get_mut("hit_count") {
hit_count.value_mut().add_assign(1);
}
Ok(Some(str_value.to_string()))
} else {
if let Some(mut error_count) = self.stats.get_mut("error_count") {
error_count.value_mut().add_assign(1);
}
Ok(None)
}
} else {
if let Some(mut hit_count) = self.stats.get_mut("hit_count") {
hit_count.value_mut().add_assign(1);
}
Ok(Some(json_str_owned))
}
}
Err(_) => {
if let Some(mut miss_count) = self.stats.get_mut("miss_count") {
miss_count.value_mut().add_assign(1);
}
Ok(None)
}
}
}
async fn set(&self, key: &str, value: &str, ttl_seconds: Option<u64>) -> crate::core::RiResult<()> {
let mut conn = (*self.connection).clone();
let result: redis::RedisResult<()> = match ttl_seconds {
Some(ttl_secs) => {
conn.set_ex(key, value, ttl_secs).await
}
None => {
conn.set(key, value).await
}
};
result.map_err(|e| crate::core::RiError::Other(format!("Redis set error: {e}")))?;
Ok(())
}
async fn delete(&self, key: &str) -> crate::core::RiResult<bool> {
let mut conn = (*self.connection).clone();
let result: redis::RedisResult<bool> = conn.del(key).await;
result.map_err(|e| crate::core::RiError::Other(format!("Redis delete error: {e}")))
}
async fn exists(&self, key: &str) -> bool {
let mut conn = (*self.connection).clone();
let result: redis::RedisResult<bool> = conn.exists(key).await;
result.unwrap_or_default()
}
async fn keys(&self) -> crate::core::RiResult<Vec<String>> {
let mut conn = (*self.connection).clone();
let pattern = "ri:cache:*";
let mut all_keys = Vec::new();
let mut cursor: u64 = 0;
loop {
let result: redis::RedisResult<(u64, Vec<String>)> = redis::cmd("SCAN")
.arg(cursor)
.arg("MATCH")
.arg(pattern)
.arg("COUNT")
.arg(100)
.query_async(&mut conn)
.await;
match result {
Ok((new_cursor, keys)) => {
all_keys.extend(keys);
cursor = new_cursor;
if cursor == 0 {
break;
}
}
Err(e) => {
return Err(crate::core::RiError::Other(format!("Redis scan error: {e}")));
}
}
}
Ok(all_keys)
}
async fn clear(&self) -> crate::core::RiResult<()> {
let mut conn = (*self.connection).clone();
let pattern = "ri:cache:*";
let mut cursor: u64 = 0;
let mut total_deleted = 0;
loop {
let result: redis::RedisResult<(u64, Vec<String>)> = redis::cmd("SCAN")
.arg(cursor)
.arg("MATCH")
.arg(pattern)
.arg("COUNT")
.arg(100)
.query_async(&mut conn)
.await;
match result {
Ok((new_cursor, keys)) => {
if !keys.is_empty() {
conn.del::<_, ()>(&keys).await
.map_err(|e| crate::core::RiError::Other(format!("Redis clear error: {e}")))?;
total_deleted += keys.len();
}
cursor = new_cursor;
if cursor == 0 {
break;
}
}
Err(e) => {
return Err(crate::core::RiError::Other(format!("Redis scan error: {e}")));
}
}
}
log::debug!("[Ri.Redis] Cleared {} cache entries", total_deleted);
Ok(())
}
async fn stats(&self) -> RiCacheStats {
let hit_count = self.stats.get("hit_count")
.map(|entry| *entry.value())
.unwrap_or(0);
let miss_count = self.stats.get("miss_count")
.map(|entry| *entry.value())
.unwrap_or(0);
let error_count = self.stats.get("error_count")
.map(|entry| *entry.value())
.unwrap_or(0);
let total_requests = hit_count + miss_count;
let avg_hit_rate = if total_requests > 0 {
hit_count as f64 / total_requests as f64
} else {
0.0
};
let total_keys = match redis::cmd("DBSIZE").query_async::<_, u64>(&mut (*self.connection).clone()).await {
Ok(size) => size as usize,
Err(_) => 0,
};
RiCacheStats {
hits: hit_count,
misses: miss_count,
entries: total_keys,
memory_usage_bytes: 0, avg_hit_rate,
hit_count,
miss_count,
eviction_count: error_count,
}
}
async fn cleanup_expired(&self) -> crate::core::RiResult<usize> {
Ok(0)
}
}