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