1use crate::keys;
4use crate::region::{Region, Strategy};
5use crate::stats::{Event, Stats};
6use rustlavel_cache::{Cache, CacheStore};
7use rustlavel_core::{Json, Result};
8use rustlavel_db::row::Columns;
9use rustlavel_db::{Database, Model, ModelExt, QueryBuilder, Row, Value};
10use std::collections::BTreeMap;
11use std::sync::Arc;
12
13struct Entry {
20 sql: String,
21 generation: i64,
22 rows: Vec<Json>,
23}
24
25impl Entry {
26 fn to_json(&self) -> Json {
27 Json::object([
28 ("sql", Json::from(self.sql.as_str())),
29 ("g", Json::from(self.generation)),
30 ("rows", Json::Array(self.rows.clone())),
31 ])
32 }
33
34 fn from_json(value: &Json) -> Option<Entry> {
35 Some(Entry {
36 sql: value.get("sql")?.as_str()?.to_string(),
37 generation: value.get("g")?.as_i64()?,
38 rows: value.get("rows")?.as_array()?.to_vec(),
39 })
40 }
41}
42
43#[derive(Clone)]
47pub struct ModelCache {
48 store: Arc<dyn Cache>,
49 regions: Arc<BTreeMap<&'static str, Region>>,
50 default_region: Arc<Region>,
51 stats: Arc<Stats>,
52 opt_in: bool,
58}
59
60impl ModelCache {
61 pub fn new(store: impl Cache) -> ModelCache {
63 ModelCache::from_arc(Arc::new(store))
64 }
65
66 pub fn shared(store: &CacheStore) -> ModelCache {
68 ModelCache::from_arc(store.driver_handle())
69 }
70
71 fn from_arc(store: Arc<dyn Cache>) -> ModelCache {
72 ModelCache {
73 store,
74 regions: Arc::new(BTreeMap::new()),
75 default_region: Arc::new(Region::default()),
76 stats: Arc::new(Stats::default()),
77 opt_in: true,
78 }
79 }
80
81 pub fn region<M: Model>(mut self, region: Region) -> ModelCache {
87 region.check(M::TABLE).expect("the cache region settings contradict each other");
88 Arc::make_mut(&mut self.regions).insert(M::TABLE, region);
89 self
90 }
91
92 pub fn cache_everything(mut self, region: Region) -> ModelCache {
97 region.check("<default>").expect("the default region settings contradict each other");
98 self.default_region = Arc::new(region);
99 self.opt_in = false;
100 self
101 }
102
103 pub fn stats(&self) -> &Stats {
104 &self.stats
105 }
106
107 fn region_for(&self, table: &str) -> Option<&Region> {
109 match self.regions.get(table) {
110 Some(region) => Some(region),
111 None if self.opt_in => None,
112 None => Some(&self.default_region),
113 }
114 }
115
116 pub async fn find<M: Model + Send>(&self, db: &Database, key: M::Key) -> Result<Option<M>> {
120 let Some(region) = self.region_for(M::TABLE).filter(|r| r.entities) else {
121 return M::find(db, key).await;
122 };
123
124 let text = Json::from(key.clone().into()).to_string();
125 let cache_key = keys::entity(M::TABLE, &text);
126
127 if let Some(model) = self
134 .store
135 .get(&cache_key)
136 .await?
137 .as_ref()
138 .and_then(row_from_json)
139 .and_then(|row| M::from_row(&row).ok())
140 {
141 self.stats.record(M::TABLE, Event::EntityHit);
142 return Ok(Some(model));
143 }
144 self.stats.record(M::TABLE, Event::EntityMiss);
145
146 let Some(row) = self.select_one(db, M::TABLE, M::PRIMARY_KEY, key.into()).await? else {
153 return Ok(None);
154 };
155 let model = M::from_row(&row)?;
156
157 if let Some(json) = row_to_json(&row) {
158 self.put(&cache_key, json, region).await?;
159 }
160 Ok(Some(model))
161 }
162
163 pub async fn get<M: Model + Send>(&self, db: &Database, query: QueryBuilder) -> Result<Vec<M>> {
165 let Some(region) = self.region_for(M::TABLE).filter(|r| r.queries) else {
166 return M::get(db, query).await;
167 };
168
169 let (sql, bindings) = query.to_sql(db.dialect())?;
170 let cache_key = keys::query(M::TABLE, keys::fingerprint(&sql, &bindings));
171 let generation = self.generation(M::TABLE, region).await?;
172
173 let usable = self
178 .store
179 .get(&cache_key)
180 .await?
181 .as_ref()
182 .and_then(Entry::from_json)
183 .filter(|entry| entry.sql == sql && entry.generation == generation);
184
185 if let Some(models) = usable.and_then(|entry| {
189 entry
190 .rows
191 .iter()
192 .map(|value| row_from_json(value).and_then(|row| M::from_row(&row).ok()))
193 .collect::<Option<Vec<M>>>()
194 }) {
195 self.stats.record(M::TABLE, Event::QueryHit);
196 return Ok(models);
197 }
198 self.stats.record(M::TABLE, Event::QueryMiss);
199
200 let rows = query.get(db).await?;
201 let models = M::hydrate(&rows)?;
202
203 if rows.len() > region.max_rows {
204 self.stats.record(M::TABLE, Event::TooLarge);
205 return Ok(models);
206 }
207
208 let Some(encoded) = rows.iter().map(row_to_json).collect::<Option<Vec<Json>>>() else {
211 self.stats.record(M::TABLE, Event::Unsupported);
212 return Ok(models);
213 };
214
215 let entry = Entry { sql, generation, rows: encoded };
216 self.put(&cache_key, entry.to_json(), region).await?;
217 Ok(models)
218 }
219
220 pub async fn first<M: Model + Send>(
222 &self,
223 db: &Database,
224 query: QueryBuilder,
225 ) -> Result<Option<M>> {
226 Ok(self.get::<M>(db, query.limit(1)).await?.into_iter().next())
227 }
228
229 pub async fn insert<M: Model + Send + Sync>(&self, db: &Database, model: &mut M) -> Result<()> {
233 model.insert(db).await?;
234 self.invalidate_table::<M>().await
235 }
236
237 pub async fn update<M: Model + Send + Sync>(&self, db: &Database, model: &M) -> Result<u64> {
239 let affected = model.update(db).await?;
240 self.forget::<M>(model.key()).await?;
241 self.invalidate_table::<M>().await?;
242 Ok(affected)
243 }
244
245 pub async fn delete<M: Model + Send + Sync>(&self, db: &Database, model: &M) -> Result<u64> {
247 let affected = model.delete(db).await?;
248 self.forget::<M>(model.key()).await?;
249 self.invalidate_table::<M>().await?;
250 Ok(affected)
251 }
252
253 pub async fn forget<M: Model>(&self, key: M::Key) -> Result<()> {
259 let text = Json::from(key.into()).to_string();
260 self.store.forget(&keys::entity(M::TABLE, &text)).await?;
261 Ok(())
262 }
263
264 pub async fn invalidate_table<M: Model>(&self) -> Result<()> {
271 let Some(region) = self.region_for(M::TABLE) else { return Ok(()) };
272 if region.strategy == Strategy::ReadOnly {
273 return Ok(());
276 }
277 self.store.increment(&keys::generation(M::TABLE), 1).await?;
278 self.stats.record(M::TABLE, Event::Invalidated);
279 Ok(())
280 }
281
282 pub async fn invalidate_all(&self) -> Result<()> {
288 for (table, region) in self.regions.iter() {
289 if region.strategy == Strategy::ReadOnly {
290 continue;
291 }
292 self.store.increment(&keys::generation(table), 1).await?;
293 self.stats.record(table, Event::Invalidated);
294 }
295 Ok(())
296 }
297
298 async fn generation(&self, table: &str, region: &Region) -> Result<i64> {
305 if region.strategy == Strategy::ReadOnly {
306 return Ok(0);
307 }
308 Ok(self.store.get(&keys::generation(table)).await?.and_then(|v| v.as_i64()).unwrap_or(0))
309 }
310
311 async fn put(&self, key: &str, value: Json, region: &Region) -> Result<()> {
312 match region.ttl {
313 Some(ttl) => self.store.put(key, value, ttl).await,
314 None => self.store.forever(key, value).await,
315 }
316 }
317
318 async fn select_one(
326 &self,
327 db: &Database,
328 table: &str,
329 primary_key: &str,
330 key: Value,
331 ) -> Result<Option<Row>> {
332 let rows = db.table(table).filter(primary_key, key).limit(1).get(db).await?;
333 Ok(rows.into_iter().next())
334 }
335}
336
337impl std::fmt::Debug for ModelCache {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 f.debug_struct("ModelCache")
340 .field("driver", &self.store.driver())
341 .field("regions", &self.regions.keys().collect::<Vec<_>>())
342 .field("opt_in", &self.opt_in)
343 .finish()
344 }
345}
346
347fn row_to_json(row: &Row) -> Option<Json> {
353 let mut fields = Vec::with_capacity(row.columns().len());
354 for name in row.columns() {
355 let value = row.value(name).ok()?;
356 fields.push((name.clone(), value_to_json(value)?));
357 }
358 Some(Json::object(fields))
359}
360
361fn row_from_json(value: &Json) -> Option<Row> {
364 let Json::Object(map) = value else { return None };
365 let columns: Vec<String> = map.keys().cloned().collect();
366 let values: Vec<Value> = map.values().map(json_to_value).collect();
367 Some(Row::new(Columns::new(columns), values))
368}
369
370fn value_to_json(value: &Value) -> Option<Json> {
379 match value {
380 Value::Bytes(_) => None,
381 other => Some(Json::from(other.clone())),
382 }
383}
384
385fn json_to_value(value: &Json) -> Value {
393 match value {
394 Json::Null => Value::Null,
395 Json::Bool(b) => Value::Bool(*b),
396 Json::Number(n) if n.fract() == 0.0 && n.abs() < 9.007_199_254_740_992e15 => {
397 Value::Int(*n as i64)
398 }
399 Json::Number(n) => Value::Float(*n),
400 Json::String(s) => Value::Text(s.clone()),
401 other => Value::Json(other.clone()),
402 }
403}
404
405impl ModelCache {
407 pub fn store(&self) -> &Arc<dyn Cache> {
408 &self.store
409 }
410}
411
412#[cfg(test)]
414mod tests {
415 use super::*;
416
417 fn row(pairs: &[(&str, Value)]) -> Row {
418 let columns: Vec<String> = pairs.iter().map(|(n, _)| (*n).to_string()).collect();
419 let values: Vec<Value> = pairs.iter().map(|(_, v)| v.clone()).collect();
420 Row::new(Columns::new(columns), values)
421 }
422
423 #[test]
424 fn a_row_survives_the_round_trip_through_json() {
425 let original = row(&[
426 ("id", Value::from(7i64)),
427 ("name", Value::from("Ada Lovelace")),
428 ("verified", Value::from(true)),
429 ("deleted_at", Value::Null),
430 ]);
431
432 let back = row_from_json(&row_to_json(&original).expect("not encodable")).expect("did not parse");
433
434 assert_eq!(back.get::<i64>("id").unwrap(), 7);
435 assert_eq!(back.get::<String>("name").unwrap(), "Ada Lovelace");
436 assert!(back.get::<bool>("verified").unwrap());
437 assert_eq!(back.columns().len(), 4, "a column was lost");
438 }
439
440 #[test]
443 fn a_row_with_a_binary_column_is_not_cached_at_all() {
444 let with_blob = row(&[("id", Value::from(1i64)), ("avatar", Value::Bytes(vec![1, 2, 3]))]);
445
446 assert!(row_to_json(&with_blob).is_none(), "a blob was silently turned into text");
447 }
448
449 #[test]
452 fn numbers_come_back_as_the_variant_the_column_needs() {
453 assert_eq!(json_to_value(&Json::Number(7.0)), Value::Int(7));
454 assert_eq!(json_to_value(&Json::Number(-7.0)), Value::Int(-7));
455 assert_eq!(json_to_value(&Json::Number(7.5)), Value::Float(7.5));
456 assert_eq!(json_to_value(&Json::Null), Value::Null);
457 assert_eq!(json_to_value(&Json::Bool(true)), Value::Bool(true));
458 assert_eq!(json_to_value(&Json::from("x")), Value::Text("x".into()));
459 }
460
461 #[test]
464 fn rubbish_in_the_cache_reads_as_a_miss_rather_than_an_error() {
465 assert!(row_from_json(&Json::from("not an object")).is_none());
466 assert!(row_from_json(&Json::Null).is_none());
467 assert!(row_from_json(&Json::Array(vec![])).is_none());
468 }
469
470 #[test]
471 fn a_query_entry_carries_the_statement_it_was_taken_from() {
472 let entry = Entry {
473 sql: "select * from users where role = ?".into(),
474 generation: 3,
475 rows: vec![Json::object([("id", Json::from(1))])],
476 };
477
478 let back = Entry::from_json(&entry.to_json()).expect("did not parse");
479 assert_eq!(back.sql, entry.sql);
480 assert_eq!(back.generation, 3);
481 assert_eq!(back.rows.len(), 1);
482
483 assert!(Entry::from_json(&Json::from("nonsense")).is_none());
485 assert!(Entry::from_json(&Json::object([("sql", Json::from("x"))])).is_none());
486 }
487}