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(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(feature = "diagnostics")]
24thread_local! {
25 static DATA_STORE_GET_CALL_COUNT: Cell<u64> = const { Cell::new(0) };
26}
27
28#[cfg(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
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
61pub(in crate::db) enum StoreVisit {
62 Continue,
63 Stop,
64}
65
66impl StoreVisit {
67 const fn should_stop(self) -> bool {
68 matches!(self, Self::Stop)
69 }
70}
71
72impl DataStore {
73 #[must_use]
75 pub const fn init_heap() -> Self {
76 Self {
77 backend: DataStoreBackend::Heap(HeapBTreeMap::new()),
78 generation: 0,
79 entity_cardinality: EntityCardinality::empty(),
80 }
81 }
82
83 #[must_use]
89 pub fn init_journaled(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
90 let mut store = Self {
91 backend: DataStoreBackend::Journaled {
92 canonical: StableBTreeMap::init(memory),
93 live: HeapBTreeMap::new(),
94 tombstones: BTreeSet::new(),
95 },
96 generation: 0,
97 entity_cardinality: EntityCardinality::empty(),
98 };
99 store.rebuild_entity_cardinality_from_entries();
100 store
101 }
102
103 pub(in crate::db) fn insert(
105 &mut self,
106 key: RawDataStoreKey,
107 row: CanonicalRow,
108 ) -> Option<RawRow> {
109 let row = row.into_raw_row();
110 let previous_journaled = if matches!(self.backend, DataStoreBackend::Journaled { .. }) {
111 self.get(&key)
112 } else {
113 None
114 };
115 let cardinality_key = key.clone();
116 let previous = match &mut self.backend {
117 DataStoreBackend::Heap(map) => map.insert(key, row),
118 DataStoreBackend::Journaled {
119 live, tombstones, ..
120 } => {
121 tombstones.remove(&key);
122 live.insert(key, row);
123 previous_journaled
124 }
125 };
126 self.entity_cardinality
127 .apply_insert(&cardinality_key, previous.as_ref());
128 self.bump_generation();
129 previous
130 }
131
132 #[cfg(test)]
134 pub(in crate::db) fn insert_raw_for_test(
135 &mut self,
136 key: RawDataStoreKey,
137 row: RawRow,
138 ) -> Option<RawRow> {
139 let previous_journaled = if matches!(self.backend, DataStoreBackend::Journaled { .. }) {
140 self.get(&key)
141 } else {
142 None
143 };
144 let cardinality_key = key.clone();
145 let previous = match &mut self.backend {
146 DataStoreBackend::Heap(map) => map.insert(key, row),
147 DataStoreBackend::Journaled {
148 live, tombstones, ..
149 } => {
150 tombstones.remove(&key);
151 live.insert(key, row);
152 previous_journaled
153 }
154 };
155 self.entity_cardinality
156 .apply_insert(&cardinality_key, previous.as_ref());
157 self.bump_generation();
158 previous
159 }
160
161 pub(in crate::db) fn remove(&mut self, key: &RawDataStoreKey) -> Option<RawRow> {
163 let previous_journaled = if matches!(self.backend, DataStoreBackend::Journaled { .. }) {
164 self.get(key)
165 } else {
166 None
167 };
168 let previous = match &mut self.backend {
169 DataStoreBackend::Heap(map) => map.remove(key),
170 DataStoreBackend::Journaled {
171 live, tombstones, ..
172 } => {
173 live.remove(key);
174 tombstones.insert(key.clone());
175 previous_journaled
176 }
177 };
178 self.entity_cardinality.apply_remove(key, previous.as_ref());
179 self.bump_generation();
180 previous
181 }
182
183 pub(in crate::db) fn reset_journaled_live_projection(
186 &mut self,
187 ) -> Result<(), crate::error::InternalError> {
188 let DataStoreBackend::Journaled {
189 live, tombstones, ..
190 } = &mut self.backend
191 else {
192 return Err(crate::error::InternalError::store_invariant());
193 };
194
195 live.clear();
196 tombstones.clear();
197 self.rebuild_entity_cardinality_from_entries();
198 self.bump_generation();
199
200 Ok(())
201 }
202
203 pub(in crate::db) fn apply_recovered_journal_put(
205 &mut self,
206 key: RawDataStoreKey,
207 row: RawRow,
208 ) -> Result<Option<RawRow>, crate::error::InternalError> {
209 let DataStoreBackend::Journaled {
210 canonical,
211 live,
212 tombstones,
213 } = &mut self.backend
214 else {
215 return Err(crate::error::InternalError::store_invariant());
216 };
217
218 let previous = if tombstones.contains(&key) {
219 None
220 } else {
221 live.get(&key).cloned().or_else(|| canonical.get(&key))
222 };
223 tombstones.remove(&key);
224 let cardinality_key = key.clone();
225 live.insert(key, row);
226 self.entity_cardinality
227 .apply_insert(&cardinality_key, previous.as_ref());
228 self.bump_generation();
229
230 Ok(previous)
231 }
232
233 pub(in crate::db) fn apply_recovered_journal_delete(
235 &mut self,
236 key: &RawDataStoreKey,
237 ) -> Result<Option<RawRow>, crate::error::InternalError> {
238 let DataStoreBackend::Journaled {
239 canonical,
240 live,
241 tombstones,
242 } = &mut self.backend
243 else {
244 return Err(crate::error::InternalError::store_invariant());
245 };
246
247 let previous = if tombstones.contains(key) {
248 None
249 } else {
250 live.get(key).cloned().or_else(|| canonical.get(key))
251 };
252 live.remove(key);
253 tombstones.insert(key.clone());
254 self.entity_cardinality.apply_remove(key, previous.as_ref());
255 self.bump_generation();
256
257 Ok(previous)
258 }
259
260 pub(in crate::db) fn fold_recovered_journal_put(
262 &mut self,
263 key: RawDataStoreKey,
264 row: RawRow,
265 ) -> Result<Option<RawRow>, crate::error::InternalError> {
266 let DataStoreBackend::Journaled {
267 canonical,
268 live,
269 tombstones,
270 } = &mut self.backend
271 else {
272 return Err(crate::error::InternalError::store_invariant());
273 };
274
275 let visible = !live.contains_key(&key) && !tombstones.contains(&key);
276 let cardinality_key = key.clone();
277 let previous = canonical.insert(key, row);
278 if visible {
279 self.entity_cardinality
280 .apply_insert(&cardinality_key, previous.as_ref());
281 }
282 self.bump_generation();
283
284 Ok(previous)
285 }
286
287 pub(in crate::db) fn fold_recovered_journal_delete(
289 &mut self,
290 key: &RawDataStoreKey,
291 ) -> Result<Option<RawRow>, crate::error::InternalError> {
292 let DataStoreBackend::Journaled {
293 canonical,
294 live,
295 tombstones,
296 } = &mut self.backend
297 else {
298 return Err(crate::error::InternalError::store_invariant());
299 };
300
301 let visible = !live.contains_key(key) && !tombstones.contains(key);
302 let previous = canonical.remove(key);
303 if visible {
304 self.entity_cardinality.apply_remove(key, previous.as_ref());
305 }
306 self.bump_generation();
307
308 Ok(previous)
309 }
310
311 pub(in crate::db) fn get(&self, key: &RawDataStoreKey) -> Option<RawRow> {
313 #[cfg(feature = "diagnostics")]
314 record_data_store_get_call();
315
316 match &self.backend {
317 DataStoreBackend::Heap(map) => map.get(key).cloned(),
318 DataStoreBackend::Journaled { .. } => Self::journaled_get_raw(&self.backend, key),
319 }
320 }
321
322 #[must_use]
324 pub(in crate::db) fn contains(&self, key: &RawDataStoreKey) -> bool {
325 match &self.backend {
326 DataStoreBackend::Heap(map) => map.contains_key(key),
327 DataStoreBackend::Journaled { .. } => {
328 Self::journaled_get_raw(&self.backend, key).is_some()
329 }
330 }
331 }
332
333 #[must_use]
335 pub(in crate::db) fn len(&self) -> u64 {
336 match &self.backend {
337 DataStoreBackend::Heap(map) => u64::try_from(map.len()).unwrap_or(u64::MAX),
338 DataStoreBackend::Journaled { .. } => {
339 let mut count = 0_u64;
340 let _: Result<(), Infallible> = self.visit_entries(|_key, _row| {
341 count = count.saturating_add(1);
342 Ok(StoreVisit::Continue)
343 });
344 count
345 }
346 }
347 }
348
349 #[must_use]
351 pub(in crate::db) const fn generation(&self) -> u64 {
352 self.generation
353 }
354
355 #[must_use]
357 pub(in crate::db) fn exact_entity_count(&self, entity: EntityTag) -> Option<u64> {
358 self.entity_cardinality.exact_count(entity)
359 }
360
361 pub(in crate::db) fn visit_entries<E>(
363 &self,
364 mut visitor: impl FnMut(&RawDataStoreKey, &RawRow) -> Result<StoreVisit, E>,
365 ) -> Result<(), E> {
366 match &self.backend {
367 DataStoreBackend::Heap(map) => {
368 for (key, row) in map {
369 if visitor(key, row)?.should_stop() {
370 break;
371 }
372 }
373 }
374 DataStoreBackend::Journaled {
375 canonical: _,
376 live: _,
377 tombstones: _,
378 } => Self::visit_journaled_entries_in_bounds(
379 &self.backend,
380 (Bound::Unbounded, Bound::Unbounded),
381 false,
382 visitor,
383 )?,
384 }
385
386 Ok(())
387 }
388
389 pub(in crate::db) fn visit_range<E>(
391 &self,
392 key_range: impl RangeBounds<RawDataStoreKey>,
393 mut visitor: impl FnMut(&RawDataStoreKey, &RawRow) -> Result<StoreVisit, E>,
394 ) -> Result<(), E> {
395 let bounds = Self::owned_range_bounds(&key_range);
396 match &self.backend {
397 DataStoreBackend::Heap(map) => {
398 for (key, row) in map.range((bounds.0.clone(), bounds.1)) {
399 if visitor(key, row)?.should_stop() {
400 break;
401 }
402 }
403 }
404 DataStoreBackend::Journaled {
405 canonical: _,
406 live: _,
407 tombstones: _,
408 } => Self::visit_journaled_entries_in_bounds(&self.backend, bounds, false, visitor)?,
409 }
410
411 Ok(())
412 }
413
414 #[cfg(any(test, feature = "query"))]
416 pub(in crate::db) fn visit_range_rev<E>(
417 &self,
418 key_range: impl RangeBounds<RawDataStoreKey>,
419 mut visitor: impl FnMut(&RawDataStoreKey, &RawRow) -> Result<StoreVisit, E>,
420 ) -> Result<(), E> {
421 let bounds = Self::owned_range_bounds(&key_range);
422 match &self.backend {
423 DataStoreBackend::Heap(map) => {
424 for (key, row) in map.range((bounds.0.clone(), bounds.1)).rev() {
425 if visitor(key, row)?.should_stop() {
426 break;
427 }
428 }
429 }
430 DataStoreBackend::Journaled {
431 canonical: _,
432 live: _,
433 tombstones: _,
434 } => Self::visit_journaled_entries_in_bounds(&self.backend, bounds, true, visitor)?,
435 }
436
437 Ok(())
438 }
439
440 pub(in crate::db) fn memory_bytes(&self) -> u64 {
442 let mut bytes = 0u64;
444 let _: Result<(), Infallible> = self.visit_entries(|key, row| {
445 bytes = bytes.saturating_add(key.as_bytes().len() as u64 + row.len() as u64);
446 Ok(StoreVisit::Continue)
447 });
448 bytes
449 }
450
451 const fn bump_generation(&mut self) {
452 self.generation = self.generation.saturating_add(1);
453 }
454
455 fn rebuild_entity_cardinality_from_entries(&mut self) {
456 let mut cardinality = EntityCardinality::empty();
457 let _: Result<(), Infallible> = self.visit_entries(|key, _row| {
458 cardinality.apply_present_key(key);
459 Ok(StoreVisit::Continue)
460 });
461 self.entity_cardinality = cardinality;
462 }
463
464 #[cfg(all(feature = "diagnostics", any(test, feature = "query")))]
466 pub(in crate::db) fn current_get_call_count() -> u64 {
467 DATA_STORE_GET_CALL_COUNT.with(Cell::get)
468 }
469
470 fn journaled_get_raw(backend: &DataStoreBackend, key: &RawDataStoreKey) -> Option<RawRow> {
471 let DataStoreBackend::Journaled {
472 canonical,
473 live,
474 tombstones,
475 } = backend
476 else {
477 return None;
478 };
479
480 if tombstones.contains(key) {
481 return None;
482 }
483 live.get(key).cloned().or_else(|| canonical.get(key))
484 }
485
486 fn owned_range_bounds(
487 key_range: &impl RangeBounds<RawDataStoreKey>,
488 ) -> (Bound<RawDataStoreKey>, Bound<RawDataStoreKey>) {
489 let lower = match key_range.start_bound() {
490 Bound::Included(key) => Bound::Included(key.clone()),
491 Bound::Excluded(key) => Bound::Excluded(key.clone()),
492 Bound::Unbounded => Bound::Unbounded,
493 };
494 let upper = match key_range.end_bound() {
495 Bound::Included(key) => Bound::Included(key.clone()),
496 Bound::Excluded(key) => Bound::Excluded(key.clone()),
497 Bound::Unbounded => Bound::Unbounded,
498 };
499
500 (lower, upper)
501 }
502
503 fn visit_journaled_entries_in_bounds<E>(
504 backend: &DataStoreBackend,
505 bounds: (Bound<RawDataStoreKey>, Bound<RawDataStoreKey>),
506 reverse: bool,
507 mut visitor: impl FnMut(&RawDataStoreKey, &RawRow) -> Result<StoreVisit, E>,
508 ) -> Result<(), E> {
509 let DataStoreBackend::Journaled {
510 canonical,
511 live,
512 tombstones,
513 } = backend
514 else {
515 return Ok(());
516 };
517
518 if canonical.is_empty() {
519 if reverse {
520 for (key, row) in live.range(bounds).rev() {
521 if visitor(key, row)?.should_stop() {
522 return Ok(());
523 }
524 }
525 } else {
526 for (key, row) in live.range(bounds) {
527 if visitor(key, row)?.should_stop() {
528 return Ok(());
529 }
530 }
531 }
532 return Ok(());
533 }
534
535 if live.is_empty() && tombstones.is_empty() {
536 if reverse {
537 for entry in canonical.range(bounds).rev() {
538 if visitor(entry.key(), &entry.value())?.should_stop() {
539 return Ok(());
540 }
541 }
542 } else {
543 for entry in canonical.range(bounds) {
544 if visitor(entry.key(), &entry.value())?.should_stop() {
545 return Ok(());
546 }
547 }
548 }
549 return Ok(());
550 }
551
552 match if reverse {
553 Direction::Desc
554 } else {
555 Direction::Asc
556 } {
557 Direction::Asc => visit_ordered_overlay(
558 canonical.range((bounds.0.clone(), bounds.1.clone())),
559 live.range((bounds.0, bounds.1)),
560 Direction::Asc,
561 |canonical_entry, live_entry| canonical_entry.key().cmp(live_entry.0),
562 |canonical_entry| !tombstones.contains(canonical_entry.key()),
563 |live_entry| !tombstones.contains(live_entry.0),
564 |entry| {
565 let visit = match entry {
566 OrderedOverlayEntry::Canonical(canonical_entry) => {
567 visitor(canonical_entry.key(), &canonical_entry.value())?
568 }
569 OrderedOverlayEntry::Live((key, row)) => visitor(key, row)?,
570 };
571 Ok(if visit.should_stop() {
572 OrderedOverlayVisit::Stop
573 } else {
574 OrderedOverlayVisit::Continue
575 })
576 },
577 ),
578 Direction::Desc => visit_ordered_overlay(
579 canonical.range((bounds.0.clone(), bounds.1.clone())).rev(),
580 live.range((bounds.0, bounds.1)).rev(),
581 Direction::Desc,
582 |canonical_entry, live_entry| canonical_entry.key().cmp(live_entry.0),
583 |canonical_entry| !tombstones.contains(canonical_entry.key()),
584 |live_entry| !tombstones.contains(live_entry.0),
585 |entry| {
586 let visit = match entry {
587 OrderedOverlayEntry::Canonical(canonical_entry) => {
588 visitor(canonical_entry.key(), &canonical_entry.value())?
589 }
590 OrderedOverlayEntry::Live((key, row)) => visitor(key, row)?,
591 };
592 Ok(if visit.should_stop() {
593 OrderedOverlayVisit::Stop
594 } else {
595 OrderedOverlayVisit::Continue
596 })
597 },
598 ),
599 }
600 }
601}
602
603#[derive(Clone, Debug)]
604struct EntityCardinality {
605 counts: HeapBTreeMap<EntityTag, u64>,
606 decodable: bool,
607}
608
609impl EntityCardinality {
610 const fn empty() -> Self {
611 Self {
612 counts: HeapBTreeMap::new(),
613 decodable: true,
614 }
615 }
616
617 fn exact_count(&self, entity: EntityTag) -> Option<u64> {
618 self.decodable
619 .then(|| self.counts.get(&entity).copied().unwrap_or(0))
620 }
621
622 fn apply_insert(&mut self, key: &RawDataStoreKey, previous: Option<&RawRow>) {
623 if previous.is_some() {
624 return;
625 }
626 self.apply_present_key(key);
627 }
628
629 fn apply_remove(&mut self, key: &RawDataStoreKey, previous: Option<&RawRow>) {
630 if previous.is_none() {
631 return;
632 }
633 self.apply_removed_key(key);
634 }
635
636 fn apply_present_key(&mut self, key: &RawDataStoreKey) {
637 if !self.decodable {
638 return;
639 }
640 let Some(entity) = key.entity_tag_prefix() else {
641 self.invalidate();
642 return;
643 };
644
645 let count = self.counts.entry(entity).or_insert(0);
646 *count = count.saturating_add(1);
647 }
648
649 fn apply_removed_key(&mut self, key: &RawDataStoreKey) {
650 if !self.decodable {
651 return;
652 }
653 let Some(entity) = key.entity_tag_prefix() else {
654 self.invalidate();
655 return;
656 };
657
658 if let Some(count) = self.counts.get_mut(&entity) {
659 *count = count.saturating_sub(1);
660 if *count == 0 {
661 self.counts.remove(&entity);
662 }
663 }
664 }
665
666 fn invalidate(&mut self) {
667 self.counts.clear();
668 self.decodable = false;
669 }
670}
671
672#[cfg(test)]
673mod tests;