prax_query/data_cache/
mod.rs1mod backend;
90mod invalidation;
91mod key;
92mod memory;
93mod options;
94mod redis;
95mod stats;
96mod tiered;
97
98pub use backend::{CacheBackend, CacheEntry, CacheError, CacheResult};
99pub use invalidation::{EntityTag, InvalidationEvent, InvalidationStrategy};
100pub use key::{CacheKey, CacheKeyBuilder, KeyPattern};
101pub use memory::{MemoryCache, MemoryCacheBuilder, MemoryCacheConfig};
102pub use options::{CacheOptions, CachePolicy, WritePolicy};
103pub use redis::{RedisCache, RedisCacheConfig, RedisConnection};
104pub use stats::{CacheMetrics, CacheStats};
105pub use tiered::{TieredCache, TieredCacheConfig};
106
107use std::sync::Arc;
108
109#[derive(Clone)]
114pub struct CacheManager<B: CacheBackend> {
115 backend: Arc<B>,
116 default_options: CacheOptions,
117 metrics: Arc<CacheMetrics>,
118}
119
120impl<B: CacheBackend> CacheManager<B> {
121 pub fn new(backend: B) -> Self {
123 Self {
124 backend: Arc::new(backend),
125 default_options: CacheOptions::default(),
126 metrics: Arc::new(CacheMetrics::new()),
127 }
128 }
129
130 pub fn with_options(backend: B, options: CacheOptions) -> Self {
132 Self {
133 backend: Arc::new(backend),
134 default_options: options,
135 metrics: Arc::new(CacheMetrics::new()),
136 }
137 }
138
139 pub fn backend(&self) -> &B {
141 &self.backend
142 }
143
144 pub fn metrics(&self) -> &CacheMetrics {
146 &self.metrics
147 }
148
149 pub async fn get<T>(&self, key: &CacheKey) -> CacheResult<Option<T>>
151 where
152 T: serde::de::DeserializeOwned,
153 {
154 let start = std::time::Instant::now();
155 let result = self.backend.get(key).await;
156 let duration = start.elapsed();
157
158 match &result {
159 Ok(Some(_)) => self.metrics.record_hit(duration),
160 Ok(None) => self.metrics.record_miss(duration),
161 Err(_) => self.metrics.record_error(),
162 }
163
164 result
165 }
166
167 pub async fn set<T>(&self, key: &CacheKey, value: &T, options: Option<&CacheOptions>) -> CacheResult<()>
169 where
170 T: serde::Serialize + Sync,
171 {
172 let opts = options.unwrap_or(&self.default_options);
173 let start = std::time::Instant::now();
174 let result = self.backend.set(key, value, opts.ttl).await;
175 let duration = start.elapsed();
176
177 if result.is_ok() {
178 self.metrics.record_write(duration);
179 } else {
180 self.metrics.record_error();
181 }
182
183 result
184 }
185
186 pub async fn get_or_set<T, F, Fut>(
191 &self,
192 key: &CacheKey,
193 f: F,
194 options: Option<&CacheOptions>,
195 ) -> CacheResult<T>
196 where
197 T: serde::Serialize + serde::de::DeserializeOwned + Sync,
198 F: FnOnce() -> Fut,
199 Fut: std::future::Future<Output = CacheResult<T>>,
200 {
201 if let Some(value) = self.get::<T>(key).await? {
203 return Ok(value);
204 }
205
206 let value = f().await?;
208
209 let _ = self.set(key, &value, options).await;
211
212 Ok(value)
213 }
214
215 pub async fn delete(&self, key: &CacheKey) -> CacheResult<bool> {
217 self.backend.delete(key).await
218 }
219
220 pub async fn exists(&self, key: &CacheKey) -> CacheResult<bool> {
222 self.backend.exists(key).await
223 }
224
225 pub async fn invalidate_pattern(&self, pattern: &KeyPattern) -> CacheResult<u64> {
227 self.backend.invalidate_pattern(pattern).await
228 }
229
230 pub async fn invalidate_entity(&self, entity: &str) -> CacheResult<u64> {
232 let pattern = KeyPattern::entity(entity);
233 self.invalidate_pattern(&pattern).await
234 }
235
236 pub async fn invalidate_record<I: std::fmt::Display>(
238 &self,
239 entity: &str,
240 id: I,
241 ) -> CacheResult<u64> {
242 let pattern = KeyPattern::record(entity, id);
243 self.invalidate_pattern(&pattern).await
244 }
245
246 pub async fn invalidate_tags(&self, tags: &[EntityTag]) -> CacheResult<u64> {
248 self.backend.invalidate_tags(tags).await
249 }
250
251 pub async fn clear(&self) -> CacheResult<()> {
253 self.backend.clear().await
254 }
255
256 pub fn stats(&self) -> CacheStats {
258 self.metrics.snapshot()
259 }
260}
261
262pub struct CacheManagerBuilder {
264 default_options: CacheOptions,
265}
266
267impl Default for CacheManagerBuilder {
268 fn default() -> Self {
269 Self::new()
270 }
271}
272
273impl CacheManagerBuilder {
274 pub fn new() -> Self {
276 Self {
277 default_options: CacheOptions::default(),
278 }
279 }
280
281 pub fn default_options(mut self, options: CacheOptions) -> Self {
283 self.default_options = options;
284 self
285 }
286
287 pub fn memory(self, config: MemoryCacheConfig) -> CacheManager<MemoryCache> {
289 let backend = MemoryCache::new(config);
290 CacheManager::with_options(backend, self.default_options)
291 }
292
293 pub async fn redis(self, config: RedisCacheConfig) -> CacheResult<CacheManager<RedisCache>> {
295 let backend = RedisCache::new(config).await?;
296 Ok(CacheManager::with_options(backend, self.default_options))
297 }
298
299 pub async fn tiered(
301 self,
302 memory_config: MemoryCacheConfig,
303 redis_config: RedisCacheConfig,
304 ) -> CacheResult<CacheManager<TieredCache<MemoryCache, RedisCache>>> {
305 let memory = MemoryCache::new(memory_config);
306 let redis = RedisCache::new(redis_config).await?;
307 let backend = TieredCache::new(memory, redis);
308 Ok(CacheManager::with_options(backend, self.default_options))
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use std::time::Duration;
316
317 #[tokio::test]
318 async fn test_memory_cache_basic() {
319 let cache = CacheManager::new(MemoryCache::new(MemoryCacheConfig::default()));
320
321 let key = CacheKey::new("test", "key1");
322
323 cache.set(&key, &"hello world", None).await.unwrap();
325
326 let value: Option<String> = cache.get(&key).await.unwrap();
328 assert_eq!(value, Some("hello world".to_string()));
329
330 cache.delete(&key).await.unwrap();
332
333 let value: Option<String> = cache.get(&key).await.unwrap();
335 assert!(value.is_none());
336 }
337
338 #[tokio::test]
339 async fn test_get_or_set() {
340 let cache = CacheManager::new(MemoryCache::new(MemoryCacheConfig::default()));
341
342 let key = CacheKey::new("test", "computed");
343 let mut call_count = 0;
344
345 let value: String = cache
347 .get_or_set(
348 &key,
349 || {
350 call_count += 1;
351 async { Ok("computed value".to_string()) }
352 },
353 None,
354 )
355 .await
356 .unwrap();
357
358 assert_eq!(value, "computed value");
359 assert_eq!(call_count, 1);
360
361 let value: String = cache
363 .get_or_set(
364 &key,
365 || {
366 call_count += 1;
367 async { Ok("should not be called".to_string()) }
368 },
369 None,
370 )
371 .await
372 .unwrap();
373
374 assert_eq!(value, "computed value");
375 assert_eq!(call_count, 1); }
377}
378
379