1use std::collections::hash_map::Entry;
2use std::collections::{HashMap, HashSet, VecDeque};
3
4use super::{RedDBRuntime, RuntimeQueryResult, RuntimeResultCacheEntry};
5
6const RESULT_CACHE_BACKEND_KEY: &str = "runtime.result_cache.backend";
7const RESULT_CACHE_DEFAULT_BACKEND: &str = "legacy";
8const RESULT_CACHE_BLOB_NAMESPACE: &str = "runtime.result_cache";
9const RESULT_CACHE_EMBEDDED_COLLECTION: &str = "red_internal_result_cache";
10const RESULT_CACHE_TTL_SECS: u64 = 30;
11const RESULT_CACHE_MAX_ENTRIES: usize = 1000;
12const RESULT_CACHE_ENABLED_KEY: &str = "runtime.result_cache.enabled";
13const RESULT_CACHE_TTL_KEY: &str = "runtime.result_cache.ttl_seconds";
14const RESULT_CACHE_CAPACITY_KEY: &str = "runtime.result_cache.capacity_entries";
15const RESULT_CACHE_PAYLOAD_MAGIC: &[u8; 8] = b"RDRC0001";
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18enum RuntimeResultCacheBackend {
19 Legacy,
20 BlobCache,
21 Shadow,
22}
23
24fn trim_result_cache(
25 map: &mut HashMap<String, RuntimeResultCacheEntry>,
26 order: &mut VecDeque<String>,
27 max_entries: usize,
28) -> u64 {
29 let mut evicted = 0u64;
30 while map.len() > max_entries {
31 if let Some(oldest) = order.pop_front() {
32 if map.remove(&oldest).is_some() {
33 evicted += 1;
34 }
35 } else {
36 break;
37 }
38 }
39 evicted
40}
41
42fn result_cache_fingerprint(result: &RuntimeQueryResult) -> String {
43 format!(
44 "{:?}|{}|{}|{}|{}|{:?}",
45 result.result,
46 result.query,
47 result.statement,
48 result.engine,
49 result.affected_rows,
50 result.statement_type
51 )
52}
53
54fn mode_to_byte(mode: crate::storage::query::modes::QueryMode) -> u8 {
55 match mode {
56 crate::storage::query::modes::QueryMode::Sql => 0,
57 crate::storage::query::modes::QueryMode::Gremlin => 1,
58 crate::storage::query::modes::QueryMode::Cypher => 2,
59 crate::storage::query::modes::QueryMode::Sparql => 3,
60 crate::storage::query::modes::QueryMode::Path => 4,
61 crate::storage::query::modes::QueryMode::Natural => 5,
62 crate::storage::query::modes::QueryMode::Unknown => 255,
63 }
64}
65
66fn mode_from_byte(byte: u8) -> Option<crate::storage::query::modes::QueryMode> {
67 match byte {
68 0 => Some(crate::storage::query::modes::QueryMode::Sql),
69 1 => Some(crate::storage::query::modes::QueryMode::Gremlin),
70 2 => Some(crate::storage::query::modes::QueryMode::Cypher),
71 3 => Some(crate::storage::query::modes::QueryMode::Sparql),
72 4 => Some(crate::storage::query::modes::QueryMode::Path),
73 5 => Some(crate::storage::query::modes::QueryMode::Natural),
74 255 => Some(crate::storage::query::modes::QueryMode::Unknown),
75 _ => None,
76 }
77}
78
79fn result_cache_static_str(value: &str) -> Option<&'static str> {
80 match value {
81 "select" => Some("select"),
82 "materialized-graph" => Some("materialized-graph"),
83 "runtime-red-schema" => Some("runtime-red-schema"),
84 "runtime-fdw" => Some("runtime-fdw"),
85 "runtime-table-rls" => Some("runtime-table-rls"),
86 "runtime-table" => Some("runtime-table"),
87 "runtime-join-rls" => Some("runtime-join-rls"),
88 "runtime-join" => Some("runtime-join"),
89 "runtime-vector" => Some("runtime-vector"),
90 "runtime-hybrid" => Some("runtime-hybrid"),
91 "runtime-secret" => Some("runtime-secret"),
92 "runtime-config" => Some("runtime-config"),
93 "runtime-tenant" => Some("runtime-tenant"),
94 "runtime-explain" => Some("runtime-explain"),
95 "runtime-tree" => Some("runtime-tree"),
96 "runtime-kv" => Some("runtime-kv"),
97 "runtime-queue" => Some("runtime-queue"),
98 _ => None,
99 }
100}
101
102fn write_u32(out: &mut Vec<u8>, value: usize) -> Option<()> {
103 let value = u32::try_from(value).ok()?;
104 out.extend_from_slice(&value.to_le_bytes());
105 Some(())
106}
107
108fn write_string(out: &mut Vec<u8>, value: &str) -> Option<()> {
109 write_u32(out, value.len())?;
110 out.extend_from_slice(value.as_bytes());
111 Some(())
112}
113
114fn write_bytes(out: &mut Vec<u8>, value: &[u8]) -> Option<()> {
115 write_u32(out, value.len())?;
116 out.extend_from_slice(value);
117 Some(())
118}
119
120fn read_u8(input: &mut &[u8]) -> Option<u8> {
121 let (&value, rest) = input.split_first()?;
122 *input = rest;
123 Some(value)
124}
125
126fn read_u32(input: &mut &[u8]) -> Option<usize> {
127 if input.len() < 4 {
128 return None;
129 }
130 let value = u32::from_le_bytes(input[..4].try_into().ok()?) as usize;
131 *input = &input[4..];
132 Some(value)
133}
134
135fn read_u64(input: &mut &[u8]) -> Option<u64> {
136 if input.len() < 8 {
137 return None;
138 }
139 let value = u64::from_le_bytes(input[..8].try_into().ok()?);
140 *input = &input[8..];
141 Some(value)
142}
143
144fn read_string(input: &mut &[u8]) -> Option<String> {
145 let len = read_u32(input)?;
146 if input.len() < len {
147 return None;
148 }
149 let value = String::from_utf8(input[..len].to_vec()).ok()?;
150 *input = &input[len..];
151 Some(value)
152}
153
154fn read_bytes<'a>(input: &mut &'a [u8]) -> Option<&'a [u8]> {
155 let len = read_u32(input)?;
156 if input.len() < len {
157 return None;
158 }
159 let value = &input[..len];
160 *input = &input[len..];
161 Some(value)
162}
163
164fn encode_result_cache_payload(entry: &RuntimeResultCacheEntry) -> Option<Vec<u8>> {
165 let result = &entry.result;
166 if result.result.pre_serialized_json.is_some()
167 || result_cache_static_str(result.statement).is_none()
168 || result_cache_static_str(result.engine).is_none()
169 || result_cache_static_str(result.statement_type).is_none()
170 || result.result.records.iter().any(|record| {
171 !record.nodes.is_empty()
172 || !record.edges.is_empty()
173 || !record.paths.is_empty()
174 || !record.vector_results.is_empty()
175 })
176 {
177 return None;
178 }
179
180 let mut out = Vec::new();
181 out.extend_from_slice(RESULT_CACHE_PAYLOAD_MAGIC);
182 write_string(&mut out, &result.query)?;
183 out.push(mode_to_byte(result.mode));
184 write_string(&mut out, result.statement)?;
185 write_string(&mut out, result.engine)?;
186 out.extend_from_slice(&result.affected_rows.to_le_bytes());
187 write_string(&mut out, result.statement_type)?;
188
189 write_u32(&mut out, result.result.columns.len())?;
190 for column in &result.result.columns {
191 write_string(&mut out, column)?;
192 }
193 out.extend_from_slice(&result.result.stats.nodes_scanned.to_le_bytes());
194 out.extend_from_slice(&result.result.stats.edges_scanned.to_le_bytes());
195 out.extend_from_slice(&result.result.stats.rows_scanned.to_le_bytes());
196 out.extend_from_slice(&result.result.stats.segments_total.to_le_bytes());
197 out.extend_from_slice(&result.result.stats.segments_scanned.to_le_bytes());
198 out.extend_from_slice(&result.result.stats.segments_pruned.to_le_bytes());
199 out.extend_from_slice(&result.result.stats.exec_time_us.to_le_bytes());
200
201 write_u32(&mut out, result.result.records.len())?;
202 for record in &result.result.records {
203 let fields = record.iter_fields().collect::<Vec<_>>();
204 write_u32(&mut out, fields.len())?;
205 for (name, value) in fields {
206 write_string(&mut out, name)?;
207 let mut encoded = Vec::new();
208 crate::storage::schema::value_codec::encode(value, &mut encoded);
209 write_bytes(&mut out, &encoded)?;
210 }
211 }
212
213 write_u32(&mut out, entry.scopes.len())?;
214 for scope in &entry.scopes {
215 write_string(&mut out, scope)?;
216 }
217 Some(out)
218}
219
220fn decode_result_cache_payload(mut input: &[u8]) -> Option<(RuntimeQueryResult, HashSet<String>)> {
221 if input.len() < RESULT_CACHE_PAYLOAD_MAGIC.len()
222 || &input[..RESULT_CACHE_PAYLOAD_MAGIC.len()] != RESULT_CACHE_PAYLOAD_MAGIC
223 {
224 return None;
225 }
226 input = &input[RESULT_CACHE_PAYLOAD_MAGIC.len()..];
227
228 let query = read_string(&mut input)?;
229 let mode = mode_from_byte(read_u8(&mut input)?)?;
230 let statement = result_cache_static_str(&read_string(&mut input)?)?;
231 let engine = result_cache_static_str(&read_string(&mut input)?)?;
232 let affected_rows = read_u64(&mut input)?;
233 let statement_type = result_cache_static_str(&read_string(&mut input)?)?;
234
235 let mut columns = Vec::new();
236 for _ in 0..read_u32(&mut input)? {
237 columns.push(read_string(&mut input)?);
238 }
239 let stats = crate::storage::query::unified::QueryStats {
240 nodes_scanned: read_u64(&mut input)?,
241 edges_scanned: read_u64(&mut input)?,
242 rows_scanned: read_u64(&mut input)?,
243 segments_total: read_u64(&mut input)?,
244 segments_scanned: read_u64(&mut input)?,
245 segments_pruned: read_u64(&mut input)?,
246 exec_time_us: read_u64(&mut input)?,
247 };
248
249 let mut records = Vec::new();
250 for _ in 0..read_u32(&mut input)? {
251 let mut record = crate::storage::query::unified::UnifiedRecord::new();
252 for _ in 0..read_u32(&mut input)? {
253 let name = read_string(&mut input)?;
254 let bytes = read_bytes(&mut input)?;
255 let (value, used) = crate::storage::schema::value_codec::decode(bytes).ok()?;
256 if used != bytes.len() {
257 return None;
258 }
259 record.set_owned(name, value);
260 }
261 records.push(record);
262 }
263
264 let mut scopes = HashSet::new();
265 for _ in 0..read_u32(&mut input)? {
266 scopes.insert(read_string(&mut input)?);
267 }
268 if !input.is_empty() {
269 return None;
270 }
271
272 Some((
273 RuntimeQueryResult {
274 query,
275 mode,
276 statement,
277 engine,
278 result: crate::storage::query::unified::UnifiedResult {
279 columns,
280 records,
281 stats,
282 pre_serialized_json: None,
283 },
284 affected_rows,
285 statement_type,
286 bookmark: None,
287 notice: None,
288 },
289 scopes,
290 ))
291}
292
293impl RedDBRuntime {
294 fn result_cache_backend(&self) -> RuntimeResultCacheBackend {
295 match self
296 .config_string(RESULT_CACHE_BACKEND_KEY, RESULT_CACHE_DEFAULT_BACKEND)
297 .as_str()
298 {
299 "blob_cache" => RuntimeResultCacheBackend::BlobCache,
300 "shadow" => RuntimeResultCacheBackend::Shadow,
301 _ => RuntimeResultCacheBackend::Legacy,
302 }
303 }
304
305 fn result_cache_enabled(&self) -> bool {
306 self.config_bool(RESULT_CACHE_ENABLED_KEY, true)
307 }
308
309 fn result_cache_ttl_secs(&self) -> u64 {
310 self.config_u64(RESULT_CACHE_TTL_KEY, RESULT_CACHE_TTL_SECS)
311 }
312
313 fn result_cache_capacity(&self) -> usize {
314 self.config_u64(RESULT_CACHE_CAPACITY_KEY, RESULT_CACHE_MAX_ENTRIES as u64) as usize
315 }
316
317 pub fn result_cache_metrics(&self) -> (u64, u64, u64) {
318 use std::sync::atomic::Ordering::Relaxed;
319 (
320 self.inner.result_cache_hits.load(Relaxed),
321 self.inner.result_cache_misses.load(Relaxed),
322 self.inner.result_cache_evictions.load(Relaxed),
323 )
324 }
325
326 fn record_result_cache_evictions(&self, evicted: u64) {
327 if evicted > 0 {
328 self.inner
329 .result_cache_evictions
330 .fetch_add(evicted, std::sync::atomic::Ordering::Relaxed);
331 }
332 }
333
334 pub(super) fn get_result_cache_entry(&self, key: &str) -> Option<RuntimeQueryResult> {
335 if !self.result_cache_enabled() {
336 return None;
337 }
338 let hit = self.get_result_cache_entry_inner(key);
339 let counter = if hit.is_some() {
340 &self.inner.result_cache_hits
341 } else {
342 &self.inner.result_cache_misses
343 };
344 counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
345 hit
346 }
347
348 fn get_result_cache_entry_inner(&self, key: &str) -> Option<RuntimeQueryResult> {
349 match self.result_cache_backend() {
350 RuntimeResultCacheBackend::Legacy => self.get_legacy_result_cache_entry(key),
351 RuntimeResultCacheBackend::BlobCache => self.get_blob_result_cache_entry(key),
352 RuntimeResultCacheBackend::Shadow => {
353 let legacy = self.get_legacy_result_cache_entry(key);
354 let blob = self.get_blob_result_cache_entry(key);
355 if let (Some(ref legacy), Some(ref blob)) = (&legacy, &blob) {
356 if result_cache_fingerprint(legacy) != result_cache_fingerprint(blob) {
357 self.inner
358 .result_cache_shadow_divergences
359 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
360 tracing::warn!(
361 key,
362 metric = crate::runtime::METRIC_CACHE_SHADOW_DIVERGENCE_TOTAL,
363 "result cache shadow backend diverged from legacy"
364 );
365 }
366 }
367 legacy
368 }
369 }
370 }
371
372 fn get_legacy_result_cache_entry(&self, key: &str) -> Option<RuntimeQueryResult> {
373 let ttl = self.result_cache_ttl_secs();
374 let cache = self.inner.result_cache.read();
375 cache.0.get(key).and_then(|entry| {
376 if entry.cached_at.elapsed().as_secs() < ttl {
377 Some(entry.result.clone())
378 } else {
379 None
380 }
381 })
382 }
383
384 fn get_blob_result_cache_entry(&self, key: &str) -> Option<RuntimeQueryResult> {
385 if self.inner.embedded_single_file {
386 if let Some(bytes) = self.get_embedded_result_cache_payload(key) {
387 let policy = crate::storage::cache::BlobCachePolicy::default()
388 .ttl_ms(self.result_cache_ttl_secs() * 1000)
389 .priority(200);
390 let put = crate::storage::cache::BlobCachePut::new(bytes).with_policy(policy);
391 if self
392 .inner
393 .result_blob_cache
394 .put(RESULT_CACHE_BLOB_NAMESPACE, key, put)
395 .is_err()
396 {
397 return None;
398 }
399 }
400 }
401 self.get_blob_result_cache_entry_from_blob_cache(key)
402 }
403
404 fn get_blob_result_cache_entry_from_blob_cache(&self, key: &str) -> Option<RuntimeQueryResult> {
405 let hit = self
406 .inner
407 .result_blob_cache
408 .get(RESULT_CACHE_BLOB_NAMESPACE, key)?;
409 {
410 let cache = self.inner.result_blob_entries.read();
411 if let Some(entry) = cache.0.get(key) {
412 return Some(entry.result.clone());
413 }
414 }
415
416 let (result, scopes) = decode_result_cache_payload(hit.value())?;
417 let mut cache = self.inner.result_blob_entries.write();
418 let (ref mut map, ref mut order) = *cache;
419 let new_entry = RuntimeResultCacheEntry {
420 result: result.clone(),
421 cached_at: std::time::Instant::now(),
422 scopes,
423 };
424 match map.entry(key.to_string()) {
426 Entry::Occupied(mut slot) => {
427 slot.insert(new_entry);
428 }
429 Entry::Vacant(slot) => {
430 order.push_back(slot.key().clone());
431 slot.insert(new_entry);
432 }
433 }
434 let evicted = trim_result_cache(map, order, self.result_cache_capacity());
435 drop(cache);
436 self.record_result_cache_evictions(evicted);
437 Some(result)
438 }
439
440 fn get_embedded_result_cache_payload(&self, key: &str) -> Option<Vec<u8>> {
441 let manager = self
442 .inner
443 .db
444 .store()
445 .get_collection(RESULT_CACHE_EMBEDDED_COLLECTION)?;
446 let mut latest: Option<(u64, Vec<u8>)> = None;
447 manager.for_each_entity(|entity| {
448 let Some(row) = entity.data.as_row() else {
449 return true;
450 };
451 let namespace_matches = row.get_field("namespace").and_then(|value| match value {
452 crate::storage::schema::Value::Text(value) => Some(value.as_ref()),
453 _ => None,
454 }) == Some(RESULT_CACHE_BLOB_NAMESPACE);
455 let key_matches = row.get_field("key").and_then(|value| match value {
456 crate::storage::schema::Value::Text(value) => Some(value.as_ref()),
457 _ => None,
458 }) == Some(key);
459 if namespace_matches && key_matches {
460 if let Some(crate::storage::schema::Value::Blob(payload)) = row.get_field("payload")
461 {
462 let id = entity.id.raw();
463 if latest
464 .as_ref()
465 .is_none_or(|(latest_id, _)| id >= *latest_id)
466 {
467 latest = Some((id, payload.clone()));
468 }
469 }
470 }
471 true
472 });
473 latest.map(|(_, payload)| payload)
474 }
475
476 pub(super) fn put_result_cache_entry(&self, key: &str, entry: RuntimeResultCacheEntry) {
477 if !self.result_cache_enabled() {
478 return;
479 }
480 match self.result_cache_backend() {
481 RuntimeResultCacheBackend::Legacy => self.put_legacy_result_cache_entry(key, entry),
482 RuntimeResultCacheBackend::BlobCache => self.put_blob_result_cache_entry(key, entry),
483 RuntimeResultCacheBackend::Shadow => {
484 self.put_legacy_result_cache_entry(key, entry.clone());
485 self.put_blob_result_cache_entry(key, entry);
486 }
487 }
488 }
489
490 fn put_legacy_result_cache_entry(&self, key: &str, entry: RuntimeResultCacheEntry) {
491 let capacity = self.result_cache_capacity();
492 let mut cache = self.inner.result_cache.write();
493 let (ref mut map, ref mut order) = *cache;
494 match map.entry(key.to_string()) {
496 Entry::Occupied(mut slot) => {
497 slot.insert(entry);
498 }
499 Entry::Vacant(slot) => {
500 order.push_back(slot.key().clone());
501 slot.insert(entry);
502 }
503 }
504 let evicted = trim_result_cache(map, order, capacity);
505 drop(cache);
506 self.record_result_cache_evictions(evicted);
507 }
508
509 fn put_blob_result_cache_entry(&self, key: &str, entry: RuntimeResultCacheEntry) {
510 let policy = crate::storage::cache::BlobCachePolicy::default()
511 .ttl_ms(self.result_cache_ttl_secs() * 1000)
512 .priority(200);
513 let dependencies = entry.scopes.iter().cloned().collect::<Vec<_>>();
514 let bytes = encode_result_cache_payload(&entry)
515 .unwrap_or_else(|| result_cache_fingerprint(&entry.result).into_bytes());
516 if self.inner.embedded_single_file {
517 self.put_embedded_result_cache_payload(key, &bytes, &dependencies);
518 }
519 let put = crate::storage::cache::BlobCachePut::new(bytes)
520 .with_dependencies(dependencies)
521 .with_policy(policy);
522 if self
523 .inner
524 .result_blob_cache
525 .put(RESULT_CACHE_BLOB_NAMESPACE, key, put)
526 .is_err()
527 {
528 return;
529 }
530
531 let capacity = self.result_cache_capacity();
532 let mut cache = self.inner.result_blob_entries.write();
533 let (ref mut map, ref mut order) = *cache;
534 match map.entry(key.to_string()) {
536 Entry::Occupied(mut slot) => {
537 slot.insert(entry);
538 }
539 Entry::Vacant(slot) => {
540 order.push_back(slot.key().clone());
541 slot.insert(entry);
542 }
543 }
544 let evicted = trim_result_cache(map, order, capacity);
545 drop(cache);
546 self.record_result_cache_evictions(evicted);
547 }
548
549 fn put_embedded_result_cache_payload(&self, key: &str, bytes: &[u8], scopes: &[String]) {
550 let store = self.inner.db.store();
551 let _ = store.get_or_create_collection(RESULT_CACHE_EMBEDDED_COLLECTION);
552 let entity = crate::storage::UnifiedEntity::new(
553 crate::storage::EntityId::new(0),
554 crate::storage::EntityKind::TableRow {
555 table: std::sync::Arc::from(RESULT_CACHE_EMBEDDED_COLLECTION),
556 row_id: 0,
557 },
558 crate::storage::EntityData::Row(crate::storage::RowData {
559 columns: Vec::new(),
560 named: Some(HashMap::from([
561 (
562 "namespace".to_string(),
563 crate::storage::schema::Value::text(RESULT_CACHE_BLOB_NAMESPACE),
564 ),
565 (
566 "key".to_string(),
567 crate::storage::schema::Value::text(key.to_string()),
568 ),
569 (
570 "payload".to_string(),
571 crate::storage::schema::Value::Blob(bytes.to_vec()),
572 ),
573 (
574 "scopes".to_string(),
575 crate::storage::schema::Value::text(scopes.join("\n")),
576 ),
577 ])),
578 schema: None,
579 }),
580 );
581 let _ = store.insert_auto(RESULT_CACHE_EMBEDDED_COLLECTION, entity);
582 }
583
584 fn invalidate_embedded_result_cache(&self) {
585 let Some(manager) = self
586 .inner
587 .db
588 .store()
589 .get_collection(RESULT_CACHE_EMBEDDED_COLLECTION)
590 else {
591 return;
592 };
593 let ids = manager
594 .query_all(|_| true)
595 .into_iter()
596 .map(|entity| entity.id)
597 .collect::<Vec<_>>();
598 if !ids.is_empty() {
599 let _ = self
600 .inner
601 .db
602 .store()
603 .delete_batch(RESULT_CACHE_EMBEDDED_COLLECTION, &ids);
604 }
605 }
606
607 fn invalidate_embedded_result_cache_for_scope(&self, scope: &str) {
608 let Some(manager) = self
609 .inner
610 .db
611 .store()
612 .get_collection(RESULT_CACHE_EMBEDDED_COLLECTION)
613 else {
614 return;
615 };
616 let ids = manager
617 .query_all(|entity| {
618 entity
619 .data
620 .as_row()
621 .and_then(|row| row.get_field("scopes"))
622 .and_then(|value| match value {
623 crate::storage::schema::Value::Text(value) => Some(value.as_ref()),
624 _ => None,
625 })
626 .is_some_and(|scopes| scopes.lines().any(|entry| entry == scope))
627 })
628 .into_iter()
629 .map(|entity| entity.id)
630 .collect::<Vec<_>>();
631 if !ids.is_empty() {
632 let _ = self
633 .inner
634 .db
635 .store()
636 .delete_batch(RESULT_CACHE_EMBEDDED_COLLECTION, &ids);
637 }
638 }
639
640 pub fn result_cache_shadow_divergences(&self) -> u64 {
641 self.inner
642 .result_cache_shadow_divergences
643 .load(std::sync::atomic::Ordering::Relaxed)
644 }
645
646 pub fn invalidate_result_cache(&self) {
647 self.invalidate_result_cache_process_only();
648 if self.inner.embedded_single_file {
649 self.invalidate_embedded_result_cache();
650 }
651 }
652
653 pub(crate) fn invalidate_result_cache_process_only(&self) {
654 let mut cache = self.inner.result_cache.write();
655 cache.0.clear();
656 cache.1.clear();
657 let mut blob_entries = self.inner.result_blob_entries.write();
658 blob_entries.0.clear();
659 blob_entries.1.clear();
660 self.inner
661 .result_blob_cache
662 .invalidate_namespace(RESULT_CACHE_BLOB_NAMESPACE);
663 let mut ask_entries = self.inner.ask_answer_cache_entries.write();
664 ask_entries.0.clear();
665 ask_entries.1.clear();
666 self.inner
667 .result_blob_cache
668 .invalidate_namespace(super::ASK_ANSWER_CACHE_NAMESPACE);
669 }
670
671 pub(crate) fn invalidate_result_cache_for_table(&self, table: &str) {
672 let legacy_has_match = {
673 let cache = self.inner.result_cache.read();
674 let (ref map, _) = *cache;
675 !map.is_empty() && map.values().any(|entry| entry.scopes.contains(table))
676 };
677 let blob_has_match = {
678 let cache = self.inner.result_blob_entries.read();
679 let (ref map, _) = *cache;
680 !map.is_empty() && map.values().any(|entry| entry.scopes.contains(table))
681 };
682 if legacy_has_match {
683 let mut cache = self.inner.result_cache.write();
684 let (ref mut map, ref mut order) = *cache;
685 map.retain(|_, entry| !entry.scopes.contains(table));
686 order.retain(|key| map.contains_key(key));
687 }
688
689 if matches!(
690 self.result_cache_backend(),
691 RuntimeResultCacheBackend::BlobCache | RuntimeResultCacheBackend::Shadow
692 ) {
693 let mut blob_entries = self.inner.result_blob_entries.write();
694 let (ref mut blob_map, ref mut blob_order) = *blob_entries;
695 blob_map.clear();
696 blob_order.clear();
697 self.inner
698 .result_blob_cache
699 .invalidate_namespace(RESULT_CACHE_BLOB_NAMESPACE);
700 if self.inner.embedded_single_file {
701 self.invalidate_embedded_result_cache();
702 }
703 } else if blob_has_match {
704 let mut blob_entries = self.inner.result_blob_entries.write();
705 let (ref mut blob_map, ref mut blob_order) = *blob_entries;
706 blob_map.retain(|_, entry| !entry.scopes.contains(table));
707 blob_order.retain(|key| blob_map.contains_key(key));
708 if self.inner.embedded_single_file {
709 self.invalidate_embedded_result_cache_for_scope(table);
710 }
711 }
712 let mut ask_entries = self.inner.ask_answer_cache_entries.write();
713 ask_entries.0.clear();
714 ask_entries.1.clear();
715 self.inner
716 .result_blob_cache
717 .invalidate_namespace(super::ASK_ANSWER_CACHE_NAMESPACE);
718 }
719}