Skip to main content

icydb_core/db/index/
entry.rs

1use 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
12///
13/// Constants
14///
15
16const 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///
23/// IndexEntryCorruption
24///
25
26#[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    // Helper used only for variants with large or representation-sensitive payloads.
67    #[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///
77/// IndexEntryEncodeError
78///
79
80#[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///
99/// IndexEntry
100///
101
102#[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///
154/// RawIndexEntry
155///
156
157#[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}