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_USIZE)) 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 #[error("index entry points at key {indexed_key} but stored row key is {row_key}")]
59 RowKeyMismatch {
60 indexed_key: Box<Key>,
61 row_key: Box<Key>,
62 },
63}
64
65impl IndexEntryCorruption {
66 #[must_use]
68 pub fn missing_key(index_key: RawIndexKey, entity_key: Key) -> Self {
69 Self::MissingKey {
70 index_key: Box::new(index_key),
71 entity_key,
72 }
73 }
74}
75
76#[derive(Debug, ThisError)]
81pub enum IndexEntryEncodeError {
82 #[error("index entry exceeds max keys: {keys} (limit {MAX_INDEX_ENTRY_KEYS})")]
83 TooManyKeys { keys: usize },
84
85 #[error("index entry key encoding failed: {0}")]
86 KeyEncoding(#[from] KeyEncodeError),
87}
88
89impl IndexEntryEncodeError {
90 #[must_use]
91 pub const fn keys(&self) -> usize {
92 match self {
93 Self::TooManyKeys { keys } => *keys,
94 Self::KeyEncoding(_) => 0,
95 }
96 }
97}
98
99#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
104pub struct IndexEntry {
105 keys: BTreeSet<Key>,
106}
107
108impl IndexEntry {
109 #[must_use]
110 pub fn new(key: Key) -> Self {
111 let mut keys = BTreeSet::new();
112 keys.insert(key);
113
114 Self { keys }
115 }
116
117 pub fn insert_key(&mut self, key: Key) {
118 self.keys.insert(key);
119 }
120
121 pub fn remove_key(&mut self, key: &Key) {
122 self.keys.remove(key);
123 }
124
125 #[must_use]
126 pub fn contains(&self, key: &Key) -> bool {
127 self.keys.contains(key)
128 }
129
130 #[must_use]
131 pub fn is_empty(&self) -> bool {
132 self.keys.is_empty()
133 }
134
135 #[must_use]
136 pub fn len(&self) -> usize {
137 self.keys.len()
138 }
139
140 pub fn iter_keys(&self) -> impl Iterator<Item = Key> + '_ {
141 self.keys.iter().copied()
142 }
143
144 #[must_use]
145 pub fn single_key(&self) -> Option<Key> {
146 if self.keys.len() == 1 {
147 self.keys.iter().copied().next()
148 } else {
149 None
150 }
151 }
152}
153
154#[derive(Clone, Debug, Eq, PartialEq)]
159pub struct RawIndexEntry(Vec<u8>);
160
161impl RawIndexEntry {
162 pub fn try_from_entry(entry: &IndexEntry) -> Result<Self, IndexEntryEncodeError> {
163 let keys = entry.len();
164 if keys > MAX_INDEX_ENTRY_KEYS {
165 return Err(IndexEntryEncodeError::TooManyKeys { keys });
166 }
167
168 let mut out = Vec::with_capacity(INDEX_ENTRY_LEN_BYTES + (keys * Key::STORED_SIZE_USIZE));
169 let count = u32::try_from(keys).map_err(|_| IndexEntryEncodeError::TooManyKeys { keys })?;
170 out.extend_from_slice(&count.to_be_bytes());
171 for key in entry.iter_keys() {
172 let bytes = key.to_bytes()?;
173 out.extend_from_slice(&bytes);
174 }
175
176 Ok(Self(out))
177 }
178
179 pub fn try_decode(&self) -> Result<IndexEntry, IndexEntryCorruption> {
180 let bytes = self.0.as_slice();
181 if bytes.len() > MAX_INDEX_ENTRY_BYTES as usize {
182 return Err(IndexEntryCorruption::TooLarge { len: bytes.len() });
183 }
184 if bytes.len() < INDEX_ENTRY_LEN_BYTES {
185 return Err(IndexEntryCorruption::MissingLength);
186 }
187
188 let mut len_buf = [0u8; INDEX_ENTRY_LEN_BYTES];
189 len_buf.copy_from_slice(&bytes[..INDEX_ENTRY_LEN_BYTES]);
190 let count = u32::from_be_bytes(len_buf) as usize;
191 if count == 0 {
192 return Err(IndexEntryCorruption::EmptyEntry);
193 }
194 if count > MAX_INDEX_ENTRY_KEYS {
195 return Err(IndexEntryCorruption::TooManyKeys { count });
196 }
197
198 let expected = INDEX_ENTRY_LEN_BYTES
199 .checked_add(
200 count
201 .checked_mul(Key::STORED_SIZE_USIZE)
202 .ok_or(IndexEntryCorruption::LengthMismatch)?,
203 )
204 .ok_or(IndexEntryCorruption::LengthMismatch)?;
205 if bytes.len() != expected {
206 return Err(IndexEntryCorruption::LengthMismatch);
207 }
208
209 let mut keys = BTreeSet::new();
210 let mut offset = INDEX_ENTRY_LEN_BYTES;
211 for _ in 0..count {
212 let end = offset + Key::STORED_SIZE_USIZE;
213 let key = Key::try_from_bytes(&bytes[offset..end])
214 .map_err(|_| IndexEntryCorruption::InvalidKey)?;
215 if !keys.insert(key) {
216 return Err(IndexEntryCorruption::DuplicateKey);
217 }
218 offset = end;
219 }
220
221 Ok(IndexEntry { keys })
222 }
223
224 #[must_use]
225 pub fn as_bytes(&self) -> &[u8] {
226 &self.0
227 }
228
229 #[must_use]
230 pub const fn len(&self) -> usize {
231 self.0.len()
232 }
233
234 #[must_use]
235 pub const fn is_empty(&self) -> bool {
236 self.0.is_empty()
237 }
238}
239
240impl Storable for RawIndexEntry {
241 fn to_bytes(&self) -> Cow<'_, [u8]> {
242 Cow::Borrowed(&self.0)
243 }
244
245 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
246 Self(bytes.into_owned())
247 }
248
249 fn into_bytes(self) -> Vec<u8> {
250 self.0
251 }
252
253 const BOUND: Bound = Bound::Bounded {
254 max_size: MAX_INDEX_ENTRY_BYTES,
255 is_fixed_size: false,
256 };
257}