Skip to main content

icydb_core/db/data/
store.rs

1//! Module: data::store
2//! Responsibility: stable BTreeMap-backed row persistence.
3//! Does not own: key/row validation policy beyond type boundaries.
4//! Boundary: commit/executor call into this layer after prevalidation.
5
6use crate::db::data::{CanonicalRow, DataKey, RawDataKey, RawRow};
7use canic_cdk::structures::{BTreeMap, DefaultMemoryImpl, btreemap::Iter, memory::VirtualMemory};
8#[cfg(feature = "diagnostics")]
9use std::cell::Cell;
10use std::ops::RangeBounds;
11
12#[cfg(feature = "diagnostics")]
13thread_local! {
14    static DATA_STORE_GET_CALL_COUNT: Cell<u64> = const { Cell::new(0) };
15}
16
17#[cfg(feature = "diagnostics")]
18fn record_data_store_get_call() {
19    DATA_STORE_GET_CALL_COUNT.with(|count| {
20        count.set(count.get().saturating_add(1));
21    });
22}
23
24///
25/// DataStore
26///
27/// Thin persistence wrapper over one stable BTreeMap.
28///
29/// Invariant: callers provide already-validated `RawDataKey` and canonical row bytes.
30/// This type intentionally does not enforce commit-phase ordering.
31///
32
33pub struct DataStore {
34    map: BTreeMap<RawDataKey, RawRow, VirtualMemory<DefaultMemoryImpl>>,
35}
36
37impl DataStore {
38    /// Initialize a data store with the provided backing memory.
39    #[must_use]
40    pub fn init(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
41        Self {
42            map: BTreeMap::init(memory),
43        }
44    }
45
46    /// Insert or replace one row by raw key.
47    pub(in crate::db) fn insert(&mut self, key: RawDataKey, row: CanonicalRow) -> Option<RawRow> {
48        self.map.insert(key, row.into_raw_row())
49    }
50
51    /// Insert one raw row directly for corruption-focused test setup only.
52    #[cfg(test)]
53    pub(crate) fn insert_raw_for_test(&mut self, key: RawDataKey, row: RawRow) -> Option<RawRow> {
54        self.map.insert(key, row)
55    }
56
57    /// Remove one row by raw key.
58    pub fn remove(&mut self, key: &RawDataKey) -> Option<RawRow> {
59        self.map.remove(key)
60    }
61
62    /// Load one row by raw key.
63    pub fn get(&self, key: &RawDataKey) -> Option<RawRow> {
64        #[cfg(feature = "diagnostics")]
65        record_data_store_get_call();
66
67        self.map.get(key)
68    }
69
70    /// Return whether one raw key exists without cloning the row payload.
71    #[must_use]
72    pub fn contains(&self, key: &RawDataKey) -> bool {
73        self.map.contains_key(key)
74    }
75
76    /// Clear all stored rows from the data store.
77    pub fn clear(&mut self) {
78        self.map.clear();
79    }
80
81    /// Return the number of stored rows without exposing the backing map.
82    #[must_use]
83    pub fn len(&self) -> u64 {
84        self.map.len()
85    }
86
87    /// Return whether the data store currently contains no rows.
88    #[must_use]
89    pub fn is_empty(&self) -> bool {
90        self.map.is_empty()
91    }
92
93    /// Return raw row entries in canonical storage order.
94    pub(in crate::db) fn entries(
95        &self,
96    ) -> Iter<'_, RawDataKey, RawRow, VirtualMemory<DefaultMemoryImpl>> {
97        self.map.iter()
98    }
99
100    /// Iterate over raw row entries whose keys belong to the provided storage range.
101    pub(in crate::db) fn range(
102        &self,
103        key_range: impl RangeBounds<RawDataKey>,
104    ) -> Iter<'_, RawDataKey, RawRow, VirtualMemory<DefaultMemoryImpl>> {
105        self.map.range(key_range)
106    }
107
108    /// Sum of bytes used by all stored rows.
109    pub fn memory_bytes(&self) -> u64 {
110        // Report map footprint as key bytes + row bytes per entry.
111        self.entries()
112            .map(|entry| DataKey::STORED_SIZE_BYTES + entry.value().len() as u64)
113            .sum()
114    }
115
116    /// Return the monotonic perf-only count of stable row fetches seen by this process.
117    #[cfg(feature = "diagnostics")]
118    pub(in crate::db) fn current_get_call_count() -> u64 {
119        DATA_STORE_GET_CALL_COUNT.with(Cell::get)
120    }
121}