icydb_core/db/index/
entry.rs1use crate::{
2 db::index::RawIndexKey,
3 key::{Key, KeyEncodeError},
4 traits::Storable,
5};
6use candid::CandidType;
7use canic_cdk::structures::storable::Bound;
8use serde::{Deserialize, Serialize};
9use std::{borrow::Cow, collections::BTreeSet};
10use thiserror::Error as ThisError;
11
12const INDEX_ENTRY_LEN_BYTES: usize = 4;
17pub const MAX_INDEX_ENTRY_KEYS: usize = 65_535;
18#[allow(clippy::cast_possible_truncation)]
19pub const MAX_INDEX_ENTRY_BYTES: u32 =
20 (INDEX_ENTRY_LEN_BYTES + (MAX_INDEX_ENTRY_KEYS * Key::STORED_SIZE)) as u32;
21
22#[derive(Debug, ThisError)]
27pub enum IndexEntryCorruption {
28 #[error("index entry exceeds max size")]
29 TooLarge { len: usize },
30
31 #[error("index entry missing key count")]
32 MissingLength,
33
34 #[error("index entry key count exceeds limit")]
35 TooManyKeys { count: usize },
36
37 #[error("index entry length does not match key count")]
38 LengthMismatch,
39
40 #[error("index entry contains invalid key bytes")]
41 InvalidKey,
42
43 #[error("index entry contains duplicate key")]
44 DuplicateKey,
45
46 #[error("index entry contains zero keys")]
47 EmptyEntry,
48
49 #[error("unique index entry contains {keys} keys")]
50 NonUniqueEntry { keys: usize },
51
52 #[error("index entry missing expected entity key: {entity_key} (index {index_key:?})")]
53 MissingKey {
54 index_key: Box<RawIndexKey>,
55 entity_key: Key,
56 },
57}
58
59impl IndexEntryCorruption {
60 #[must_use]
62 pub fn missing_key(index_key: RawIndexKey, entity_key: Key) -> Self {
63 Self::MissingKey {
64 index_key: Box::new(index_key),
65 entity_key,
66 }
67 }
68}
69
70#[derive(Debug, ThisError)]
75pub enum IndexEntryEncodeError {
76 #[error("index entry exceeds max keys: {keys} (limit {MAX_INDEX_ENTRY_KEYS})")]
77 TooManyKeys { keys: usize },
78 #[error("index entry key encoding failed: {0}")]
79 KeyEncoding(#[from] KeyEncodeError),
80}
81
82impl IndexEntryEncodeError {
83 #[must_use]
84 pub const fn keys(&self) -> usize {
85 match self {
86 Self::TooManyKeys { keys } => *keys,
87 Self::KeyEncoding(_) => 0,
88 }
89 }
90}
91
92#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
97pub struct IndexEntry {
98 keys: BTreeSet<Key>,
99}
100
101impl IndexEntry {
102 #[must_use]
103 pub fn new(key: Key) -> Self {
104 let mut keys = BTreeSet::new();
105 keys.insert(key);
106
107 Self { keys }
108 }
109
110 pub fn insert_key(&mut self, key: Key) {
111 self.keys.insert(key);
112 }
113
114 pub fn remove_key(&mut self, key: &Key) {
115 self.keys.remove(key);
116 }
117
118 #[must_use]
119 pub fn contains(&self, key: &Key) -> bool {
120 self.keys.contains(key)
121 }
122
123 #[must_use]
124 pub fn is_empty(&self) -> bool {
125 self.keys.is_empty()
126 }
127
128 #[must_use]
129 pub fn len(&self) -> usize {
130 self.keys.len()
131 }
132
133 pub fn iter_keys(&self) -> impl Iterator<Item = Key> + '_ {
134 self.keys.iter().copied()
135 }
136
137 #[must_use]
138 pub fn single_key(&self) -> Option<Key> {
139 if self.keys.len() == 1 {
140 self.keys.iter().copied().next()
141 } else {
142 None
143 }
144 }
145}
146
147#[derive(Clone, Debug, Eq, PartialEq)]
152pub struct RawIndexEntry(Vec<u8>);
153
154impl RawIndexEntry {
155 pub fn try_from_entry(entry: &IndexEntry) -> Result<Self, IndexEntryEncodeError> {
156 let keys = entry.len();
157 if keys > MAX_INDEX_ENTRY_KEYS {
158 return Err(IndexEntryEncodeError::TooManyKeys { keys });
159 }
160
161 let mut out = Vec::with_capacity(INDEX_ENTRY_LEN_BYTES + (keys * Key::STORED_SIZE));
162 let count = u32::try_from(keys).map_err(|_| IndexEntryEncodeError::TooManyKeys { keys })?;
163 out.extend_from_slice(&count.to_be_bytes());
164 for key in entry.iter_keys() {
165 let bytes = key.to_bytes()?;
166 out.extend_from_slice(&bytes);
167 }
168
169 Ok(Self(out))
170 }
171
172 pub fn try_decode(&self) -> Result<IndexEntry, IndexEntryCorruption> {
173 let bytes = self.0.as_slice();
174 if bytes.len() > MAX_INDEX_ENTRY_BYTES as usize {
175 return Err(IndexEntryCorruption::TooLarge { len: bytes.len() });
176 }
177 if bytes.len() < INDEX_ENTRY_LEN_BYTES {
178 return Err(IndexEntryCorruption::MissingLength);
179 }
180
181 let mut len_buf = [0u8; INDEX_ENTRY_LEN_BYTES];
182 len_buf.copy_from_slice(&bytes[..INDEX_ENTRY_LEN_BYTES]);
183 let count = u32::from_be_bytes(len_buf) as usize;
184 if count == 0 {
185 return Err(IndexEntryCorruption::EmptyEntry);
186 }
187 if count > MAX_INDEX_ENTRY_KEYS {
188 return Err(IndexEntryCorruption::TooManyKeys { count });
189 }
190
191 let expected = INDEX_ENTRY_LEN_BYTES
192 .checked_add(
193 count
194 .checked_mul(Key::STORED_SIZE)
195 .ok_or(IndexEntryCorruption::LengthMismatch)?,
196 )
197 .ok_or(IndexEntryCorruption::LengthMismatch)?;
198 if bytes.len() != expected {
199 return Err(IndexEntryCorruption::LengthMismatch);
200 }
201
202 let mut keys = BTreeSet::new();
203 let mut offset = INDEX_ENTRY_LEN_BYTES;
204 for _ in 0..count {
205 let end = offset + Key::STORED_SIZE;
206 let key = Key::try_from_bytes(&bytes[offset..end])
207 .map_err(|_| IndexEntryCorruption::InvalidKey)?;
208 if !keys.insert(key) {
209 return Err(IndexEntryCorruption::DuplicateKey);
210 }
211 offset = end;
212 }
213
214 Ok(IndexEntry { keys })
215 }
216
217 #[must_use]
218 pub fn as_bytes(&self) -> &[u8] {
219 &self.0
220 }
221
222 #[must_use]
223 pub const fn len(&self) -> usize {
224 self.0.len()
225 }
226
227 #[must_use]
228 pub const fn is_empty(&self) -> bool {
229 self.0.is_empty()
230 }
231}
232
233impl Storable for RawIndexEntry {
234 fn to_bytes(&self) -> Cow<'_, [u8]> {
235 Cow::Borrowed(&self.0)
236 }
237
238 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
239 Self(bytes.into_owned())
240 }
241
242 fn into_bytes(self) -> Vec<u8> {
243 self.0
244 }
245
246 const BOUND: Bound = Bound::Bounded {
247 max_size: MAX_INDEX_ENTRY_BYTES,
248 is_fixed_size: false,
249 };
250}