icydb_core/db/index/store/
mod.rs1mod fingerprint_debug;
2mod lookup;
3
4use crate::{
5 db::index::{
6 entry::{MAX_INDEX_ENTRY_BYTES, RawIndexEntry},
7 key::RawIndexKey,
8 },
9 traits::Storable,
10};
11use canic_cdk::structures::{BTreeMap, DefaultMemoryImpl, memory::VirtualMemory, storable::Bound};
12use canic_utils::hash::Xxh3;
13use std::borrow::Cow;
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
44pub(crate) struct RawIndexFingerprint([u8; 16]);
45
46impl RawIndexFingerprint {
47 pub(crate) const STORED_SIZE: u32 = 16;
48}
49
50impl Storable for RawIndexFingerprint {
51 fn to_bytes(&self) -> Cow<'_, [u8]> {
52 Cow::Borrowed(&self.0)
53 }
54
55 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
56 let mut out = [0u8; 16];
57 if bytes.len() == out.len() {
58 out.copy_from_slice(bytes.as_ref());
59 }
60 Self(out)
61 }
62
63 fn into_bytes(self) -> Vec<u8> {
64 self.0.to_vec()
65 }
66
67 const BOUND: Bound = Bound::Bounded {
68 max_size: Self::STORED_SIZE,
69 is_fixed_size: true,
70 };
71}
72
73#[derive(Clone, Debug)]
80struct InlineIndexValue {
81 entry: RawIndexEntry,
82 fingerprint: RawIndexFingerprint,
83}
84
85impl InlineIndexValue {
86 const STORED_SIZE: u32 = MAX_INDEX_ENTRY_BYTES + RawIndexFingerprint::STORED_SIZE;
87}
88
89impl Storable for InlineIndexValue {
90 fn to_bytes(&self) -> Cow<'_, [u8]> {
91 Cow::Owned(self.clone().into_bytes())
92 }
93
94 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
95 let bytes = bytes.as_ref();
96 let (entry_bytes, fingerprint_bytes) =
97 if bytes.len() < RawIndexFingerprint::STORED_SIZE as usize {
98 (bytes, &[][..])
99 } else {
100 bytes.split_at(bytes.len() - RawIndexFingerprint::STORED_SIZE as usize)
101 };
102
103 let mut out = [0u8; 16];
104 if fingerprint_bytes.len() == out.len() {
105 out.copy_from_slice(fingerprint_bytes);
106 }
107
108 Self {
109 entry: RawIndexEntry::from_bytes(Cow::Borrowed(entry_bytes)),
110 fingerprint: RawIndexFingerprint(out),
111 }
112 }
113
114 fn into_bytes(self) -> Vec<u8> {
115 let mut bytes = self.entry.into_bytes();
116 bytes.extend_from_slice(&self.fingerprint.0);
117 bytes
118 }
119
120 const BOUND: Bound = Bound::Bounded {
121 max_size: Self::STORED_SIZE,
122 is_fixed_size: false,
123 };
124}
125
126pub struct IndexStore {
131 entry: VirtualMemory<DefaultMemoryImpl>,
132}
133
134impl IndexStore {
135 #[must_use]
136 pub const fn init(entry: VirtualMemory<DefaultMemoryImpl>) -> Self {
137 Self { entry }
138 }
139
140 pub(crate) fn entries(&self) -> Vec<(RawIndexKey, RawIndexEntry)> {
142 self.entry_map()
143 .iter()
144 .map(|entry| (entry.key().clone(), entry.value().entry))
145 .collect()
146 }
147
148 pub fn len(&self) -> u64 {
149 self.entry_map().len()
150 }
151
152 pub fn is_empty(&self) -> bool {
153 self.entry_map().is_empty()
154 }
155
156 pub(crate) fn get(&self, key: &RawIndexKey) -> Option<RawIndexEntry> {
157 let value = self.entry_map().get(key);
158
159 #[cfg(debug_assertions)]
162 if let Some(ref inline) = value
163 && let Err(err) = Self::verify_entry_fingerprint(None, key, inline)
164 {
165 panic!(
166 "invariant violation (debug-only): index fingerprint verification failed: {err}"
167 );
168 }
169
170 value.map(|inline| inline.entry)
171 }
172
173 pub(crate) fn insert(&self, key: RawIndexKey, value: RawIndexEntry) -> Option<RawIndexEntry> {
174 let fingerprint = Self::entry_fingerprint(&key, &value);
175 let inline = InlineIndexValue {
176 entry: value,
177 fingerprint,
178 };
179 self.entry_map().insert(key, inline).map(|prev| prev.entry)
180 }
181
182 pub(crate) fn remove(&self, key: &RawIndexKey) -> Option<RawIndexEntry> {
183 self.entry_map().remove(key).map(|prev| prev.entry)
184 }
185
186 pub fn clear(&mut self) {
187 self.entry_map().clear();
188 }
189
190 pub fn memory_bytes(&self) -> u64 {
191 self.entry_map()
192 .iter()
193 .map(|entry| {
194 let value: InlineIndexValue = entry.value();
195 entry.key().as_bytes().len() as u64
196 + value.entry.len() as u64
197 + u64::from(RawIndexFingerprint::STORED_SIZE)
198 })
199 .sum::<u64>()
200 }
201
202 fn entry_map(
203 &self,
204 ) -> BTreeMap<RawIndexKey, InlineIndexValue, VirtualMemory<DefaultMemoryImpl>> {
205 BTreeMap::init(self.entry.clone())
206 }
207
208 fn entry_fingerprint(key: &RawIndexKey, entry: &RawIndexEntry) -> RawIndexFingerprint {
209 const VERSION: u8 = 1;
210
211 let mut hasher = Xxh3::with_seed(0);
212 hasher.update(&[VERSION]);
213 hasher.update(key.as_bytes());
214 hasher.update(entry.as_bytes());
215
216 RawIndexFingerprint(hasher.digest128().to_be_bytes())
217 }
218}