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