1use crate::{
7 db::{
8 data::{CanonicalRow, RawDataStoreKey, RawRow},
9 direction::Direction,
10 ordered_overlay::{OrderedOverlayEntry, OrderedOverlayVisit, visit_ordered_overlay},
11 },
12 types::EntityTag,
13};
14use ic_stable_structures::{
15 BTreeMap as StableBTreeMap, DefaultMemoryImpl, memory_manager::VirtualMemory,
16};
17#[cfg(all(feature = "sql", feature = "diagnostics"))]
18use std::cell::Cell;
19use std::collections::{BTreeMap as HeapBTreeMap, BTreeSet};
20use std::convert::Infallible;
21use std::ops::{Bound, RangeBounds};
22
23#[cfg(all(feature = "sql", feature = "diagnostics"))]
24thread_local! {
25 static DATA_STORE_GET_CALL_COUNT: Cell<u64> = const { Cell::new(0) };
26}
27
28#[cfg(all(feature = "sql", feature = "diagnostics"))]
29fn record_data_store_get_call() {
30 DATA_STORE_GET_CALL_COUNT.with(|count| {
31 count.set(count.get().saturating_add(1));
32 });
33}
34
35pub struct DataStore {
45 backend: DataStoreBackend,
46 generation: u64,
47 entity_cardinality: EntityCardinality,
48}
49
50enum DataStoreBackend {
51 Heap(HeapBTreeMap<RawDataStoreKey, RawRow>),
52 Journaled {
53 canonical: StableBTreeMap<RawDataStoreKey, RawRow, VirtualMemory<DefaultMemoryImpl>>,
54 live: HeapBTreeMap<RawDataStoreKey, RawRow>,
55 tombstones: BTreeSet<RawDataStoreKey>,
56 },
57}
58
59pub(in crate::db) enum StoredRowRead<'a> {
65 Missing,
66 Borrowed(&'a RawRow),
67 Owned(RawRow),
68}
69
70impl StoredRowRead<'_> {
71 #[must_use]
73 pub(in crate::db) const fn as_row(&self) -> Option<&RawRow> {
74 match self {
75 Self::Missing => None,
76 Self::Borrowed(row) => Some(row),
77 Self::Owned(row) => Some(row),
78 }
79 }
80
81 #[must_use]
83 fn into_owned(self) -> Option<RawRow> {
84 match self {
85 Self::Missing => None,
86 Self::Borrowed(row) => Some(row.clone()),
87 Self::Owned(row) => Some(row),
88 }
89 }
90}
91
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
94pub(in crate::db) enum StoreVisit {
95 Continue,
96 Stop,
97}
98
99impl StoreVisit {
100 const fn should_stop(self) -> bool {
101 matches!(self, Self::Stop)
102 }
103}
104
105impl DataStore {
106 #[must_use]
108 pub const fn init_heap() -> Self {
109 Self {
110 backend: DataStoreBackend::Heap(HeapBTreeMap::new()),
111 generation: 0,
112 entity_cardinality: EntityCardinality::empty(),
113 }
114 }
115
116 #[must_use]
122 pub fn init_journaled(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
123 let canonical = StableBTreeMap::init(memory);
124 let entity_cardinality = if canonical.is_empty() {
125 EntityCardinality::empty()
126 } else {
127 EntityCardinality::unavailable()
128 };
129 Self {
130 backend: DataStoreBackend::Journaled {
131 canonical,
132 live: HeapBTreeMap::new(),
133 tombstones: BTreeSet::new(),
134 },
135 generation: 0,
136 entity_cardinality,
140 }
141 }
142
143 pub(in crate::db) fn insert(
145 &mut self,
146 key: RawDataStoreKey,
147 row: CanonicalRow,
148 ) -> Option<RawRow> {
149 let row = row.into_raw_row();
150 let previous_journaled = if matches!(self.backend, DataStoreBackend::Journaled { .. }) {
151 self.get(&key)
152 } else {
153 None
154 };
155 let cardinality_key = key.clone();
156 let previous = match &mut self.backend {
157 DataStoreBackend::Heap(map) => map.insert(key, row),
158 DataStoreBackend::Journaled {
159 live, tombstones, ..
160 } => {
161 tombstones.remove(&key);
162 live.insert(key, row);
163 previous_journaled
164 }
165 };
166 self.entity_cardinality
167 .apply_insert(&cardinality_key, previous.as_ref());
168 self.bump_generation();
169 previous
170 }
171
172 #[cfg(test)]
174 pub(in crate::db) fn insert_raw_for_test(
175 &mut self,
176 key: RawDataStoreKey,
177 row: RawRow,
178 ) -> Option<RawRow> {
179 let previous_journaled = if matches!(self.backend, DataStoreBackend::Journaled { .. }) {
180 self.get(&key)
181 } else {
182 None
183 };
184 let cardinality_key = key.clone();
185 let previous = match &mut self.backend {
186 DataStoreBackend::Heap(map) => map.insert(key, row),
187 DataStoreBackend::Journaled {
188 live, tombstones, ..
189 } => {
190 tombstones.remove(&key);
191 live.insert(key, row);
192 previous_journaled
193 }
194 };
195 self.entity_cardinality
196 .apply_insert(&cardinality_key, previous.as_ref());
197 self.bump_generation();
198 previous
199 }
200
201 pub(in crate::db) fn remove(&mut self, key: &RawDataStoreKey) -> Option<RawRow> {
203 let previous_journaled = if matches!(self.backend, DataStoreBackend::Journaled { .. }) {
204 self.get(key)
205 } else {
206 None
207 };
208 let previous = match &mut self.backend {
209 DataStoreBackend::Heap(map) => map.remove(key),
210 DataStoreBackend::Journaled {
211 live, tombstones, ..
212 } => {
213 live.remove(key);
214 tombstones.insert(key.clone());
215 previous_journaled
216 }
217 };
218 self.entity_cardinality.apply_remove(key, previous.as_ref());
219 self.bump_generation();
220 previous
221 }
222
223 pub(in crate::db) fn reset_journaled_live_projection(
226 &mut self,
227 ) -> Result<(), crate::error::InternalError> {
228 let DataStoreBackend::Journaled {
229 canonical,
230 live,
231 tombstones,
232 } = &mut self.backend
233 else {
234 return Err(crate::error::InternalError::store_invariant());
235 };
236
237 live.clear();
238 tombstones.clear();
239 self.entity_cardinality = if canonical.is_empty() {
240 EntityCardinality::empty()
241 } else {
242 EntityCardinality::unavailable()
243 };
244 self.bump_generation();
245
246 Ok(())
247 }
248
249 pub(in crate::db) fn apply_recovered_journal_put(
251 &mut self,
252 key: RawDataStoreKey,
253 row: RawRow,
254 ) -> Result<Option<RawRow>, crate::error::InternalError> {
255 let DataStoreBackend::Journaled {
256 canonical,
257 live,
258 tombstones,
259 } = &mut self.backend
260 else {
261 return Err(crate::error::InternalError::store_invariant());
262 };
263
264 let previous = if tombstones.contains(&key) {
265 None
266 } else {
267 live.get(&key).cloned().or_else(|| canonical.get(&key))
268 };
269 tombstones.remove(&key);
270 let cardinality_key = key.clone();
271 live.insert(key, row);
272 self.entity_cardinality
273 .apply_insert(&cardinality_key, previous.as_ref());
274 self.bump_generation();
275
276 Ok(previous)
277 }
278
279 pub(in crate::db) fn apply_recovered_journal_delete(
281 &mut self,
282 key: &RawDataStoreKey,
283 ) -> Result<Option<RawRow>, crate::error::InternalError> {
284 let DataStoreBackend::Journaled {
285 canonical,
286 live,
287 tombstones,
288 } = &mut self.backend
289 else {
290 return Err(crate::error::InternalError::store_invariant());
291 };
292
293 let previous = if tombstones.contains(key) {
294 None
295 } else {
296 live.get(key).cloned().or_else(|| canonical.get(key))
297 };
298 live.remove(key);
299 tombstones.insert(key.clone());
300 self.entity_cardinality.apply_remove(key, previous.as_ref());
301 self.bump_generation();
302
303 Ok(previous)
304 }
305
306 pub(in crate::db) fn fold_recovered_journal_put(
308 &mut self,
309 key: RawDataStoreKey,
310 row: RawRow,
311 ) -> Result<Option<RawRow>, crate::error::InternalError> {
312 let DataStoreBackend::Journaled {
313 canonical,
314 live,
315 tombstones,
316 } = &mut self.backend
317 else {
318 return Err(crate::error::InternalError::store_invariant());
319 };
320
321 let visible = !live.contains_key(&key) && !tombstones.contains(&key);
322 let cardinality_key = key.clone();
323 let previous = canonical.insert(key, row);
324 if visible {
325 self.entity_cardinality
326 .apply_insert(&cardinality_key, previous.as_ref());
327 }
328 self.bump_generation();
329
330 Ok(previous)
331 }
332
333 pub(in crate::db) fn fold_recovered_journal_delete(
335 &mut self,
336 key: &RawDataStoreKey,
337 ) -> Result<Option<RawRow>, crate::error::InternalError> {
338 let DataStoreBackend::Journaled {
339 canonical,
340 live,
341 tombstones,
342 } = &mut self.backend
343 else {
344 return Err(crate::error::InternalError::store_invariant());
345 };
346
347 let visible = !live.contains_key(key) && !tombstones.contains(key);
348 let previous = canonical.remove(key);
349 if visible {
350 self.entity_cardinality.apply_remove(key, previous.as_ref());
351 }
352 self.bump_generation();
353
354 Ok(previous)
355 }
356
357 pub(in crate::db) fn get(&self, key: &RawDataStoreKey) -> Option<RawRow> {
359 self.read(key).into_owned()
360 }
361
362 pub(in crate::db) fn read<'a>(&'a self, key: &RawDataStoreKey) -> StoredRowRead<'a> {
364 #[cfg(all(feature = "sql", feature = "diagnostics"))]
365 record_data_store_get_call();
366
367 match &self.backend {
368 DataStoreBackend::Heap(map) => map
369 .get(key)
370 .map_or(StoredRowRead::Missing, StoredRowRead::Borrowed),
371 DataStoreBackend::Journaled {
372 canonical,
373 live,
374 tombstones,
375 } => {
376 if tombstones.contains(key) {
377 StoredRowRead::Missing
378 } else if let Some(row) = live.get(key) {
379 StoredRowRead::Borrowed(row)
380 } else {
381 canonical
382 .get(key)
383 .map_or(StoredRowRead::Missing, StoredRowRead::Owned)
384 }
385 }
386 }
387 }
388
389 #[must_use]
391 pub(in crate::db) fn contains(&self, key: &RawDataStoreKey) -> bool {
392 match &self.backend {
393 DataStoreBackend::Heap(map) => map.contains_key(key),
394 DataStoreBackend::Journaled {
395 canonical,
396 live,
397 tombstones,
398 } => {
399 !tombstones.contains(key)
400 && (live.contains_key(key) || canonical.get(key).is_some())
401 }
402 }
403 }
404
405 #[must_use]
407 pub(in crate::db) fn len(&self) -> u64 {
408 match &self.backend {
409 DataStoreBackend::Heap(map) => u64::try_from(map.len()).unwrap_or(u64::MAX),
410 DataStoreBackend::Journaled { .. } => {
411 let mut count = 0_u64;
412 let _: Result<(), Infallible> = self.visit_entries(|_key, _row| {
413 count = count.saturating_add(1);
414 Ok(StoreVisit::Continue)
415 });
416 count
417 }
418 }
419 }
420
421 #[must_use]
423 pub(in crate::db) const fn generation(&self) -> u64 {
424 self.generation
425 }
426
427 #[must_use]
429 pub(in crate::db) fn exact_entity_count(&self, entity: EntityTag) -> Option<u64> {
430 self.entity_cardinality.exact_count(entity)
431 }
432
433 pub(in crate::db) fn visit_entries<E>(
435 &self,
436 mut visitor: impl FnMut(&RawDataStoreKey, &RawRow) -> Result<StoreVisit, E>,
437 ) -> Result<(), E> {
438 match &self.backend {
439 DataStoreBackend::Heap(map) => {
440 for (key, row) in map {
441 if visitor(key, row)?.should_stop() {
442 break;
443 }
444 }
445 }
446 DataStoreBackend::Journaled {
447 canonical: _,
448 live: _,
449 tombstones: _,
450 } => Self::visit_journaled_entries_in_bounds(
451 &self.backend,
452 (Bound::Unbounded, Bound::Unbounded),
453 visitor,
454 )?,
455 }
456
457 Ok(())
458 }
459
460 pub(in crate::db) fn visit_range<E>(
462 &self,
463 key_range: impl RangeBounds<RawDataStoreKey>,
464 mut visitor: impl FnMut(&RawDataStoreKey, &RawRow) -> Result<StoreVisit, E>,
465 ) -> Result<(), E> {
466 let bounds = Self::owned_range_bounds(&key_range);
467 match &self.backend {
468 DataStoreBackend::Heap(map) => {
469 for (key, row) in map.range((bounds.0.clone(), bounds.1)) {
470 if visitor(key, row)?.should_stop() {
471 break;
472 }
473 }
474 }
475 DataStoreBackend::Journaled {
476 canonical: _,
477 live: _,
478 tombstones: _,
479 } => Self::visit_journaled_entries_in_bounds(&self.backend, bounds, visitor)?,
480 }
481
482 Ok(())
483 }
484
485 pub(in crate::db) fn try_visit_range_with_row_preflight<E>(
492 &self,
493 key_range: impl RangeBounds<RawDataStoreKey>,
494 mut preflight: impl FnMut(&RawDataStoreKey) -> Result<StoreVisit, E>,
495 mut visitor: impl FnMut(&RawDataStoreKey, &RawRow) -> Result<StoreVisit, E>,
496 ) -> Result<Option<bool>, E> {
497 let bounds = Self::owned_range_bounds(&key_range);
498 let mut stopped = false;
499 match &self.backend {
500 DataStoreBackend::Heap(map) => {
501 for (key, row) in map.range((bounds.0.clone(), bounds.1)) {
502 if preflight(key)?.should_stop() {
503 stopped = true;
504 break;
505 }
506 if visitor(key, row)?.should_stop() {
507 stopped = true;
508 break;
509 }
510 }
511 }
512 DataStoreBackend::Journaled {
513 canonical,
514 live,
515 tombstones,
516 } if canonical.is_empty() => {
517 for (key, row) in live.range((bounds.0.clone(), bounds.1)) {
518 if tombstones.contains(key) {
519 continue;
520 }
521 if preflight(key)?.should_stop() {
522 stopped = true;
523 break;
524 }
525 if visitor(key, row)?.should_stop() {
526 stopped = true;
527 break;
528 }
529 }
530 }
531 DataStoreBackend::Journaled {
532 canonical,
533 live,
534 tombstones,
535 } if live.is_empty() && tombstones.is_empty() => {
536 for entry in canonical.range((bounds.0.clone(), bounds.1)) {
537 if preflight(entry.key())?.should_stop() {
538 stopped = true;
539 break;
540 }
541 if visitor(entry.key(), &entry.value())?.should_stop() {
542 stopped = true;
543 break;
544 }
545 }
546 }
547 DataStoreBackend::Journaled { .. } => return Ok(None),
548 }
549
550 Ok(Some(!stopped))
551 }
552
553 pub(in crate::db) fn visit_key_range<E>(
560 &self,
561 key_range: impl RangeBounds<RawDataStoreKey>,
562 visitor: impl FnMut(&RawDataStoreKey) -> Result<StoreVisit, E>,
563 ) -> Result<(), E> {
564 self.visit_keys_in_bounds(Self::owned_range_bounds(&key_range), false, visitor)
565 }
566
567 pub(in crate::db) fn visit_key_range_rev<E>(
569 &self,
570 key_range: impl RangeBounds<RawDataStoreKey>,
571 visitor: impl FnMut(&RawDataStoreKey) -> Result<StoreVisit, E>,
572 ) -> Result<(), E> {
573 self.visit_keys_in_bounds(Self::owned_range_bounds(&key_range), true, visitor)
574 }
575
576 pub(in crate::db) fn memory_bytes(&self) -> u64 {
578 let mut bytes = 0u64;
580 let _: Result<(), Infallible> = self.visit_entries(|key, row| {
581 bytes = bytes.saturating_add(key.as_bytes().len() as u64 + row.len() as u64);
582 Ok(StoreVisit::Continue)
583 });
584 bytes
585 }
586
587 const fn bump_generation(&mut self) {
588 self.generation = self.generation.saturating_add(1);
589 }
590
591 #[cfg(test)]
592 fn rebuild_entity_cardinality_from_entries(&mut self) {
593 let mut cardinality = EntityCardinality::empty();
594 let _: Result<(), Infallible> = self.visit_entries(|key, _row| {
595 cardinality.apply_present_key(key);
596 Ok(StoreVisit::Continue)
597 });
598 self.entity_cardinality = cardinality;
599 }
600
601 #[cfg(all(feature = "sql", feature = "diagnostics"))]
603 pub(in crate::db) fn current_get_call_count() -> u64 {
604 DATA_STORE_GET_CALL_COUNT.with(Cell::get)
605 }
606
607 fn owned_range_bounds(
608 key_range: &impl RangeBounds<RawDataStoreKey>,
609 ) -> (Bound<RawDataStoreKey>, Bound<RawDataStoreKey>) {
610 let lower = match key_range.start_bound() {
611 Bound::Included(key) => Bound::Included(key.clone()),
612 Bound::Excluded(key) => Bound::Excluded(key.clone()),
613 Bound::Unbounded => Bound::Unbounded,
614 };
615 let upper = match key_range.end_bound() {
616 Bound::Included(key) => Bound::Included(key.clone()),
617 Bound::Excluded(key) => Bound::Excluded(key.clone()),
618 Bound::Unbounded => Bound::Unbounded,
619 };
620
621 (lower, upper)
622 }
623
624 fn visit_keys_in_bounds<E>(
625 &self,
626 bounds: (Bound<RawDataStoreKey>, Bound<RawDataStoreKey>),
627 reverse: bool,
628 mut visitor: impl FnMut(&RawDataStoreKey) -> Result<StoreVisit, E>,
629 ) -> Result<(), E> {
630 match &self.backend {
631 DataStoreBackend::Heap(map) => {
632 if reverse {
633 for (key, _row) in map.range(bounds).rev() {
634 if visitor(key)?.should_stop() {
635 break;
636 }
637 }
638 } else {
639 for (key, _row) in map.range(bounds) {
640 if visitor(key)?.should_stop() {
641 break;
642 }
643 }
644 }
645 }
646 DataStoreBackend::Journaled { .. } => {
647 Self::visit_journaled_keys_in_bounds(&self.backend, bounds, reverse, visitor)?;
648 }
649 }
650
651 Ok(())
652 }
653
654 fn visit_journaled_keys_in_bounds<E>(
655 backend: &DataStoreBackend,
656 bounds: (Bound<RawDataStoreKey>, Bound<RawDataStoreKey>),
657 reverse: bool,
658 mut visitor: impl FnMut(&RawDataStoreKey) -> Result<StoreVisit, E>,
659 ) -> Result<(), E> {
660 let DataStoreBackend::Journaled {
661 canonical,
662 live,
663 tombstones,
664 } = backend
665 else {
666 return Ok(());
667 };
668
669 if canonical.is_empty() {
670 if reverse {
671 for (key, _row) in live.range(bounds).rev() {
672 if visitor(key)?.should_stop() {
673 return Ok(());
674 }
675 }
676 } else {
677 for (key, _row) in live.range(bounds) {
678 if visitor(key)?.should_stop() {
679 return Ok(());
680 }
681 }
682 }
683 return Ok(());
684 }
685
686 if live.is_empty() && tombstones.is_empty() {
687 if reverse {
688 for entry in canonical.range(bounds).rev() {
689 if visitor(entry.key())?.should_stop() {
690 return Ok(());
691 }
692 }
693 } else {
694 for entry in canonical.range(bounds) {
695 if visitor(entry.key())?.should_stop() {
696 return Ok(());
697 }
698 }
699 }
700 return Ok(());
701 }
702
703 let direction = if reverse {
704 Direction::Desc
705 } else {
706 Direction::Asc
707 };
708 match direction {
709 Direction::Asc => visit_ordered_overlay(
710 canonical.range((bounds.0.clone(), bounds.1.clone())),
711 live.range((bounds.0, bounds.1)),
712 direction,
713 |canonical_entry, live_entry| canonical_entry.key().cmp(live_entry.0),
714 |canonical_entry| !tombstones.contains(canonical_entry.key()),
715 |live_entry| !tombstones.contains(live_entry.0),
716 |entry| {
717 let visit = match entry {
718 OrderedOverlayEntry::Canonical(canonical_entry) => {
719 visitor(canonical_entry.key())?
720 }
721 OrderedOverlayEntry::Live((key, _row)) => visitor(key)?,
722 };
723 Ok(if visit.should_stop() {
724 OrderedOverlayVisit::Stop
725 } else {
726 OrderedOverlayVisit::Continue
727 })
728 },
729 ),
730 Direction::Desc => visit_ordered_overlay(
731 canonical.range((bounds.0.clone(), bounds.1.clone())).rev(),
732 live.range((bounds.0, bounds.1)).rev(),
733 direction,
734 |canonical_entry, live_entry| canonical_entry.key().cmp(live_entry.0),
735 |canonical_entry| !tombstones.contains(canonical_entry.key()),
736 |live_entry| !tombstones.contains(live_entry.0),
737 |entry| {
738 let visit = match entry {
739 OrderedOverlayEntry::Canonical(canonical_entry) => {
740 visitor(canonical_entry.key())?
741 }
742 OrderedOverlayEntry::Live((key, _row)) => visitor(key)?,
743 };
744 Ok(if visit.should_stop() {
745 OrderedOverlayVisit::Stop
746 } else {
747 OrderedOverlayVisit::Continue
748 })
749 },
750 ),
751 }
752 }
753
754 fn visit_journaled_entries_in_bounds<E>(
755 backend: &DataStoreBackend,
756 bounds: (Bound<RawDataStoreKey>, Bound<RawDataStoreKey>),
757 mut visitor: impl FnMut(&RawDataStoreKey, &RawRow) -> Result<StoreVisit, E>,
758 ) -> Result<(), E> {
759 let DataStoreBackend::Journaled {
760 canonical,
761 live,
762 tombstones,
763 } = backend
764 else {
765 return Ok(());
766 };
767
768 if canonical.is_empty() {
769 for (key, row) in live.range(bounds) {
770 if visitor(key, row)?.should_stop() {
771 return Ok(());
772 }
773 }
774 return Ok(());
775 }
776
777 if live.is_empty() && tombstones.is_empty() {
778 for entry in canonical.range(bounds) {
779 if visitor(entry.key(), &entry.value())?.should_stop() {
780 return Ok(());
781 }
782 }
783 return Ok(());
784 }
785
786 visit_ordered_overlay(
787 canonical.range((bounds.0.clone(), bounds.1.clone())),
788 live.range((bounds.0, bounds.1)),
789 Direction::Asc,
790 |canonical_entry, live_entry| canonical_entry.key().cmp(live_entry.0),
791 |canonical_entry| !tombstones.contains(canonical_entry.key()),
792 |live_entry| !tombstones.contains(live_entry.0),
793 |entry| {
794 let visit = match entry {
795 OrderedOverlayEntry::Canonical(canonical_entry) => {
796 visitor(canonical_entry.key(), &canonical_entry.value())?
797 }
798 OrderedOverlayEntry::Live((key, row)) => visitor(key, row)?,
799 };
800 Ok(if visit.should_stop() {
801 OrderedOverlayVisit::Stop
802 } else {
803 OrderedOverlayVisit::Continue
804 })
805 },
806 )
807 }
808}
809
810#[derive(Clone, Debug)]
811struct EntityCardinality {
812 counts: HeapBTreeMap<EntityTag, u64>,
813 decodable: bool,
814}
815
816impl EntityCardinality {
817 const fn empty() -> Self {
818 Self {
819 counts: HeapBTreeMap::new(),
820 decodable: true,
821 }
822 }
823
824 const fn unavailable() -> Self {
825 Self {
826 counts: HeapBTreeMap::new(),
827 decodable: false,
828 }
829 }
830
831 fn exact_count(&self, entity: EntityTag) -> Option<u64> {
832 self.decodable
833 .then(|| self.counts.get(&entity).copied().unwrap_or(0))
834 }
835
836 fn apply_insert(&mut self, key: &RawDataStoreKey, previous: Option<&RawRow>) {
837 if previous.is_some() {
838 return;
839 }
840 self.apply_present_key(key);
841 }
842
843 fn apply_remove(&mut self, key: &RawDataStoreKey, previous: Option<&RawRow>) {
844 if previous.is_none() {
845 return;
846 }
847 self.apply_removed_key(key);
848 }
849
850 fn apply_present_key(&mut self, key: &RawDataStoreKey) {
851 if !self.decodable {
852 return;
853 }
854 let Some(entity) = key.entity_tag_prefix() else {
855 self.invalidate();
856 return;
857 };
858
859 let count = self.counts.entry(entity).or_insert(0);
860 *count = count.saturating_add(1);
861 }
862
863 fn apply_removed_key(&mut self, key: &RawDataStoreKey) {
864 if !self.decodable {
865 return;
866 }
867 let Some(entity) = key.entity_tag_prefix() else {
868 self.invalidate();
869 return;
870 };
871
872 if let Some(count) = self.counts.get_mut(&entity) {
873 *count = count.saturating_sub(1);
874 if *count == 0 {
875 self.counts.remove(&entity);
876 }
877 }
878 }
879
880 fn invalidate(&mut self) {
881 self.counts.clear();
882 self.decodable = false;
883 }
884}
885
886#[cfg(test)]
887mod tests;