1use crate::{
2 MAX_INDEX_FIELDS,
3 db::{
4 executor::ExecutorError,
5 store::{DataKey, StoreRegistry},
6 },
7 error::InternalError,
8 obs::sink::{self, MetricsEvent},
9 prelude::*,
10};
11use candid::CandidType;
12use canic_cdk::structures::{BTreeMap, DefaultMemoryImpl, memory::VirtualMemory};
13use canic_memory::{impl_storable_bounded, impl_storable_unbounded};
14use canic_utils::hash::hash_u64;
15use derive_more::{Deref, DerefMut, Display};
16use serde::{Deserialize, Serialize};
17use std::{
18 collections::HashSet,
19 fmt::{self, Display},
20};
21
22#[derive(Deref, DerefMut)]
27pub struct IndexStoreRegistry(StoreRegistry<IndexStore>);
28
29impl IndexStoreRegistry {
30 #[must_use]
31 #[allow(clippy::new_without_default)]
32 pub fn new() -> Self {
34 Self(StoreRegistry::new())
35 }
36}
37
38#[derive(Deref, DerefMut)]
43pub struct IndexStore(BTreeMap<IndexKey, IndexEntry, VirtualMemory<DefaultMemoryImpl>>);
44
45impl IndexStore {
46 #[must_use]
47 pub fn init(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
49 Self(BTreeMap::init(memory))
50 }
51
52 pub fn insert_index_entry<E: EntityKind>(
57 &mut self,
58 entity: &E,
59 index: &IndexModel,
60 ) -> Result<(), InternalError> {
61 let Some(index_key) = IndexKey::new(entity, index) else {
63 return Ok(());
64 };
65 let key = entity.key();
66
67 if let Some(mut existing) = self.get(&index_key) {
68 if index.unique {
69 if existing.contains(&key) {
71 return Ok(());
72 }
73
74 if !existing.is_empty() {
76 sink::record(MetricsEvent::UniqueViolation {
77 entity_path: E::PATH,
78 });
79
80 return Err(ExecutorError::index_violation(E::PATH, index.fields).into());
81 }
82
83 self.insert(index_key.clone(), IndexEntry::new(index.fields, key));
84 } else {
85 existing.insert_key(key); self.insert(index_key.clone(), existing);
87 }
88 } else {
89 self.insert(index_key, IndexEntry::new(index.fields, key));
90 }
91 sink::record(MetricsEvent::IndexInsert {
92 entity_path: E::PATH,
93 });
94
95 Ok(())
96 }
97
98 pub fn remove_index_entry<E: EntityKind>(&mut self, entity: &E, index: &IndexModel) {
101 let Some(index_key) = IndexKey::new(entity, index) else {
103 return;
104 };
105
106 if let Some(mut existing) = self.get(&index_key) {
107 existing.remove_key(&entity.key()); if existing.is_empty() {
110 self.remove(&index_key);
111 } else {
112 self.insert(index_key, existing);
114 }
115 sink::record(MetricsEvent::IndexRemove {
116 entity_path: E::PATH,
117 });
118 }
119 }
120
121 #[must_use]
122 pub fn resolve_data_values<E: EntityKind>(
124 &self,
125 index: &IndexModel,
126 prefix: &[Value],
127 ) -> Vec<DataKey> {
128 let mut out = Vec::new();
129
130 for (_, entry) in self.iter_with_hashed_prefix::<E>(index, prefix) {
131 out.extend(entry.keys.iter().map(|&k| DataKey::new::<E>(k)));
132 }
133
134 out
135 }
136
137 pub fn memory_bytes(&self) -> u64 {
139 self.iter()
140 .map(|entry| u64::from(IndexKey::STORABLE_MAX_SIZE) + entry.value().len() as u64)
141 .sum()
142 }
143
144 fn iter_with_hashed_prefix<E: EntityKind>(
147 &self,
148 index: &IndexModel,
149 prefix: &[Value],
150 ) -> impl Iterator<Item = (IndexKey, IndexEntry)> {
151 let index_id = IndexId::new::<E>(index);
152 let hashed_prefix_opt = Self::index_fingerprints(prefix); let (start_key, end_key) = if let Some(hp) = hashed_prefix_opt {
157 IndexKey::bounds_for_prefix(index_id, hp)
158 } else {
159 (
160 IndexKey {
161 index_id,
162 hashed_values: Vec::new(),
163 },
164 IndexKey {
165 index_id,
166 hashed_values: Vec::new(),
167 },
168 )
169 };
170
171 self.range(start_key..end_key)
172 .map(|entry| (entry.key().clone(), entry.value()))
173 }
174
175 fn index_fingerprints(values: &[Value]) -> Option<Vec<[u8; 16]>> {
176 values.iter().map(Value::to_index_fingerprint).collect()
178 }
179}
180
181#[derive(
186 Clone,
187 Debug,
188 Display,
189 Copy,
190 PartialEq,
191 Eq,
192 Hash,
193 PartialOrd,
194 Ord,
195 CandidType,
196 Serialize,
197 Deserialize,
198)]
199pub struct IndexId(u64);
200
201impl IndexId {
202 #[must_use]
203 pub fn new<E: EntityKind>(index: &IndexModel) -> Self {
205 Self::from_path_and_fields(E::PATH, index.fields)
206 }
207
208 fn from_path_and_fields(path: &str, fields: &[&str]) -> Self {
209 let cap = path.len() + fields.iter().map(|f| f.len() + 1).sum::<usize>();
210 let mut buffer = Vec::with_capacity(cap);
211
212 buffer.extend_from_slice(path.as_bytes());
214 for field in fields {
215 buffer.extend_from_slice(field.as_bytes());
216 buffer.extend_from_slice(b"|");
217 }
218
219 Self(hash_u64(&buffer))
220 }
221
222 #[must_use]
223 pub fn max_storable() -> Self {
225 Self::from_path_and_fields(
226 "path::to::long::entity::name::Entity",
227 &[
228 "long_field_one",
229 "long_field_two",
230 "long_field_three",
231 "long_field_four",
232 ],
233 )
234 }
235}
236
237#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
242pub struct IndexKey {
243 pub index_id: IndexId,
244 pub hashed_values: Vec<[u8; 16]>,
245}
246
247impl IndexKey {
248 pub const STORABLE_MAX_SIZE: u32 = 180;
250
251 #[must_use]
252 pub fn new<E: EntityKind>(entity: &E, index: &IndexModel) -> Option<Self> {
254 let mut hashed_values = Vec::<[u8; 16]>::with_capacity(index.fields.len());
255
256 for field in index.fields {
258 let value = entity.get_value(field)?;
259 let fp = value.to_index_fingerprint()?; hashed_values.push(fp);
262 }
263
264 Some(Self {
265 index_id: IndexId::new::<E>(index),
266 hashed_values,
267 })
268 }
269
270 #[must_use]
272 pub fn max_storable() -> Self {
274 Self {
275 index_id: IndexId::max_storable(),
276 hashed_values: (0..MAX_INDEX_FIELDS).map(|_| [u8::MAX; 16]).collect(),
277 }
278 }
279
280 #[must_use]
283 pub fn bounds_for_prefix(index_id: IndexId, mut prefix: Vec<[u8; 16]>) -> (Self, Self) {
285 let start = Self {
286 index_id,
287 hashed_values: prefix.clone(),
288 };
289 prefix.push([0xFF; 16]);
290 let end = Self {
291 index_id,
292 hashed_values: prefix,
293 };
294 (start, end)
295 }
296}
297
298impl Display for IndexKey {
299 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
300 write!(
301 f,
302 "id: {}, values: {}",
303 self.index_id,
304 self.hashed_values.len()
305 )
306 }
307}
308
309impl_storable_bounded!(IndexKey, IndexKey::STORABLE_MAX_SIZE, false);
310
311#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
316pub struct IndexEntry {
317 fields: Vec<String>,
318 keys: HashSet<Key>,
319}
320
321impl IndexEntry {
322 #[must_use]
323 pub fn new(fields: &[&str], key: Key) -> Self {
325 let mut key_set = HashSet::with_capacity(1);
326 key_set.insert(key);
327
328 Self {
329 fields: fields.iter().map(ToString::to_string).collect(),
330 keys: key_set,
331 }
332 }
333
334 pub fn insert_key(&mut self, key: Key) {
336 let _ = self.keys.insert(key);
337 }
338
339 pub fn remove_key(&mut self, key: &Key) {
341 let _ = self.keys.remove(key);
342 }
343
344 #[must_use]
346 pub fn contains(&self, key: &Key) -> bool {
347 self.keys.contains(key)
348 }
349
350 #[must_use]
352 pub fn is_empty(&self) -> bool {
353 self.keys.is_empty()
354 }
355
356 #[must_use]
358 pub fn len(&self) -> usize {
359 self.keys.len()
360 }
361
362 #[must_use]
364 pub fn single_key(&self) -> Option<Key> {
365 if self.keys.len() == 1 {
366 self.keys.iter().copied().next()
367 } else {
368 None
369 }
370 }
371
372 #[must_use]
374 pub fn to_sorted_vec(&self) -> Vec<Key> {
375 let mut keys: Vec<_> = self.keys.iter().copied().collect();
376 keys.sort_unstable(); keys
378 }
379}
380
381impl_storable_unbounded!(IndexEntry);
382
383#[cfg(test)]
388mod tests {
389 use super::*;
390 use crate::traits::*;
391
392 #[test]
393 fn index_key_max_size_is_bounded() {
394 let index_key = IndexKey::max_storable();
395 let size = Storable::to_bytes(&index_key).len();
396
397 assert!(
398 size <= IndexKey::STORABLE_MAX_SIZE as usize,
399 "serialized IndexKey too large: got {size} bytes (limit {})",
400 IndexKey::STORABLE_MAX_SIZE
401 );
402 }
403
404 #[test]
405 fn index_entry_round_trip() {
406 let original = IndexEntry::new(&["a", "b"], Key::from(1u64));
407 let encoded = Storable::to_bytes(&original);
408 let decoded = IndexEntry::from_bytes(encoded);
409
410 assert_eq!(original.fields, decoded.fields);
411 assert_eq!(original.to_sorted_vec(), decoded.to_sorted_vec());
412 }
413}