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: reddb_rql::modes::QueryMode) -> u8 {
55 match mode {
56 reddb_rql::modes::QueryMode::Sql => 0,
57 reddb_rql::modes::QueryMode::Gremlin => 1,
58 reddb_rql::modes::QueryMode::Cypher => 2,
59 reddb_rql::modes::QueryMode::Sparql => 3,
60 reddb_rql::modes::QueryMode::Path => 4,
61 reddb_rql::modes::QueryMode::Natural => 5,
62 reddb_rql::modes::QueryMode::Unknown => 255,
63 }
64}
65
66fn mode_from_byte(byte: u8) -> Option<reddb_rql::modes::QueryMode> {
67 match byte {
68 0 => Some(reddb_rql::modes::QueryMode::Sql),
69 1 => Some(reddb_rql::modes::QueryMode::Gremlin),
70 2 => Some(reddb_rql::modes::QueryMode::Cypher),
71 3 => Some(reddb_rql::modes::QueryMode::Sparql),
72 4 => Some(reddb_rql::modes::QueryMode::Path),
73 5 => Some(reddb_rql::modes::QueryMode::Natural),
74 255 => Some(reddb_rql::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.stats.vector.is_some()
168 || result.result.pre_serialized_json.is_some()
169 || result_cache_static_str(result.statement).is_none()
170 || result_cache_static_str(result.engine).is_none()
171 || result_cache_static_str(result.statement_type).is_none()
172 || result.result.records.iter().any(|record| {
173 !record.nodes.is_empty()
174 || !record.edges.is_empty()
175 || !record.paths.is_empty()
176 || !record.vector_results.is_empty()
177 })
178 {
179 return None;
180 }
181
182 let mut out = Vec::new();
183 out.extend_from_slice(RESULT_CACHE_PAYLOAD_MAGIC);
184 write_string(&mut out, &result.query)?;
185 out.push(mode_to_byte(result.mode));
186 write_string(&mut out, result.statement)?;
187 write_string(&mut out, result.engine)?;
188 out.extend_from_slice(&result.affected_rows.to_le_bytes());
189 write_string(&mut out, result.statement_type)?;
190
191 write_u32(&mut out, result.result.columns.len())?;
192 for column in &result.result.columns {
193 write_string(&mut out, column)?;
194 }
195 out.extend_from_slice(&result.result.stats.nodes_scanned.to_le_bytes());
196 out.extend_from_slice(&result.result.stats.edges_scanned.to_le_bytes());
197 out.extend_from_slice(&result.result.stats.rows_scanned.to_le_bytes());
198 out.extend_from_slice(&result.result.stats.segments_total.to_le_bytes());
199 out.extend_from_slice(&result.result.stats.segments_scanned.to_le_bytes());
200 out.extend_from_slice(&result.result.stats.segments_pruned.to_le_bytes());
201 out.extend_from_slice(&result.result.stats.exec_time_us.to_le_bytes());
202
203 write_u32(&mut out, result.result.records.len())?;
204 for record in &result.result.records {
205 let fields = record.iter_fields().collect::<Vec<_>>();
206 write_u32(&mut out, fields.len())?;
207 for (name, value) in fields {
208 write_string(&mut out, name)?;
209 let mut encoded = Vec::new();
210 reddb_types::value_codec::encode(value, &mut encoded);
211 write_bytes(&mut out, &encoded)?;
212 }
213 }
214
215 write_u32(&mut out, entry.scopes.len())?;
216 for scope in &entry.scopes {
217 write_string(&mut out, scope)?;
218 }
219 Some(out)
220}
221
222fn decode_result_cache_payload(mut input: &[u8]) -> Option<(RuntimeQueryResult, HashSet<String>)> {
223 if input.len() < RESULT_CACHE_PAYLOAD_MAGIC.len()
224 || &input[..RESULT_CACHE_PAYLOAD_MAGIC.len()] != RESULT_CACHE_PAYLOAD_MAGIC
225 {
226 return None;
227 }
228 input = &input[RESULT_CACHE_PAYLOAD_MAGIC.len()..];
229
230 let query = read_string(&mut input)?;
231 let mode = mode_from_byte(read_u8(&mut input)?)?;
232 let statement = result_cache_static_str(&read_string(&mut input)?)?;
233 let engine = result_cache_static_str(&read_string(&mut input)?)?;
234 let affected_rows = read_u64(&mut input)?;
235 let statement_type = result_cache_static_str(&read_string(&mut input)?)?;
236
237 let mut columns = Vec::new();
238 for _ in 0..read_u32(&mut input)? {
239 columns.push(read_string(&mut input)?);
240 }
241 let stats = crate::storage::query::unified::QueryStats {
242 nodes_scanned: read_u64(&mut input)?,
243 edges_scanned: read_u64(&mut input)?,
244 rows_scanned: read_u64(&mut input)?,
245 segments_total: read_u64(&mut input)?,
246 segments_scanned: read_u64(&mut input)?,
247 segments_pruned: read_u64(&mut input)?,
248 exec_time_us: read_u64(&mut input)?,
249 vector: None,
250 };
251
252 let mut records = Vec::new();
253 for _ in 0..read_u32(&mut input)? {
254 let mut record = crate::storage::query::unified::UnifiedRecord::new();
255 for _ in 0..read_u32(&mut input)? {
256 let name = read_string(&mut input)?;
257 let bytes = read_bytes(&mut input)?;
258 let (value, used) = reddb_types::value_codec::decode(bytes).ok()?;
259 if used != bytes.len() {
260 return None;
261 }
262 record.set_owned(name, value);
263 }
264 records.push(record);
265 }
266
267 let mut scopes = HashSet::new();
268 for _ in 0..read_u32(&mut input)? {
269 scopes.insert(read_string(&mut input)?);
270 }
271 if !input.is_empty() {
272 return None;
273 }
274
275 Some((
276 RuntimeQueryResult {
277 query,
278 mode,
279 statement,
280 engine,
281 result: crate::storage::query::unified::UnifiedResult {
282 columns,
283 records,
284 stats,
285 pre_serialized_json: None,
286 },
287 affected_rows,
288 statement_type,
289 bookmark: None,
290 notice: None,
291 },
292 scopes,
293 ))
294}
295
296impl RedDBRuntime {
297 fn result_cache_backend(&self) -> RuntimeResultCacheBackend {
298 match self
299 .config_string(RESULT_CACHE_BACKEND_KEY, RESULT_CACHE_DEFAULT_BACKEND)
300 .as_str()
301 {
302 "blob_cache" => RuntimeResultCacheBackend::BlobCache,
303 "shadow" => RuntimeResultCacheBackend::Shadow,
304 _ => RuntimeResultCacheBackend::Legacy,
305 }
306 }
307
308 fn result_cache_enabled(&self) -> bool {
309 self.config_bool(RESULT_CACHE_ENABLED_KEY, true)
310 }
311
312 fn result_cache_ttl_secs(&self) -> u64 {
313 self.config_u64(RESULT_CACHE_TTL_KEY, RESULT_CACHE_TTL_SECS)
314 }
315
316 fn result_cache_capacity(&self) -> usize {
317 self.config_u64(RESULT_CACHE_CAPACITY_KEY, RESULT_CACHE_MAX_ENTRIES as u64) as usize
318 }
319
320 pub fn result_cache_metrics(&self) -> (u64, u64, u64) {
321 use std::sync::atomic::Ordering::Relaxed;
322 (
323 self.inner.result_cache_hits.load(Relaxed),
324 self.inner.result_cache_misses.load(Relaxed),
325 self.inner.result_cache_evictions.load(Relaxed),
326 )
327 }
328
329 fn record_result_cache_evictions(&self, evicted: u64) {
330 if evicted > 0 {
331 self.inner
332 .result_cache_evictions
333 .fetch_add(evicted, std::sync::atomic::Ordering::Relaxed);
334 }
335 }
336
337 pub(super) fn get_result_cache_entry(&self, key: &str) -> Option<RuntimeQueryResult> {
338 if !self.result_cache_enabled() {
339 return None;
340 }
341 let hit = self.get_result_cache_entry_inner(key);
342 let counter = if hit.is_some() {
343 &self.inner.result_cache_hits
344 } else {
345 &self.inner.result_cache_misses
346 };
347 counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
348 hit
349 }
350
351 fn get_result_cache_entry_inner(&self, key: &str) -> Option<RuntimeQueryResult> {
352 match self.result_cache_backend() {
353 RuntimeResultCacheBackend::Legacy => self.get_legacy_result_cache_entry(key),
354 RuntimeResultCacheBackend::BlobCache => self.get_blob_result_cache_entry(key),
355 RuntimeResultCacheBackend::Shadow => {
356 let legacy = self.get_legacy_result_cache_entry(key);
357 let blob = self.get_blob_result_cache_entry(key);
358 if let (Some(ref legacy), Some(ref blob)) = (&legacy, &blob) {
359 if result_cache_fingerprint(legacy) != result_cache_fingerprint(blob) {
360 self.inner
361 .result_cache_shadow_divergences
362 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
363 tracing::warn!(
364 key,
365 metric = crate::runtime::METRIC_CACHE_SHADOW_DIVERGENCE_TOTAL,
366 "result cache shadow backend diverged from legacy"
367 );
368 }
369 }
370 legacy
371 }
372 }
373 }
374
375 fn get_legacy_result_cache_entry(&self, key: &str) -> Option<RuntimeQueryResult> {
376 let ttl = self.result_cache_ttl_secs();
377 let cache = self.inner.result_cache.read();
378 cache.0.get(key).and_then(|entry| {
379 if entry.cached_at.elapsed().as_secs() < ttl {
380 Some(entry.result.clone())
381 } else {
382 None
383 }
384 })
385 }
386
387 fn get_blob_result_cache_entry(&self, key: &str) -> Option<RuntimeQueryResult> {
388 if let Some(result) = self.get_blob_result_cache_entry_from_blob_cache(key) {
389 return Some(result);
390 }
391 if self.inner.embedded_single_file {
395 if let Some(bytes) = self.get_embedded_result_cache_payload(key) {
396 let policy = crate::storage::cache::BlobCachePolicy::default()
397 .ttl_ms(self.result_cache_ttl_secs() * 1000)
398 .priority(200);
399 let put = crate::storage::cache::BlobCachePut::new(bytes).with_policy(policy);
400 if self
401 .inner
402 .result_blob_cache
403 .put(RESULT_CACHE_BLOB_NAMESPACE, key, put)
404 .is_err()
405 {
406 return None;
407 }
408 return self.get_blob_result_cache_entry_from_blob_cache(key);
409 }
410 }
411 None
412 }
413
414 fn get_blob_result_cache_entry_from_blob_cache(&self, key: &str) -> Option<RuntimeQueryResult> {
415 let hit = self
416 .inner
417 .result_blob_cache
418 .get(RESULT_CACHE_BLOB_NAMESPACE, key)?;
419 {
420 let cache = self.inner.result_blob_entries.read();
421 if let Some(entry) = cache.0.get(key) {
422 return Some(entry.result.clone());
423 }
424 }
425
426 let (result, scopes) = decode_result_cache_payload(hit.value())?;
427 let mut cache = self.inner.result_blob_entries.write();
428 let (ref mut map, ref mut order) = *cache;
429 let new_entry = RuntimeResultCacheEntry {
430 result: result.clone(),
431 cached_at: std::time::Instant::now(),
432 scopes,
433 };
434 match map.entry(key.to_string()) {
436 Entry::Occupied(mut slot) => {
437 slot.insert(new_entry);
438 }
439 Entry::Vacant(slot) => {
440 order.push_back(slot.key().clone());
441 slot.insert(new_entry);
442 }
443 }
444 let evicted = trim_result_cache(map, order, self.result_cache_capacity());
445 drop(cache);
446 self.record_result_cache_evictions(evicted);
447 Some(result)
448 }
449
450 fn get_embedded_result_cache_payload(&self, key: &str) -> Option<Vec<u8>> {
451 let manager = self
452 .inner
453 .db
454 .store()
455 .get_collection(RESULT_CACHE_EMBEDDED_COLLECTION)?;
456 let mut latest: Option<(u64, Vec<u8>)> = None;
457 manager.for_each_entity(|entity| {
458 let Some(row) = entity.data.as_row() else {
459 return true;
460 };
461 let namespace_matches = row.get_field("namespace").and_then(|value| match value {
462 reddb_types::Value::Text(value) => Some(value.as_ref()),
463 _ => None,
464 }) == Some(RESULT_CACHE_BLOB_NAMESPACE);
465 let key_matches = row.get_field("key").and_then(|value| match value {
466 reddb_types::Value::Text(value) => Some(value.as_ref()),
467 _ => None,
468 }) == Some(key);
469 if namespace_matches && key_matches {
470 if let Some(reddb_types::Value::Blob(payload)) = row.get_field("payload") {
471 let id = entity.id.raw();
472 if latest
473 .as_ref()
474 .is_none_or(|(latest_id, _)| id >= *latest_id)
475 {
476 latest = Some((id, payload.clone()));
477 }
478 }
479 }
480 true
481 });
482 latest.map(|(_, payload)| payload)
483 }
484
485 pub(super) fn put_result_cache_entry(&self, key: &str, entry: RuntimeResultCacheEntry) {
486 if !self.result_cache_enabled() {
487 return;
488 }
489 match self.result_cache_backend() {
490 RuntimeResultCacheBackend::Legacy => self.put_legacy_result_cache_entry(key, entry),
491 RuntimeResultCacheBackend::BlobCache => self.put_blob_result_cache_entry(key, entry),
492 RuntimeResultCacheBackend::Shadow => {
493 self.put_legacy_result_cache_entry(key, entry.clone());
494 self.put_blob_result_cache_entry(key, entry);
495 }
496 }
497 }
498
499 fn put_legacy_result_cache_entry(&self, key: &str, entry: RuntimeResultCacheEntry) {
500 let capacity = self.result_cache_capacity();
501 let mut cache = self.inner.result_cache.write();
502 let (ref mut map, ref mut order) = *cache;
503 match map.entry(key.to_string()) {
505 Entry::Occupied(mut slot) => {
506 slot.insert(entry);
507 }
508 Entry::Vacant(slot) => {
509 order.push_back(slot.key().clone());
510 slot.insert(entry);
511 }
512 }
513 let evicted = trim_result_cache(map, order, capacity);
514 drop(cache);
515 self.record_result_cache_evictions(evicted);
516 }
517
518 fn put_blob_result_cache_entry(&self, key: &str, entry: RuntimeResultCacheEntry) {
519 let policy = crate::storage::cache::BlobCachePolicy::default()
520 .ttl_ms(self.result_cache_ttl_secs() * 1000)
521 .priority(200);
522 let dependencies = entry.scopes.iter().cloned().collect::<Vec<_>>();
523 let bytes = encode_result_cache_payload(&entry)
524 .unwrap_or_else(|| result_cache_fingerprint(&entry.result).into_bytes());
525 let put = crate::storage::cache::BlobCachePut::new(bytes)
526 .with_dependencies(dependencies)
527 .with_policy(policy);
528 if self
529 .inner
530 .result_blob_cache
531 .put(RESULT_CACHE_BLOB_NAMESPACE, key, put)
532 .is_err()
533 {
534 return;
535 }
536
537 let capacity = self.result_cache_capacity();
538 let mut cache = self.inner.result_blob_entries.write();
539 let (ref mut map, ref mut order) = *cache;
540 match map.entry(key.to_string()) {
542 Entry::Occupied(mut slot) => {
543 slot.insert(entry);
544 }
545 Entry::Vacant(slot) => {
546 order.push_back(slot.key().clone());
547 slot.insert(entry);
548 }
549 }
550 let evicted = trim_result_cache(map, order, capacity);
551 drop(cache);
552 self.record_result_cache_evictions(evicted);
553 }
554
555 fn invalidate_embedded_result_cache(&self) {
556 let Some(manager) = self
557 .inner
558 .db
559 .store()
560 .get_collection(RESULT_CACHE_EMBEDDED_COLLECTION)
561 else {
562 return;
563 };
564 let ids = manager
565 .query_all(|_| true)
566 .into_iter()
567 .map(|entity| entity.id)
568 .collect::<Vec<_>>();
569 if !ids.is_empty() {
570 let _ = self
571 .inner
572 .db
573 .store()
574 .delete_batch(RESULT_CACHE_EMBEDDED_COLLECTION, &ids);
575 }
576 }
577
578 fn invalidate_embedded_result_cache_for_scope(&self, scope: &str) {
579 let Some(manager) = self
580 .inner
581 .db
582 .store()
583 .get_collection(RESULT_CACHE_EMBEDDED_COLLECTION)
584 else {
585 return;
586 };
587 let ids = manager
588 .query_all(|entity| {
589 entity
590 .data
591 .as_row()
592 .and_then(|row| row.get_field("scopes"))
593 .and_then(|value| match value {
594 reddb_types::Value::Text(value) => Some(value.as_ref()),
595 _ => None,
596 })
597 .is_some_and(|scopes| scopes.lines().any(|entry| entry == scope))
598 })
599 .into_iter()
600 .map(|entity| entity.id)
601 .collect::<Vec<_>>();
602 if !ids.is_empty() {
603 let _ = self
604 .inner
605 .db
606 .store()
607 .delete_batch(RESULT_CACHE_EMBEDDED_COLLECTION, &ids);
608 }
609 }
610
611 pub fn result_cache_shadow_divergences(&self) -> u64 {
612 self.inner
613 .result_cache_shadow_divergences
614 .load(std::sync::atomic::Ordering::Relaxed)
615 }
616
617 pub fn invalidate_result_cache(&self) {
618 self.invalidate_result_cache_process_only();
619 if self.inner.embedded_single_file {
620 self.invalidate_embedded_result_cache();
621 }
622 }
623
624 pub(crate) fn invalidate_result_cache_process_only(&self) {
625 let mut cache = self.inner.result_cache.write();
626 cache.0.clear();
627 cache.1.clear();
628 let mut blob_entries = self.inner.result_blob_entries.write();
629 blob_entries.0.clear();
630 blob_entries.1.clear();
631 self.inner
632 .result_blob_cache
633 .invalidate_namespace(RESULT_CACHE_BLOB_NAMESPACE);
634 let mut ask_entries = self.inner.ask_answer_cache_entries.write();
635 ask_entries.0.clear();
636 ask_entries.1.clear();
637 self.inner
638 .result_blob_cache
639 .invalidate_namespace(super::ASK_ANSWER_CACHE_NAMESPACE);
640 }
641
642 pub(crate) fn invalidate_result_cache_for_table(&self, table: &str) {
643 let legacy_has_match = {
644 let cache = self.inner.result_cache.read();
645 let (ref map, _) = *cache;
646 !map.is_empty() && map.values().any(|entry| entry.scopes.contains(table))
647 };
648 let blob_has_match = {
649 let cache = self.inner.result_blob_entries.read();
650 let (ref map, _) = *cache;
651 !map.is_empty() && map.values().any(|entry| entry.scopes.contains(table))
652 };
653 if legacy_has_match {
654 let mut cache = self.inner.result_cache.write();
655 let (ref mut map, ref mut order) = *cache;
656 map.retain(|_, entry| !entry.scopes.contains(table));
657 order.retain(|key| map.contains_key(key));
658 }
659
660 if matches!(
661 self.result_cache_backend(),
662 RuntimeResultCacheBackend::BlobCache | RuntimeResultCacheBackend::Shadow
663 ) {
664 let mut blob_entries = self.inner.result_blob_entries.write();
665 let (ref mut blob_map, ref mut blob_order) = *blob_entries;
666 blob_map.clear();
667 blob_order.clear();
668 self.inner
669 .result_blob_cache
670 .invalidate_namespace(RESULT_CACHE_BLOB_NAMESPACE);
671 if self.inner.embedded_single_file {
672 self.invalidate_embedded_result_cache();
673 }
674 } else if blob_has_match {
675 let mut blob_entries = self.inner.result_blob_entries.write();
676 let (ref mut blob_map, ref mut blob_order) = *blob_entries;
677 blob_map.retain(|_, entry| !entry.scopes.contains(table));
678 blob_order.retain(|key| blob_map.contains_key(key));
679 if self.inner.embedded_single_file {
680 self.invalidate_embedded_result_cache_for_scope(table);
681 }
682 }
683 let mut ask_entries = self.inner.ask_answer_cache_entries.write();
684 ask_entries.0.clear();
685 ask_entries.1.clear();
686 self.inner
687 .result_blob_cache
688 .invalidate_namespace(super::ASK_ANSWER_CACHE_NAMESPACE);
689 }
690}