pub use sz_rust_orm_facade::l2_cache::{CacheKey, CacheKeyKind, L2Cache, L2CacheStats};
use std::collections::HashMap;
use std::time::Duration;
use sz_rust_orm_facade::Value;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct WithCacheConfig {
pub key: Option<String>,
pub expire: Option<Duration>,
pub tag: Option<String>,
}
impl WithCacheConfig {
pub fn new(key: Option<String>, expire: Option<Duration>, tag: Option<String>) -> Self {
Self { key, expire, tag }
}
pub fn is_auto_key(&self) -> bool {
self.key.is_none()
}
pub fn is_permanent(&self) -> bool {
self.expire.is_none()
}
pub fn is_default_tag(&self) -> bool {
self.tag.is_none()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum WithCacheOption {
#[default]
None,
All,
Specific(HashMap<String, WithCacheConfig>),
}
impl WithCacheOption {
pub fn is_enabled(&self) -> bool {
!matches!(self, WithCacheOption::None)
}
pub fn is_all(&self) -> bool {
matches!(self, WithCacheOption::All)
}
pub fn is_specific(&self) -> bool {
matches!(self, WithCacheOption::Specific(_))
}
pub fn get_config(&self, relation_name: &str) -> Option<&WithCacheConfig> {
match self {
WithCacheOption::All => Some(&DEFAULT_ALL_CONFIG),
WithCacheOption::Specific(map) => map.get(relation_name),
WithCacheOption::None => None,
}
}
}
const DEFAULT_ALL_CONFIG: WithCacheConfig = WithCacheConfig {
key: None,
expire: None,
tag: None,
};
pub fn php_with_cache_config(
key: Option<&str>,
expire: Option<Duration>,
tag: Option<&str>,
) -> WithCacheConfig {
WithCacheConfig {
key: key.map(|s| s.to_string()),
expire,
tag: tag.map(|s| s.to_string()),
}
}
pub fn php_relation_cache_key(database: &str, table: &str, key: &str) -> String {
format!("think_{}.{}|{}", database, table, key)
}
pub fn php_relation_cache_tag(table: &str, custom_tag: Option<&str>) -> String {
match custom_tag {
Some(t) if !t.is_empty() => t.to_string(),
_ => table.to_string(),
}
}
pub fn php_relation_cache_remember(
cache: &L2Cache,
key: &CacheKey,
value: Value,
ttl: Option<Duration>,
) {
cache.put(key, value, ttl);
}
pub fn php_relation_cache_fetch(cache: &L2Cache, key: &CacheKey) -> Option<Value> {
cache.get(key)
}
pub fn php_relation_cache_invalidate(cache: &L2Cache, table: &str) {
cache.invalidate_table(table);
}
pub fn php_relation_cache_delete(cache: &L2Cache, key: &CacheKey) {
cache.invalidate(key);
}
#[cfg(test)]
mod tests {
use super::*;
use sz_rust_orm_facade::Value;
#[test]
fn test_with_cache_config_default() {
let config = WithCacheConfig::default();
assert_eq!(config.key, None);
assert_eq!(config.expire, None);
assert_eq!(config.tag, None);
}
#[test]
fn test_with_cache_config_new() {
let config = WithCacheConfig::new(
Some("custom_key".to_string()),
Some(Duration::from_secs(3600)),
Some("custom_tag".to_string()),
);
assert_eq!(config.key, Some("custom_key".to_string()));
assert_eq!(config.expire, Some(Duration::from_secs(3600)));
assert_eq!(config.tag, Some("custom_tag".to_string()));
}
#[test]
fn test_with_cache_config_is_auto_key() {
let config = WithCacheConfig::default();
assert!(config.is_auto_key());
let config = WithCacheConfig::new(Some("custom".to_string()), None, None);
assert!(!config.is_auto_key());
}
#[test]
fn test_with_cache_config_is_permanent() {
let config = WithCacheConfig::default();
assert!(config.is_permanent());
let config = WithCacheConfig::new(None, Some(Duration::from_secs(60)), None);
assert!(!config.is_permanent());
}
#[test]
fn test_with_cache_config_is_default_tag() {
let config = WithCacheConfig::default();
assert!(config.is_default_tag());
let config = WithCacheConfig::new(None, None, Some("custom_tag".to_string()));
assert!(!config.is_default_tag());
}
#[test]
fn test_with_cache_option_default_is_none() {
let opt = WithCacheOption::default();
assert!(matches!(opt, WithCacheOption::None));
}
#[test]
fn test_with_cache_option_all_is_enabled() {
let opt = WithCacheOption::All;
assert!(opt.is_enabled());
assert!(opt.is_all());
assert!(!opt.is_specific());
}
#[test]
fn test_with_cache_option_specific_is_enabled() {
let mut map = HashMap::new();
map.insert("orders".to_string(), WithCacheConfig::default());
let opt = WithCacheOption::Specific(map);
assert!(opt.is_enabled());
assert!(!opt.is_all());
assert!(opt.is_specific());
}
#[test]
fn test_with_cache_option_none_is_not_enabled() {
let opt = WithCacheOption::None;
assert!(!opt.is_enabled());
assert!(!opt.is_all());
assert!(!opt.is_specific());
}
#[test]
fn test_with_cache_option_get_config_all() {
let opt = WithCacheOption::All;
let config = opt.get_config("any_relation").unwrap();
assert_eq!(config.key, None);
assert_eq!(config.expire, None);
assert_eq!(config.tag, None);
}
#[test]
fn test_with_cache_option_get_config_specific_hit() {
let mut map = HashMap::new();
map.insert(
"orders".to_string(),
WithCacheConfig::new(None, Some(Duration::from_secs(3600)), None),
);
let opt = WithCacheOption::Specific(map);
let config = opt.get_config("orders").unwrap();
assert_eq!(config.expire, Some(Duration::from_secs(3600)));
}
#[test]
fn test_with_cache_option_get_config_specific_miss_and_none() {
let mut map = HashMap::new();
map.insert("orders".to_string(), WithCacheConfig::default());
let opt = WithCacheOption::Specific(map);
assert!(opt.get_config("nonexistent").is_none());
let opt = WithCacheOption::None;
assert!(opt.get_config("any").is_none());
}
#[test]
fn test_php_with_cache_config_default() {
let config = php_with_cache_config(None, None, None);
assert_eq!(config.key, None);
assert_eq!(config.expire, None);
assert_eq!(config.tag, None);
}
#[test]
fn test_php_with_cache_config_with_expire() {
let config = php_with_cache_config(None, Some(Duration::from_secs(3600)), None);
assert_eq!(config.key, None);
assert_eq!(config.expire, Some(Duration::from_secs(3600)));
assert_eq!(config.tag, None);
}
#[test]
fn test_php_with_cache_config_with_custom_key() {
let config = php_with_cache_config(Some("custom_key"), None, None);
assert_eq!(config.key, Some("custom_key".to_string()));
}
#[test]
fn test_php_with_cache_config_with_custom_tag() {
let config = php_with_cache_config(None, None, Some("user_cache"));
assert_eq!(config.tag, Some("user_cache".to_string()));
}
#[test]
fn test_php_with_cache_config_full() {
let config =
php_with_cache_config(Some("key1"), Some(Duration::from_secs(3600)), Some("tag1"));
assert_eq!(config.key, Some("key1".to_string()));
assert_eq!(config.expire, Some(Duration::from_secs(3600)));
assert_eq!(config.tag, Some("tag1".to_string()));
}
#[test]
fn test_php_relation_cache_key_basic() {
let key = php_relation_cache_key("shop", "users", "1");
assert_eq!(key, "think_shop.users|1");
}
#[test]
fn test_php_relation_cache_key_different_databases() {
let key1 = php_relation_cache_key("shop", "users", "1");
let key2 = php_relation_cache_key("admin", "users", "1");
assert_ne!(key1, key2);
}
#[test]
fn test_php_relation_cache_key_different_tables() {
let key1 = php_relation_cache_key("shop", "users", "1");
let key2 = php_relation_cache_key("shop", "orders", "1");
assert_ne!(key1, key2);
}
#[test]
fn test_php_relation_cache_key_different_pk() {
let key1 = php_relation_cache_key("shop", "users", "1");
let key2 = php_relation_cache_key("shop", "users", "2");
assert_ne!(key1, key2);
}
#[test]
fn test_php_relation_cache_key_format() {
let key = php_relation_cache_key("my_db", "my_table", "my_key");
assert_eq!(key, "think_my_db.my_table|my_key");
assert!(key.starts_with("think_"));
assert!(key.contains("."));
assert!(key.contains("|"));
}
#[test]
fn test_php_relation_cache_tag_default() {
let tag = php_relation_cache_tag("users", None);
assert_eq!(tag, "users");
}
#[test]
fn test_php_relation_cache_tag_custom() {
let tag = php_relation_cache_tag("users", Some("user_cache"));
assert_eq!(tag, "user_cache");
}
#[test]
fn test_php_relation_cache_tag_empty_string_uses_table() {
let tag = php_relation_cache_tag("users", Some(""));
assert_eq!(tag, "users");
}
#[test]
fn test_php_relation_cache_tag_different_tables() {
let tag1 = php_relation_cache_tag("users", None);
let tag2 = php_relation_cache_tag("orders", None);
assert_ne!(tag1, tag2);
}
#[test]
fn test_php_relation_cache_remember_and_fetch_hit() {
let cache = L2Cache::new();
let key = CacheKey::by_relation("users", "orders:1");
php_relation_cache_remember(&cache, &key, Value::I64(42), None);
let val = php_relation_cache_fetch(&cache, &key);
assert_eq!(val, Some(Value::I64(42)));
}
#[test]
fn test_php_relation_cache_fetch_miss() {
let cache = L2Cache::new();
let key = CacheKey::by_relation("users", "orders:1");
let val = php_relation_cache_fetch(&cache, &key);
assert_eq!(val, None);
}
#[test]
fn test_php_relation_cache_invalidate_table() {
let cache = L2Cache::new();
let key1 = CacheKey::by_relation("users", "orders:1");
let key2 = CacheKey::by_relation("users", "orders:2");
let key3 = CacheKey::by_relation("orders", "items:1");
php_relation_cache_remember(&cache, &key1, Value::I64(1), None);
php_relation_cache_remember(&cache, &key2, Value::I64(2), None);
php_relation_cache_remember(&cache, &key3, Value::I64(3), None);
php_relation_cache_invalidate(&cache, "users");
assert_eq!(php_relation_cache_fetch(&cache, &key1), None);
assert_eq!(php_relation_cache_fetch(&cache, &key2), None);
assert_eq!(php_relation_cache_fetch(&cache, &key3), Some(Value::I64(3)));
}
#[test]
fn test_php_relation_cache_delete_single() {
let cache = L2Cache::new();
let key1 = CacheKey::by_relation("users", "orders:1");
let key2 = CacheKey::by_relation("users", "orders:2");
php_relation_cache_remember(&cache, &key1, Value::I64(1), None);
php_relation_cache_remember(&cache, &key2, Value::I64(2), None);
php_relation_cache_delete(&cache, &key1);
assert_eq!(php_relation_cache_fetch(&cache, &key1), None);
assert_eq!(php_relation_cache_fetch(&cache, &key2), Some(Value::I64(2)));
}
#[test]
fn test_php_relation_cache_ttl_expiration() {
let cache = L2Cache::new();
let key = CacheKey::by_relation("users", "orders:1");
php_relation_cache_remember(
&cache,
&key,
Value::I64(42),
Some(Duration::from_millis(50)),
);
assert_eq!(php_relation_cache_fetch(&cache, &key), Some(Value::I64(42)));
std::thread::sleep(Duration::from_millis(100));
assert_eq!(php_relation_cache_fetch(&cache, &key), None);
}
#[test]
fn test_php_relation_cache_multiple_relations() {
let cache = L2Cache::new();
let orders_key = CacheKey::by_relation("users", "orders:1");
let profile_key = CacheKey::by_relation("users", "profile:1");
php_relation_cache_remember(&cache, &orders_key, Value::I64(10), None);
php_relation_cache_remember(
&cache,
&profile_key,
Value::String("Alice".to_string()),
None,
);
assert_eq!(
php_relation_cache_fetch(&cache, &orders_key),
Some(Value::I64(10))
);
assert_eq!(
php_relation_cache_fetch(&cache, &profile_key),
Some(Value::String("Alice".to_string()))
);
}
#[test]
fn test_php_relation_cache_table_isolation() {
let cache = L2Cache::new();
let users_key = CacheKey::by_relation("users", "pk:1");
let orders_key = CacheKey::by_relation("orders", "pk:1");
php_relation_cache_remember(&cache, &users_key, Value::I64(1), None);
php_relation_cache_remember(&cache, &orders_key, Value::I64(2), None);
php_relation_cache_invalidate(&cache, "users");
assert_eq!(php_relation_cache_fetch(&cache, &users_key), None);
assert_eq!(
php_relation_cache_fetch(&cache, &orders_key),
Some(Value::I64(2))
);
}
#[test]
fn test_with_cache_option_all_integration() {
let opt = WithCacheOption::All;
let cache = L2Cache::new();
for relation_name in &["orders", "profile", "comments"] {
let config = opt.get_config(relation_name).unwrap();
let key = CacheKey::by_relation("users", format!("{}:1", relation_name));
let ttl = config.expire;
php_relation_cache_remember(&cache, &key, Value::I64(1), ttl);
}
for relation_name in &["orders", "profile", "comments"] {
let key = CacheKey::by_relation("users", format!("{}:1", relation_name));
assert!(php_relation_cache_fetch(&cache, &key).is_some());
}
}
#[test]
fn test_with_cache_option_specific_integration() {
let mut map = HashMap::new();
map.insert(
"orders".to_string(),
WithCacheConfig::new(None, Some(Duration::from_secs(3600)), None),
);
let opt = WithCacheOption::Specific(map);
assert!(opt.get_config("orders").is_some());
assert!(opt.get_config("profile").is_none());
let cache = L2Cache::new();
if let Some(config) = opt.get_config("orders") {
let key = CacheKey::by_relation("users", "orders:1");
php_relation_cache_remember(&cache, &key, Value::I64(1), config.expire);
}
let orders_key = CacheKey::by_relation("users", "orders:1");
assert!(php_relation_cache_fetch(&cache, &orders_key).is_some());
}
#[test]
fn test_php_relation_cache_overwrite() {
let cache = L2Cache::new();
let key = CacheKey::by_relation("users", "orders:1");
php_relation_cache_remember(&cache, &key, Value::I64(1), None);
php_relation_cache_remember(&cache, &key, Value::I64(2), None);
assert_eq!(php_relation_cache_fetch(&cache, &key), Some(Value::I64(2)));
}
#[test]
fn test_r5_php_with_cache_true_to_all() {
let opt = WithCacheOption::All;
assert!(opt.is_enabled());
assert!(opt.is_all());
assert!(opt.get_config("orders").is_some());
assert!(opt.get_config("profile").is_some());
}
#[test]
fn test_r5_php_with_cache_named_to_specific() {
let mut map = HashMap::new();
map.insert(
"orders".to_string(),
php_with_cache_config(None, Some(Duration::from_secs(3600)), None),
);
let opt = WithCacheOption::Specific(map);
assert!(opt.is_enabled());
assert!(opt.is_specific());
let config = opt.get_config("orders").unwrap();
assert_eq!(config.expire, Some(Duration::from_secs(3600)));
assert!(opt.get_config("profile").is_none());
}
#[test]
fn test_r5_php_with_cache_false_to_none() {
let opt = WithCacheOption::None;
assert!(!opt.is_enabled());
assert!(opt.get_config("any").is_none());
}
#[test]
fn test_r5_php_tag_default_to_table() {
let tag = php_relation_cache_tag("users", None);
assert_eq!(tag, "users"); }
#[test]
fn test_r5_php_cache_clear_to_invalidate_table() {
let cache = L2Cache::new();
let key1 = CacheKey::by_relation("users", "orders:1");
let key2 = CacheKey::by_relation("users", "profile:1");
let key3 = CacheKey::by_relation("orders", "items:1");
php_relation_cache_remember(&cache, &key1, Value::I64(1), None);
php_relation_cache_remember(&cache, &key2, Value::I64(2), None);
php_relation_cache_remember(&cache, &key3, Value::I64(3), None);
php_relation_cache_invalidate(&cache, "users");
assert_eq!(php_relation_cache_fetch(&cache, &key1), None);
assert_eq!(php_relation_cache_fetch(&cache, &key2), None);
assert_eq!(php_relation_cache_fetch(&cache, &key3), Some(Value::I64(3)));
}
#[test]
fn test_r5_php_cache_delete_to_invalidate_key() {
let cache = L2Cache::new();
let key = CacheKey::by_relation("users", "orders:1");
php_relation_cache_remember(&cache, &key, Value::I64(42), None);
assert!(php_relation_cache_fetch(&cache, &key).is_some());
php_relation_cache_delete(&cache, &key);
assert!(php_relation_cache_fetch(&cache, &key).is_none());
}
#[test]
fn test_r5_php_get_cache_key_format() {
let key = php_relation_cache_key("shop", "users", "1");
assert_eq!(key, "think_shop.users|1");
assert!(key.starts_with("think_"));
assert!(key.contains("."));
assert!(key.contains("|"));
}
}