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(in crate::db) fn insert_raw_for_test(
54        &mut self,
55        key: RawDataKey,
56        row: RawRow,
57    ) -> Option<RawRow> {
58        self.map.insert(key, row)
59    }
60
61    /// Remove one row by raw key.
62    pub(in crate::db) fn remove(&mut self, key: &RawDataKey) -> Option<RawRow> {
63        self.map.remove(key)
64    }
65
66    /// Load one row by raw key.
67    pub(in crate::db) fn get(&self, key: &RawDataKey) -> Option<RawRow> {
68        #[cfg(feature = "diagnostics")]
69        record_data_store_get_call();
70
71        self.map.get(key)
72    }
73
74    /// Return whether one raw key exists without cloning the row payload.
75    #[must_use]
76    pub(in crate::db) fn contains(&self, key: &RawDataKey) -> bool {
77        self.map.contains_key(key)
78    }
79
80    /// Clear all stored rows from the data store.
81    #[cfg(test)]
82    pub(in crate::db) fn clear(&mut self) {
83        self.map.clear();
84    }
85
86    /// Return the number of stored rows without exposing the backing map.
87    #[must_use]
88    pub(in crate::db) fn len(&self) -> u64 {
89        self.map.len()
90    }
91
92    /// Return whether the data store currently contains no rows.
93    #[must_use]
94    #[cfg(test)]
95    pub(in crate::db) fn is_empty(&self) -> bool {
96        self.map.is_empty()
97    }
98
99    /// Return raw row entries in canonical storage order.
100    pub(in crate::db) fn entries(
101        &self,
102    ) -> Iter<'_, RawDataKey, RawRow, VirtualMemory<DefaultMemoryImpl>> {
103        self.map.iter()
104    }
105
106    /// Iterate over raw row entries whose keys belong to the provided storage range.
107    pub(in crate::db) fn range(
108        &self,
109        key_range: impl RangeBounds<RawDataKey>,
110    ) -> Iter<'_, RawDataKey, RawRow, VirtualMemory<DefaultMemoryImpl>> {
111        self.map.range(key_range)
112    }
113
114    /// Sum of bytes used by all stored rows.
115    pub(in crate::db) fn memory_bytes(&self) -> u64 {
116        // Report map footprint as key bytes + row bytes per entry.
117        self.entries()
118            .map(|entry| DataKey::STORED_SIZE_BYTES + entry.value().len() as u64)
119            .sum()
120    }
121
122    /// Return the monotonic perf-only count of stable row fetches seen by this process.
123    #[cfg(feature = "diagnostics")]
124    pub(in crate::db) fn current_get_call_count() -> u64 {
125        DATA_STORE_GET_CALL_COUNT.with(Cell::get)
126    }
127}