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_USIZE)) 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
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///
100/// IndexEntry
101///
102
103#[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///
155/// RawIndexEntry
156///
157
158#[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}