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>(
169 &self,
170 key: &CacheKey,
171 value: &T,
172 options: Option<&CacheOptions>,
173 ) -> CacheResult<()>
174 where
175 T: serde::Serialize + Sync,
176 {
177 let opts = options.unwrap_or(&self.default_options);
178 let start = std::time::Instant::now();
179 let result = self.backend.set(key, value, opts.ttl).await;
180 let duration = start.elapsed();
181
182 if result.is_ok() {
183 self.metrics.record_write(duration);
184 } else {
185 self.metrics.record_error();
186 }
187
188 result
189 }
190
191 pub async fn get_or_set<T, F, Fut>(
196 &self,
197 key: &CacheKey,
198 f: F,
199 options: Option<&CacheOptions>,
200 ) -> CacheResult<T>
201 where
202 T: serde::Serialize + serde::de::DeserializeOwned + Sync,
203 F: FnOnce() -> Fut,
204 Fut: std::future::Future<Output = CacheResult<T>>,
205 {
206 if let Some(value) = self.get::<T>(key).await? {
208 return Ok(value);
209 }
210
211 let value = f().await?;
213
214 let _ = self.set(key, &value, options).await;
216
217 Ok(value)
218 }
219
220 pub async fn delete(&self, key: &CacheKey) -> CacheResult<bool> {
222 self.backend.delete(key).await
223 }
224
225 pub async fn exists(&self, key: &CacheKey) -> CacheResult<bool> {
227 self.backend.exists(key).await
228 }
229
230 pub async fn invalidate_pattern(&self, pattern: &KeyPattern) -> CacheResult<u64> {
232 self.backend.invalidate_pattern(pattern).await
233 }
234
235 pub async fn invalidate_entity(&self, entity: &str) -> CacheResult<u64> {
237 let pattern = KeyPattern::entity(entity);
238 self.invalidate_pattern(&pattern).await
239 }
240
241 pub async fn invalidate_record<I: std::fmt::Display>(
243 &self,
244 entity: &str,
245 id: I,
246 ) -> CacheResult<u64> {
247 let pattern = KeyPattern::record(entity, id);
248 self.invalidate_pattern(&pattern).await
249 }
250
251 pub async fn invalidate_tags(&self, tags: &[EntityTag]) -> CacheResult<u64> {
253 self.backend.invalidate_tags(tags).await
254 }
255
256 pub async fn clear(&self) -> CacheResult<()> {
258 self.backend.clear().await
259 }
260
261 pub fn stats(&self) -> CacheStats {
263 self.metrics.snapshot()
264 }
265}
266
267pub struct CacheManagerBuilder {
269 default_options: CacheOptions,
270}
271
272impl Default for CacheManagerBuilder {
273 fn default() -> Self {
274 Self::new()
275 }
276}
277
278impl CacheManagerBuilder {
279 pub fn new() -> Self {
281 Self {
282 default_options: CacheOptions::default(),
283 }
284 }
285
286 pub fn default_options(mut self, options: CacheOptions) -> Self {
288 self.default_options = options;
289 self
290 }
291
292 pub fn memory(self, config: MemoryCacheConfig) -> CacheManager<MemoryCache> {
294 let backend = MemoryCache::new(config);
295 CacheManager::with_options(backend, self.default_options)
296 }
297
298 pub async fn redis(self, config: RedisCacheConfig) -> CacheResult<CacheManager<RedisCache>> {
300 let backend = RedisCache::new(config).await?;
301 Ok(CacheManager::with_options(backend, self.default_options))
302 }
303
304 pub async fn tiered(
306 self,
307 memory_config: MemoryCacheConfig,
308 redis_config: RedisCacheConfig,
309 ) -> CacheResult<CacheManager<TieredCache<MemoryCache, RedisCache>>> {
310 let memory = MemoryCache::new(memory_config);
311 let redis = RedisCache::new(redis_config).await?;
312 let backend = TieredCache::new(memory, redis);
313 Ok(CacheManager::with_options(backend, self.default_options))
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320 use std::time::Duration;
321
322 #[tokio::test]
323 async fn test_memory_cache_basic() {
324 let cache = CacheManager::new(MemoryCache::new(MemoryCacheConfig::default()));
325
326 let key = CacheKey::new("test", "key1");
327
328 cache.set(&key, &"hello world", None).await.unwrap();
330
331 let value: Option<String> = cache.get(&key).await.unwrap();
333 assert_eq!(value, Some("hello world".to_string()));
334
335 cache.delete(&key).await.unwrap();
337
338 let value: Option<String> = cache.get(&key).await.unwrap();
340 assert!(value.is_none());
341 }
342
343 #[tokio::test]
344 async fn test_get_or_set() {
345 let cache = CacheManager::new(MemoryCache::new(MemoryCacheConfig::default()));
346
347 let key = CacheKey::new("test", "computed");
348 let mut call_count = 0;
349
350 let value: String = cache
352 .get_or_set(
353 &key,
354 || {
355 call_count += 1;
356 async { Ok("computed value".to_string()) }
357 },
358 None,
359 )
360 .await
361 .unwrap();
362
363 assert_eq!(value, "computed value");
364 assert_eq!(call_count, 1);
365
366 let value: String = cache
368 .get_or_set(
369 &key,
370 || {
371 call_count += 1;
372 async { Ok("should not be called".to_string()) }
373 },
374 None,
375 )
376 .await
377 .unwrap();
378
379 assert_eq!(value, "computed value");
380 assert_eq!(call_count, 1); }
382}