1use thiserror::Error;
25
26use crate::registry::{CollectionId, DocId, FieldId};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[repr(u8)]
31pub enum KeyTag {
32 Doc = 0x01,
34 Collection = 0x02,
36 Schema = 0x03,
38 Dictionary = 0x04,
40 Registry = 0x05,
42 ColdDoc = 0x06,
44 HashIndex = 0x10,
46 SortedIndex = 0x11,
48 ArrayIndex = 0x12,
50 UniqueIndex = 0x13,
52 CompoundIndex = 0x14,
54 CdcHead = 0x20,
56 CdcEvent = 0x21,
58}
59
60#[derive(Debug, Error, PartialEq, Eq)]
62pub enum KeyDecodeError {
63 #[error("invalid key length: expected {expected}, got {actual}")]
65 InvalidLength {
66 expected: usize,
68 actual: usize,
70 },
71 #[error("unexpected key tag: expected 0x{expected:02x}, got 0x{actual:02x}")]
73 UnexpectedTag {
74 expected: u8,
76 actual: u8,
78 },
79}
80
81#[must_use]
83pub fn encode_doc_key(collection_id: CollectionId, doc_id: DocId) -> [u8; 7] {
84 let mut key = [0u8; 7];
85 key[0] = KeyTag::Doc as u8;
86 key[1..3].copy_from_slice(&collection_id.to_le_bytes());
87 key[3..7].copy_from_slice(&doc_id.to_le_bytes());
88 key
89}
90
91pub fn decode_doc_key(key: &[u8]) -> Result<(CollectionId, DocId), KeyDecodeError> {
93 decode_collection_doc_key(key, KeyTag::Doc)
94}
95
96#[must_use]
98pub fn encode_cold_doc_key(collection_id: CollectionId, doc_id: DocId) -> [u8; 7] {
99 let mut key = [0u8; 7];
100 key[0] = KeyTag::ColdDoc as u8;
101 key[1..3].copy_from_slice(&collection_id.to_le_bytes());
102 key[3..7].copy_from_slice(&doc_id.to_le_bytes());
103 key
104}
105
106pub fn decode_cold_doc_key(key: &[u8]) -> Result<(CollectionId, DocId), KeyDecodeError> {
108 decode_collection_doc_key(key, KeyTag::ColdDoc)
109}
110
111#[must_use]
113pub fn encode_collection_key(tag: KeyTag, collection_id: CollectionId) -> [u8; 3] {
114 let mut key = [0u8; 3];
115 key[0] = tag as u8;
116 key[1..3].copy_from_slice(&collection_id.to_le_bytes());
117 key
118}
119
120pub fn decode_collection_key(
122 key: &[u8],
123 expected_tag: KeyTag,
124) -> Result<CollectionId, KeyDecodeError> {
125 ensure_key_shape(key, 3, expected_tag)?;
126 Ok(u16::from_le_bytes([key[1], key[2]]))
127}
128
129#[must_use]
131pub fn encode_hashed_bucket_key(
132 tag: KeyTag,
133 collection_id: CollectionId,
134 field_id: FieldId,
135 value_hash: u32,
136) -> [u8; 10] {
137 let mut key = [0u8; 10];
138 key[0] = tag as u8;
139 key[1..3].copy_from_slice(&collection_id.to_le_bytes());
140 key[3..5].copy_from_slice(&field_id.to_le_bytes());
141 key[5..9].copy_from_slice(&value_hash.to_le_bytes());
142 key
143}
144
145pub fn decode_hashed_bucket_key(
147 key: &[u8],
148 expected_tag: KeyTag,
149) -> Result<(CollectionId, FieldId, u32), KeyDecodeError> {
150 ensure_key_shape(key, 10, expected_tag)?;
151 let collection_id = u16::from_le_bytes([key[1], key[2]]);
152 let field_id = u16::from_le_bytes([key[3], key[4]]);
153 let value_hash = u32::from_le_bytes([key[5], key[6], key[7], key[8]]);
154 Ok((collection_id, field_id, value_hash))
155}
156
157#[must_use]
159pub fn encode_sorted_index_key(collection_id: CollectionId, field_id: FieldId) -> [u8; 5] {
160 let mut key = [0u8; 5];
161 key[0] = KeyTag::SortedIndex as u8;
162 key[1..3].copy_from_slice(&collection_id.to_le_bytes());
163 key[3..5].copy_from_slice(&field_id.to_le_bytes());
164 key
165}
166
167pub fn decode_sorted_index_key(key: &[u8]) -> Result<(CollectionId, FieldId), KeyDecodeError> {
169 ensure_key_shape(key, 5, KeyTag::SortedIndex)?;
170 Ok((
171 u16::from_le_bytes([key[1], key[2]]),
172 u16::from_le_bytes([key[3], key[4]]),
173 ))
174}
175
176#[must_use]
178pub fn encode_compound_index_key(
179 collection_id: CollectionId,
180 first_field_id: FieldId,
181 second_field_id: FieldId,
182 value_hash: u32,
183) -> [u8; 11] {
184 let mut key = [0u8; 11];
185 key[0] = KeyTag::CompoundIndex as u8;
186 key[1..3].copy_from_slice(&collection_id.to_le_bytes());
187 key[3..5].copy_from_slice(&first_field_id.to_le_bytes());
188 key[5..7].copy_from_slice(&second_field_id.to_le_bytes());
189 key[7..11].copy_from_slice(&value_hash.to_le_bytes());
190 key
191}
192
193pub fn decode_compound_index_key(
195 key: &[u8],
196) -> Result<(CollectionId, FieldId, FieldId, u32), KeyDecodeError> {
197 ensure_key_shape(key, 11, KeyTag::CompoundIndex)?;
198 Ok((
199 u16::from_le_bytes([key[1], key[2]]),
200 u16::from_le_bytes([key[3], key[4]]),
201 u16::from_le_bytes([key[5], key[6]]),
202 u32::from_le_bytes([key[7], key[8], key[9], key[10]]),
203 ))
204}
205
206#[must_use]
208pub fn encode_cdc_event_key(collection_id: CollectionId, sequence: u64) -> [u8; 11] {
209 let mut key = [0u8; 11];
210 key[0] = KeyTag::CdcEvent as u8;
211 key[1..3].copy_from_slice(&collection_id.to_le_bytes());
212 key[3..11].copy_from_slice(&sequence.to_le_bytes());
213 key
214}
215
216pub fn decode_cdc_event_key(key: &[u8]) -> Result<(CollectionId, u64), KeyDecodeError> {
218 ensure_key_shape(key, 11, KeyTag::CdcEvent)?;
219 Ok((
220 u16::from_le_bytes([key[1], key[2]]),
221 u64::from_le_bytes([
222 key[3], key[4], key[5], key[6], key[7], key[8], key[9], key[10],
223 ]),
224 ))
225}
226
227fn decode_collection_doc_key(
228 key: &[u8],
229 expected_tag: KeyTag,
230) -> Result<(CollectionId, DocId), KeyDecodeError> {
231 ensure_key_shape(key, 7, expected_tag)?;
232 let collection_id = u16::from_le_bytes([key[1], key[2]]);
233 let doc_id = u32::from_le_bytes([key[3], key[4], key[5], key[6]]);
234 Ok((collection_id, doc_id))
235}
236
237fn ensure_key_shape(
238 key: &[u8],
239 expected_len: usize,
240 expected_tag: KeyTag,
241) -> Result<(), KeyDecodeError> {
242 if key.len() != expected_len {
243 return Err(KeyDecodeError::InvalidLength {
244 expected: expected_len,
245 actual: key.len(),
246 });
247 }
248 if key[0] != expected_tag as u8 {
249 return Err(KeyDecodeError::UnexpectedTag {
250 expected: expected_tag as u8,
251 actual: key[0],
252 });
253 }
254 Ok(())
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn round_trip_doc_key() {
263 let encoded = encode_doc_key(10, 1234);
264 let decoded = decode_doc_key(&encoded).expect("doc key should decode");
265 assert_eq!(decoded, (10, 1234));
266 }
267
268 #[test]
269 fn round_trip_hashed_bucket_key() {
270 let encoded = encode_hashed_bucket_key(KeyTag::HashIndex, 4, 9, 0xAABBCCDD);
271 let decoded =
272 decode_hashed_bucket_key(&encoded, KeyTag::HashIndex).expect("hash key should decode");
273 assert_eq!(decoded, (4, 9, 0xAABBCCDD));
274 }
275
276 #[test]
277 fn rejects_invalid_tag() {
278 let mut encoded = encode_doc_key(1, 2);
279 encoded[0] = KeyTag::ColdDoc as u8;
280 let err = decode_doc_key(&encoded).expect_err("wrong tag should fail");
281 assert_eq!(
282 err,
283 KeyDecodeError::UnexpectedTag {
284 expected: KeyTag::Doc as u8,
285 actual: KeyTag::ColdDoc as u8
286 }
287 );
288 }
289}