icydb_core/db/index/
store.rs1use crate::{
7 db::index::{
8 entry::{MAX_INDEX_ENTRY_BYTES, RawIndexEntry},
9 key::RawIndexKey,
10 },
11 traits::Storable,
12};
13
14use canic_cdk::structures::{
15 BTreeMap, DefaultMemoryImpl, memory::VirtualMemory, storable::Bound as StorableBound,
16};
17use canic_utils::hash::Xxh3;
18use std::borrow::Cow;
19
20pub struct IndexStore {
30 pub(super) map: BTreeMap<RawIndexKey, StoredIndexValue, VirtualMemory<DefaultMemoryImpl>>,
31 generation: u64,
32}
33
34impl IndexStore {
35 #[must_use]
36 pub fn init(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
37 Self {
38 map: BTreeMap::init(memory),
39 generation: 0,
40 }
41 }
42
43 pub(crate) fn entries(&self) -> Vec<(RawIndexKey, RawIndexEntry)> {
45 self.map
46 .iter()
47 .map(|entry| (entry.key().clone(), entry.value().entry))
48 .collect()
49 }
50
51 pub(in crate::db) fn get(&self, key: &RawIndexKey) -> Option<RawIndexEntry> {
52 let value = self.map.get(key);
53
54 #[cfg(debug_assertions)]
55 if let Some(ref stored) = value {
56 Self::verify_if_debug(key, stored);
57 }
58
59 value.map(|stored| stored.entry)
60 }
61
62 pub fn len(&self) -> u64 {
63 self.map.len()
64 }
65
66 pub fn is_empty(&self) -> bool {
67 self.map.is_empty()
68 }
69
70 #[must_use]
71 pub(in crate::db) const fn generation(&self) -> u64 {
72 self.generation
73 }
74
75 pub(crate) fn insert(
76 &mut self,
77 key: RawIndexKey,
78 entry: RawIndexEntry,
79 ) -> Option<RawIndexEntry> {
80 let fingerprint = Self::entry_fingerprint(&key, &entry);
81
82 let stored = StoredIndexValue { entry, fingerprint };
83 let previous = self.map.insert(key, stored).map(|prev| prev.entry);
84 self.bump_generation();
85 previous
86 }
87
88 pub(crate) fn remove(&mut self, key: &RawIndexKey) -> Option<RawIndexEntry> {
89 let previous = self.map.remove(key).map(|prev| prev.entry);
90 self.bump_generation();
91 previous
92 }
93
94 pub fn clear(&mut self) {
95 self.map.clear();
96 self.bump_generation();
97 }
98
99 pub fn memory_bytes(&self) -> u64 {
101 self.map
102 .iter()
103 .map(|entry| {
104 entry.key().as_bytes().len() as u64
105 + entry.value().entry.len() as u64
106 + u64::from(RawIndexFingerprint::STORED_SIZE)
107 })
108 .sum()
109 }
110
111 const fn bump_generation(&mut self) {
112 self.generation = self.generation.saturating_add(1);
113 }
114
115 fn entry_fingerprint(key: &RawIndexKey, entry: &RawIndexEntry) -> RawIndexFingerprint {
116 const VERSION: u8 = 1;
117
118 let mut hasher = Xxh3::with_seed(0);
119 hasher.update(&[VERSION]);
120 hasher.update(key.as_bytes());
121 hasher.update(entry.as_bytes());
122
123 RawIndexFingerprint(hasher.digest128().to_be_bytes())
124 }
125
126 #[cfg(debug_assertions)]
127 pub(super) fn verify_if_debug(key: &RawIndexKey, stored: &StoredIndexValue) {
128 let expected = Self::entry_fingerprint(key, &stored.entry);
129
130 debug_assert!(
131 stored.fingerprint == expected,
132 "debug invariant violation: index fingerprint mismatch"
133 );
134 }
135}
136
137#[derive(Clone, Debug)]
145pub(super) struct StoredIndexValue {
146 pub(super) entry: RawIndexEntry,
147 fingerprint: RawIndexFingerprint,
148}
149
150impl StoredIndexValue {
151 const STORED_SIZE: u32 = MAX_INDEX_ENTRY_BYTES + RawIndexFingerprint::STORED_SIZE;
152}
153
154impl Storable for StoredIndexValue {
155 fn to_bytes(&self) -> Cow<'_, [u8]> {
156 Cow::Owned(self.clone().into_bytes())
157 }
158
159 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
160 let bytes = bytes.as_ref();
161
162 let (entry_bytes, fingerprint_bytes) =
163 if bytes.len() < RawIndexFingerprint::STORED_SIZE as usize {
164 (bytes, &[][..])
165 } else {
166 bytes.split_at(bytes.len() - RawIndexFingerprint::STORED_SIZE as usize)
167 };
168
169 let mut out = [0u8; 16];
170 if fingerprint_bytes.len() == out.len() {
171 out.copy_from_slice(fingerprint_bytes);
172 }
173
174 Self {
175 entry: RawIndexEntry::from_bytes(Cow::Borrowed(entry_bytes)),
176 fingerprint: RawIndexFingerprint(out),
177 }
178 }
179
180 fn into_bytes(self) -> Vec<u8> {
181 let mut bytes = self.entry.into_bytes();
182 bytes.extend_from_slice(&self.fingerprint.0);
183 bytes
184 }
185
186 const BOUND: StorableBound = StorableBound::Bounded {
187 max_size: Self::STORED_SIZE,
188 is_fixed_size: false,
189 };
190}
191
192#[derive(Clone, Copy, Debug, Eq, PartialEq)]
197pub(crate) struct RawIndexFingerprint([u8; 16]);
198
199impl RawIndexFingerprint {
200 pub(crate) const STORED_SIZE: u32 = 16;
201}
202
203impl Storable for RawIndexFingerprint {
204 fn to_bytes(&self) -> Cow<'_, [u8]> {
205 Cow::Borrowed(&self.0)
206 }
207
208 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
209 let mut out = [0u8; 16];
210 if bytes.len() == out.len() {
211 out.copy_from_slice(bytes.as_ref());
212 }
213 Self(out)
214 }
215
216 fn into_bytes(self) -> Vec<u8> {
217 self.0.to_vec()
218 }
219
220 const BOUND: StorableBound = StorableBound::Bounded {
221 max_size: Self::STORED_SIZE,
222 is_fixed_size: true,
223 };
224}