Skip to main content

mqdb_core/
keys.rs

1// Copyright 2025-2026 LabOverWire. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::error::{Error, Result};
5
6pub const SEPARATOR: u8 = b'/';
7pub const DATA_PREFIX: &[u8] = b"data";
8pub const INDEX_PREFIX: &[u8] = b"idx";
9pub const WASM_INDEX_PREFIX: &str = "index";
10pub const SUB_PREFIX: &[u8] = b"sub";
11pub const DEDUP_PREFIX: &[u8] = b"dedup";
12pub const META_PREFIX: &[u8] = b"meta";
13
14#[must_use]
15pub fn encode_data_key(entity: &str, id: &str) -> Vec<u8> {
16    let mut key = Vec::with_capacity(DATA_PREFIX.len() + 1 + entity.len() + 1 + id.len());
17    key.extend_from_slice(DATA_PREFIX);
18    key.push(SEPARATOR);
19    key.extend_from_slice(entity.as_bytes());
20    key.push(SEPARATOR);
21    key.extend_from_slice(id.as_bytes());
22    key
23}
24
25/// # Errors
26/// Returns an error if the key is not a valid data key.
27pub fn decode_data_key(key: &[u8]) -> Result<(String, String)> {
28    let parts: Vec<&[u8]> = key.split(|&b| b == SEPARATOR).collect();
29
30    if parts.len() != 3 || parts[0] != DATA_PREFIX {
31        return Err(Error::InvalidKey(format!("invalid data key: {key:?}")));
32    }
33
34    let entity = String::from_utf8(parts[1].to_vec())
35        .map_err(|_| Error::InvalidKey("entity not valid UTF-8".into()))?;
36    let id = String::from_utf8(parts[2].to_vec())
37        .map_err(|_| Error::InvalidKey("id not valid UTF-8".into()))?;
38
39    Ok((entity, id))
40}
41
42#[must_use]
43pub fn encode_index_key(entity: &str, field: &str, value: &[u8], id: &str) -> Vec<u8> {
44    let mut key = Vec::with_capacity(
45        INDEX_PREFIX.len() + 1 + entity.len() + 1 + field.len() + 1 + value.len() + 1 + id.len(),
46    );
47    key.extend_from_slice(INDEX_PREFIX);
48    key.push(SEPARATOR);
49    key.extend_from_slice(entity.as_bytes());
50    key.push(SEPARATOR);
51    key.extend_from_slice(field.as_bytes());
52    key.push(SEPARATOR);
53    key.extend_from_slice(value);
54    key.push(SEPARATOR);
55    key.extend_from_slice(id.as_bytes());
56    key
57}
58
59#[must_use]
60pub fn encode_index_prefix(entity: &str, field: &str, value: Option<&[u8]>) -> Vec<u8> {
61    let mut key = Vec::new();
62    key.extend_from_slice(INDEX_PREFIX);
63    key.push(SEPARATOR);
64    key.extend_from_slice(entity.as_bytes());
65    key.push(SEPARATOR);
66    key.extend_from_slice(field.as_bytes());
67
68    if let Some(v) = value {
69        key.push(SEPARATOR);
70        key.extend_from_slice(v);
71    }
72
73    key
74}
75
76#[must_use]
77pub fn encode_subscription_key(sub_id: &str) -> Vec<u8> {
78    let mut key = Vec::with_capacity(SUB_PREFIX.len() + 1 + sub_id.len());
79    key.extend_from_slice(SUB_PREFIX);
80    key.push(SEPARATOR);
81    key.extend_from_slice(sub_id.as_bytes());
82    key
83}
84
85#[must_use]
86pub fn encode_dedup_key(correlation_id: &str) -> Vec<u8> {
87    let mut key = Vec::with_capacity(DEDUP_PREFIX.len() + 1 + correlation_id.len());
88    key.extend_from_slice(DEDUP_PREFIX);
89    key.push(SEPARATOR);
90    key.extend_from_slice(correlation_id.as_bytes());
91    key
92}
93
94#[must_use]
95pub fn encode_meta_key(key_name: &str) -> Vec<u8> {
96    let mut key = Vec::with_capacity(META_PREFIX.len() + 1 + key_name.len());
97    key.extend_from_slice(META_PREFIX);
98    key.push(SEPARATOR);
99    key.extend_from_slice(key_name.as_bytes());
100    key
101}
102
103#[must_use]
104fn encode_i64_sortable(val: i64) -> [u8; 8] {
105    let bits = val.to_be_bytes();
106    let mut out = bits;
107    out[0] ^= 0x80;
108    out
109}
110
111#[must_use]
112fn encode_f64_sortable(val: f64) -> [u8; 8] {
113    let bits = val.to_bits().to_be_bytes();
114    let mut out = bits;
115    if val.is_sign_negative() {
116        for b in &mut out {
117            *b ^= 0xFF;
118        }
119    } else {
120        out[0] ^= 0x80;
121    }
122    out
123}
124
125/// # Errors
126/// Returns an error if the value cannot be indexed.
127pub fn encode_value_for_index(value: &serde_json::Value) -> Result<Vec<u8>> {
128    match value {
129        serde_json::Value::Null => Ok(b"null".to_vec()),
130        serde_json::Value::Bool(b) => {
131            if *b {
132                Ok(b"true".to_vec())
133            } else {
134                Ok(b"false".to_vec())
135            }
136        }
137        serde_json::Value::Number(n) => {
138            if let Some(i) = n.as_i64() {
139                Ok(encode_i64_sortable(i).to_vec())
140            } else if let Some(f) = n.as_f64() {
141                Ok(encode_f64_sortable(f).to_vec())
142            } else {
143                Ok(n.to_string().into_bytes())
144            }
145        }
146        serde_json::Value::String(s) => Ok(s.as_bytes().to_vec()),
147        _ => Err(Error::Validation(
148            "cannot index arrays or objects directly".into(),
149        )),
150    }
151}
152
153#[must_use]
154pub fn encode_index_definition_key(entity: &str) -> Vec<u8> {
155    format!("meta/index/{entity}").into_bytes()
156}
157
158#[must_use]
159pub fn encode_schema_key(entity: &str) -> Vec<u8> {
160    format!("meta/schema/{entity}").into_bytes()
161}
162
163#[must_use]
164pub fn encode_constraint_key(constraint_type: &str, entity: &str, name: &str) -> Vec<u8> {
165    format!("meta/constraint/{constraint_type}/{entity}/{name}").into_bytes()
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn test_encode_decode_data_key() {
174        let key = encode_data_key("users", "123");
175        assert_eq!(key, b"data/users/123");
176
177        let (entity, id) = decode_data_key(&key).unwrap();
178        assert_eq!(entity, "users");
179        assert_eq!(id, "123");
180    }
181
182    #[test]
183    fn test_encode_index_key() {
184        let key = encode_index_key("users", "email", b"test@example.com", "123");
185        assert_eq!(key, b"idx/users/email/test@example.com/123");
186    }
187
188    #[test]
189    fn test_encode_value_for_index() {
190        let val = serde_json::json!("hello");
191        let encoded = encode_value_for_index(&val).unwrap();
192        assert_eq!(encoded, b"hello");
193    }
194
195    #[test]
196    fn i64_encoding_preserves_sort_order() {
197        let values: &[i64] = &[i64::MIN, -1000, -1, 0, 1, 1000, i64::MAX];
198        let encoded: Vec<[u8; 8]> = values.iter().map(|v| encode_i64_sortable(*v)).collect();
199        for pair in encoded.windows(2) {
200            assert!(
201                pair[0] < pair[1],
202                "{:?} should sort before {:?}",
203                pair[0],
204                pair[1]
205            );
206        }
207    }
208
209    #[test]
210    fn f64_encoding_preserves_sort_order() {
211        let values: &[f64] = &[
212            f64::NEG_INFINITY,
213            -1e10,
214            -1.0,
215            -0.001,
216            0.0,
217            0.001,
218            1.0,
219            1e10,
220            f64::INFINITY,
221        ];
222        let encoded: Vec<[u8; 8]> = values.iter().map(|v| encode_f64_sortable(*v)).collect();
223        for pair in encoded.windows(2) {
224            assert!(
225                pair[0] < pair[1],
226                "{:?} should sort before {:?}",
227                pair[0],
228                pair[1]
229            );
230        }
231    }
232
233    #[test]
234    fn test_encode_index_definition_key() {
235        let key = encode_index_definition_key("users");
236        assert_eq!(key, b"meta/index/users");
237    }
238
239    #[test]
240    fn negative_integers_sort_before_positive_in_index() {
241        let neg = encode_value_for_index(&serde_json::json!(-5)).unwrap();
242        let zero = encode_value_for_index(&serde_json::json!(0)).unwrap();
243        let pos = encode_value_for_index(&serde_json::json!(5)).unwrap();
244        assert!(neg < zero);
245        assert!(zero < pos);
246    }
247}