icydb_core/db/index/
key.rs1use crate::{
2 MAX_INDEX_FIELDS,
3 db::{
4 identity::{EntityName, IndexName},
5 index::fingerprint,
6 },
7 prelude::{EntityKind, IndexModel},
8 traits::Storable,
9};
10use canic_cdk::structures::storable::Bound;
11use derive_more::Display;
12use std::borrow::Cow;
13
14#[derive(Clone, Copy, Debug, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
23pub struct IndexId(pub IndexName);
24
25impl IndexId {
26 #[must_use]
27 pub fn new<E: EntityKind>(index: &IndexModel) -> Self {
28 let entity = EntityName::from_static(E::ENTITY_NAME);
29 Self(IndexName::from_parts(&entity, index.fields))
30 }
31
32 #[must_use]
35 pub const fn max_storable() -> Self {
36 Self(IndexName::max_storable())
37 }
38}
39
40#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
49pub struct IndexKey {
50 index_id: IndexId,
51 len: u8,
52 values: [[u8; 16]; MAX_INDEX_FIELDS],
53}
54
55impl IndexKey {
56 #[allow(clippy::cast_possible_truncation)]
57 pub const STORED_SIZE: u32 = IndexName::STORED_SIZE + 1 + (MAX_INDEX_FIELDS as u32 * 16);
58
59 pub fn new<E: EntityKind>(entity: &E, index: &IndexModel) -> Option<Self> {
60 if index.fields.len() > MAX_INDEX_FIELDS {
61 return None;
62 }
63
64 let mut values = [[0u8; 16]; MAX_INDEX_FIELDS];
65 let mut len = 0usize;
66
67 for field in index.fields {
68 let value = entity.get_value(field)?;
69 let fp = fingerprint::to_index_fingerprint(&value)?;
70 values[len] = fp;
71 len += 1;
72 }
73
74 #[allow(clippy::cast_possible_truncation)]
75 Some(Self {
76 index_id: IndexId::new::<E>(index),
77 len: len as u8,
78 values,
79 })
80 }
81
82 #[must_use]
83 pub const fn empty(index_id: IndexId) -> Self {
84 Self {
85 index_id,
86 len: 0,
87 values: [[0u8; 16]; MAX_INDEX_FIELDS],
88 }
89 }
90
91 #[must_use]
92 #[allow(clippy::cast_possible_truncation)]
93 pub fn bounds_for_prefix(
94 index_id: IndexId,
95 index_len: usize,
96 prefix: &[[u8; 16]],
97 ) -> (Self, Self) {
98 let mut start = Self::empty(index_id);
99 let mut end = Self::empty(index_id);
100
101 for (i, fp) in prefix.iter().enumerate() {
102 start.values[i] = *fp;
103 end.values[i] = *fp;
104 }
105
106 start.len = index_len as u8;
107 end.len = start.len;
108
109 for value in end.values.iter_mut().take(index_len).skip(prefix.len()) {
110 *value = [0xFF; 16];
111 }
112
113 (start, end)
114 }
115
116 #[must_use]
117 pub fn to_raw(&self) -> RawIndexKey {
118 let mut buf = [0u8; Self::STORED_SIZE as usize];
119
120 let name_bytes = self.index_id.0.to_bytes();
121 buf[..name_bytes.len()].copy_from_slice(&name_bytes);
122
123 let mut offset = IndexName::STORED_SIZE as usize;
124 buf[offset] = self.len;
125 offset += 1;
126
127 for value in &self.values {
128 buf[offset..offset + 16].copy_from_slice(value);
129 offset += 16;
130 }
131
132 RawIndexKey(buf)
133 }
134
135 pub fn try_from_raw(raw: &RawIndexKey) -> Result<Self, &'static str> {
136 let bytes = &raw.0;
137 if bytes.len() != Self::STORED_SIZE as usize {
138 return Err("corrupted IndexKey: invalid size");
139 }
140
141 let mut offset = 0;
142
143 let index_name =
144 IndexName::from_bytes(&bytes[offset..offset + IndexName::STORED_SIZE as usize])
145 .map_err(|_| "corrupted IndexKey: invalid IndexName bytes")?;
146 offset += IndexName::STORED_SIZE as usize;
147
148 let len = bytes[offset];
149 offset += 1;
150
151 if len as usize > MAX_INDEX_FIELDS {
152 return Err("corrupted IndexKey: invalid index length");
153 }
154
155 let mut values = [[0u8; 16]; MAX_INDEX_FIELDS];
156 for value in &mut values {
157 value.copy_from_slice(&bytes[offset..offset + 16]);
158 offset += 16;
159 }
160
161 let len_usize = len as usize;
162 for value in values.iter().skip(len_usize) {
163 if value.iter().any(|&b| b != 0) {
164 return Err("corrupted IndexKey: non-zero fingerprint padding");
165 }
166 }
167
168 Ok(Self {
169 index_id: IndexId(index_name),
170 len,
171 values,
172 })
173 }
174}
175
176#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
184pub struct RawIndexKey([u8; IndexKey::STORED_SIZE as usize]);
185
186impl RawIndexKey {
187 #[must_use]
189 pub const fn as_bytes(&self) -> &[u8; IndexKey::STORED_SIZE as usize] {
190 &self.0
191 }
192}
193
194impl Storable for RawIndexKey {
195 fn to_bytes(&self) -> Cow<'_, [u8]> {
196 Cow::Borrowed(&self.0)
197 }
198
199 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
200 let mut out = [0u8; IndexKey::STORED_SIZE as usize];
201 if bytes.len() == out.len() {
202 out.copy_from_slice(bytes.as_ref());
203 }
204 Self(out)
205 }
206
207 fn into_bytes(self) -> Vec<u8> {
208 self.0.to_vec()
209 }
210
211 const BOUND: Bound = Bound::Bounded {
212 max_size: IndexKey::STORED_SIZE,
213 is_fixed_size: true,
214 };
215}