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
59impl IndexEntryCorruption {
60    // Helper used only for variants with large or representation-sensitive payloads.
61    #[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///
71/// IndexEntryEncodeError
72///
73
74#[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///
93/// IndexEntry
94///
95
96#[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///
148/// RawIndexEntry
149///
150
151#[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}