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
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(all(feature = "sql", 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 pub(in crate::db) fn visit_range_rev<E>(
416 &self,
417 key_range: impl RangeBounds<RawDataStoreKey>,
418 mut visitor: impl FnMut(&RawDataStoreKey, &RawRow) -> Result<StoreVisit, E>,
419 ) -> Result<(), E> {
420 let bounds = Self::owned_range_bounds(&key_range);
421 match &self.backend {
422 DataStoreBackend::Heap(map) => {
423 for (key, row) in map.range((bounds.0.clone(), bounds.1)).rev() {
424 if visitor(key, row)?.should_stop() {
425 break;
426 }
427 }
428 }
429 DataStoreBackend::Journaled {
430 canonical: _,
431 live: _,
432 tombstones: _,
433 } => Self::visit_journaled_entries_in_bounds(&self.backend, bounds, true, visitor)?,
434 }
435
436 Ok(())
437 }
438
439 pub(in crate::db) fn memory_bytes(&self) -> u64 {
441 let mut bytes = 0u64;
443 let _: Result<(), Infallible> = self.visit_entries(|key, row| {
444 bytes = bytes.saturating_add(key.as_bytes().len() as u64 + row.len() as u64);
445 Ok(StoreVisit::Continue)
446 });
447 bytes
448 }
449
450 const fn bump_generation(&mut self) {
451 self.generation = self.generation.saturating_add(1);
452 }
453
454 fn rebuild_entity_cardinality_from_entries(&mut self) {
455 let mut cardinality = EntityCardinality::empty();
456 let _: Result<(), Infallible> = self.visit_entries(|key, _row| {
457 cardinality.apply_present_key(key);
458 Ok(StoreVisit::Continue)
459 });
460 self.entity_cardinality = cardinality;
461 }
462
463 #[cfg(all(feature = "sql", feature = "diagnostics"))]
465 pub(in crate::db) fn current_get_call_count() -> u64 {
466 DATA_STORE_GET_CALL_COUNT.with(Cell::get)
467 }
468
469 fn journaled_get_raw(backend: &DataStoreBackend, key: &RawDataStoreKey) -> Option<RawRow> {
470 let DataStoreBackend::Journaled {
471 canonical,
472 live,
473 tombstones,
474 } = backend
475 else {
476 return None;
477 };
478
479 if tombstones.contains(key) {
480 return None;
481 }
482 live.get(key).cloned().or_else(|| canonical.get(key))
483 }
484
485 fn owned_range_bounds(
486 key_range: &impl RangeBounds<RawDataStoreKey>,
487 ) -> (Bound<RawDataStoreKey>, Bound<RawDataStoreKey>) {
488 let lower = match key_range.start_bound() {
489 Bound::Included(key) => Bound::Included(key.clone()),
490 Bound::Excluded(key) => Bound::Excluded(key.clone()),
491 Bound::Unbounded => Bound::Unbounded,
492 };
493 let upper = match key_range.end_bound() {
494 Bound::Included(key) => Bound::Included(key.clone()),
495 Bound::Excluded(key) => Bound::Excluded(key.clone()),
496 Bound::Unbounded => Bound::Unbounded,
497 };
498
499 (lower, upper)
500 }
501
502 fn visit_journaled_entries_in_bounds<E>(
503 backend: &DataStoreBackend,
504 bounds: (Bound<RawDataStoreKey>, Bound<RawDataStoreKey>),
505 reverse: bool,
506 mut visitor: impl FnMut(&RawDataStoreKey, &RawRow) -> Result<StoreVisit, E>,
507 ) -> Result<(), E> {
508 let DataStoreBackend::Journaled {
509 canonical,
510 live,
511 tombstones,
512 } = backend
513 else {
514 return Ok(());
515 };
516
517 if canonical.is_empty() {
518 if reverse {
519 for (key, row) in live.range(bounds).rev() {
520 if visitor(key, row)?.should_stop() {
521 return Ok(());
522 }
523 }
524 } else {
525 for (key, row) in live.range(bounds) {
526 if visitor(key, row)?.should_stop() {
527 return Ok(());
528 }
529 }
530 }
531 return Ok(());
532 }
533
534 if live.is_empty() && tombstones.is_empty() {
535 if reverse {
536 for entry in canonical.range(bounds).rev() {
537 if visitor(entry.key(), &entry.value())?.should_stop() {
538 return Ok(());
539 }
540 }
541 } else {
542 for entry in canonical.range(bounds) {
543 if visitor(entry.key(), &entry.value())?.should_stop() {
544 return Ok(());
545 }
546 }
547 }
548 return Ok(());
549 }
550
551 match if reverse {
552 Direction::Desc
553 } else {
554 Direction::Asc
555 } {
556 Direction::Asc => visit_ordered_overlay(
557 canonical.range((bounds.0.clone(), bounds.1.clone())),
558 live.range((bounds.0, bounds.1)),
559 Direction::Asc,
560 |canonical_entry, live_entry| canonical_entry.key().cmp(live_entry.0),
561 |canonical_entry| !tombstones.contains(canonical_entry.key()),
562 |live_entry| !tombstones.contains(live_entry.0),
563 |entry| {
564 let visit = match entry {
565 OrderedOverlayEntry::Canonical(canonical_entry) => {
566 visitor(canonical_entry.key(), &canonical_entry.value())?
567 }
568 OrderedOverlayEntry::Live((key, row)) => visitor(key, row)?,
569 };
570 Ok(if visit.should_stop() {
571 OrderedOverlayVisit::Stop
572 } else {
573 OrderedOverlayVisit::Continue
574 })
575 },
576 ),
577 Direction::Desc => visit_ordered_overlay(
578 canonical.range((bounds.0.clone(), bounds.1.clone())).rev(),
579 live.range((bounds.0, bounds.1)).rev(),
580 Direction::Desc,
581 |canonical_entry, live_entry| canonical_entry.key().cmp(live_entry.0),
582 |canonical_entry| !tombstones.contains(canonical_entry.key()),
583 |live_entry| !tombstones.contains(live_entry.0),
584 |entry| {
585 let visit = match entry {
586 OrderedOverlayEntry::Canonical(canonical_entry) => {
587 visitor(canonical_entry.key(), &canonical_entry.value())?
588 }
589 OrderedOverlayEntry::Live((key, row)) => visitor(key, row)?,
590 };
591 Ok(if visit.should_stop() {
592 OrderedOverlayVisit::Stop
593 } else {
594 OrderedOverlayVisit::Continue
595 })
596 },
597 ),
598 }
599 }
600}
601
602#[derive(Clone, Debug)]
603struct EntityCardinality {
604 counts: HeapBTreeMap<EntityTag, u64>,
605 decodable: bool,
606}
607
608impl EntityCardinality {
609 const fn empty() -> Self {
610 Self {
611 counts: HeapBTreeMap::new(),
612 decodable: true,
613 }
614 }
615
616 fn exact_count(&self, entity: EntityTag) -> Option<u64> {
617 self.decodable
618 .then(|| self.counts.get(&entity).copied().unwrap_or(0))
619 }
620
621 fn apply_insert(&mut self, key: &RawDataStoreKey, previous: Option<&RawRow>) {
622 if previous.is_some() {
623 return;
624 }
625 self.apply_present_key(key);
626 }
627
628 fn apply_remove(&mut self, key: &RawDataStoreKey, previous: Option<&RawRow>) {
629 if previous.is_none() {
630 return;
631 }
632 self.apply_removed_key(key);
633 }
634
635 fn apply_present_key(&mut self, key: &RawDataStoreKey) {
636 if !self.decodable {
637 return;
638 }
639 let Some(entity) = key.entity_tag_prefix() else {
640 self.invalidate();
641 return;
642 };
643
644 let count = self.counts.entry(entity).or_insert(0);
645 *count = count.saturating_add(1);
646 }
647
648 fn apply_removed_key(&mut self, key: &RawDataStoreKey) {
649 if !self.decodable {
650 return;
651 }
652 let Some(entity) = key.entity_tag_prefix() else {
653 self.invalidate();
654 return;
655 };
656
657 if let Some(count) = self.counts.get_mut(&entity) {
658 *count = count.saturating_sub(1);
659 if *count == 0 {
660 self.counts.remove(&entity);
661 }
662 }
663 }
664
665 fn invalidate(&mut self) {
666 self.counts.clear();
667 self.decodable = false;
668 }
669}
670
671#[cfg(test)]
672mod tests;