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::{
7    db::data::{
8        CanonicalRow, DataKey, RawDataKey, RawRow, SelectiveRowRead, StorageKey,
9        StructuralRowContract, decode_sparse_indexed_raw_row_with_contract,
10        decode_sparse_required_slot_with_contract,
11    },
12    error::InternalError,
13    value::Value,
14};
15use canic_cdk::structures::{BTreeMap, DefaultMemoryImpl, memory::VirtualMemory};
16#[cfg(feature = "diagnostics")]
17use std::cell::Cell;
18
19#[cfg(feature = "diagnostics")]
20thread_local! {
21    static DATA_STORE_GET_CALL_COUNT: Cell<u64> = const { Cell::new(0) };
22}
23
24#[cfg(feature = "diagnostics")]
25fn record_data_store_get_call() {
26    DATA_STORE_GET_CALL_COUNT.with(|count| {
27        count.set(count.get().saturating_add(1));
28    });
29}
30
31///
32/// DataStore
33///
34/// Thin persistence wrapper over one stable BTreeMap.
35///
36/// Invariant: callers provide already-validated `RawDataKey` and canonical row bytes.
37/// This type intentionally does not enforce commit-phase ordering.
38///
39
40pub struct DataStore {
41    map: BTreeMap<RawDataKey, RawRow, VirtualMemory<DefaultMemoryImpl>>,
42}
43
44impl DataStore {
45    /// Initialize a data store with the provided backing memory.
46    #[must_use]
47    pub fn init(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
48        Self {
49            map: BTreeMap::init(memory),
50        }
51    }
52
53    /// Insert or replace one row by raw key.
54    pub(in crate::db) fn insert(&mut self, key: RawDataKey, row: CanonicalRow) -> Option<RawRow> {
55        self.map.insert(key, row.into_raw_row())
56    }
57
58    /// Insert one raw row directly for corruption-focused test setup only.
59    #[cfg(test)]
60    pub(crate) fn insert_raw_for_test(&mut self, key: RawDataKey, row: RawRow) -> Option<RawRow> {
61        self.map.insert(key, row)
62    }
63
64    /// Remove one row by raw key.
65    pub fn remove(&mut self, key: &RawDataKey) -> Option<RawRow> {
66        self.map.remove(key)
67    }
68
69    /// Load one row by raw key.
70    pub fn get(&self, key: &RawDataKey) -> Option<RawRow> {
71        #[cfg(feature = "diagnostics")]
72        record_data_store_get_call();
73
74        self.map.get(key)
75    }
76
77    /// Selectively decode one caller-declared slot list from one persisted row key.
78    pub(in crate::db) fn read_slot_values(
79        &self,
80        key: &RawDataKey,
81        contract: StructuralRowContract,
82        expected_key: StorageKey,
83        required_slots: &[usize],
84    ) -> Result<SelectiveRowRead<Vec<Option<Value>>>, InternalError> {
85        // Phase 1: preserve the storage-boundary distinction between one
86        // missing row and one present row that decodes the requested sparse
87        // slot set.
88        let Some(raw_row) = self.get(key) else {
89            return Ok(SelectiveRowRead::MissingRow);
90        };
91
92        // Phase 2: keep selective row reads on one slot-list contract, while
93        // still letting the storage layer choose the narrower one-field decode
94        // path internally when the caller only needs one slot.
95        let values = if let [required_slot] = required_slots {
96            vec![decode_sparse_required_slot_with_contract(
97                &raw_row,
98                contract,
99                expected_key,
100                *required_slot,
101            )?]
102        } else {
103            decode_sparse_indexed_raw_row_with_contract(
104                &raw_row,
105                contract,
106                expected_key,
107                required_slots,
108            )?
109        };
110
111        Ok(SelectiveRowRead::Present(values))
112    }
113
114    /// Return whether one raw key exists without cloning the row payload.
115    #[must_use]
116    pub fn contains(&self, key: &RawDataKey) -> bool {
117        self.map.contains_key(key)
118    }
119
120    /// Clear all stored rows from the data store.
121    pub fn clear(&mut self) {
122        self.map.clear();
123    }
124
125    /// Sum of bytes used by all stored rows.
126    pub fn memory_bytes(&self) -> u64 {
127        // Report map footprint as key bytes + row bytes per entry.
128        self.iter()
129            .map(|entry| DataKey::STORED_SIZE_BYTES + entry.value().len() as u64)
130            .sum()
131    }
132
133    /// Return the monotonic perf-only count of stable row fetches seen by this process.
134    #[cfg(feature = "diagnostics")]
135    pub(in crate::db) fn current_get_call_count() -> u64 {
136        DATA_STORE_GET_CALL_COUNT.with(Cell::get)
137    }
138}
139
140impl std::ops::Deref for DataStore {
141    type Target = BTreeMap<RawDataKey, RawRow, VirtualMemory<DefaultMemoryImpl>>;
142
143    fn deref(&self) -> &Self::Target {
144        &self.map
145    }
146}