Skip to main content

reddb_engine/storage/
metadata_codec.rs

1//! Canonical native metadata bytes and entity-record adapter, shared with WASM.
2use super::entity::{EntityId, UnifiedEntity};
3use super::metadata::{Metadata, MetadataValue};
4
5fn read_entity(data: &[u8], version: u32) -> Result<UnifiedEntity, String> {
6    super::entity_codec::read_entity_binary(data, &mut 0, version).map_err(invalid)
7}
8
9fn invalid(error: impl std::fmt::Display) -> String {
10    error.to_string()
11}
12
13fn read_count(data: &[u8], position: &mut usize, bytes_min: usize) -> Result<usize, String> {
14    let count = usize::try_from(read_u32(data, position)?).map_err(invalid)?;
15    if count > data.len().saturating_sub(*position) / bytes_min {
16        return Err(invalid("metadata count exceeds the remaining payload"));
17    }
18    Ok(count)
19}
20
21/// Preserve native entity/value and optional typed metadata framing.
22pub fn serialize_entity_record(
23    entity: &UnifiedEntity,
24    metadata: Option<&Metadata>,
25    version: u32,
26) -> Vec<u8> {
27    let mut bytes = Vec::with_capacity(256);
28    super::entity_codec::write_entity_binary(&mut bytes, entity, version);
29    let entity = bytes;
30    if metadata.is_some_and(|metadata| !metadata.fields.is_empty()) {
31        reddb_file::encode_native_entity_record_frame(&entity, Some(&serialize_metadata(metadata)))
32    } else {
33        reddb_file::encode_native_entity_record_frame(&entity, None)
34    }
35}
36
37/// Decode the existing framed record, retaining the pre-V7 entity-only fallback.
38pub fn deserialize_entity_record(
39    data: &[u8],
40    version: u32,
41) -> Result<(UnifiedEntity, Option<Metadata>), String> {
42    let Some(frame) = reddb_file::decode_native_entity_record_frame(data).map_err(invalid)? else {
43        return read_entity(data, version).map(|entity| (entity, None));
44    };
45    let entity = read_entity(frame.entity, version)?;
46    let metadata = if frame.metadata.is_empty() {
47        None
48    } else {
49        let metadata = deserialize_metadata(frame.metadata)?;
50        (!metadata.is_empty()).then_some(metadata)
51    };
52    Ok((entity, metadata))
53}
54
55pub fn serialize_metadata(metadata: Option<&Metadata>) -> Vec<u8> {
56    let Some(metadata) = metadata else {
57        return Vec::new();
58    };
59    if metadata.is_empty() {
60        return Vec::new();
61    }
62
63    let mut entries: Vec<_> = metadata.iter().collect();
64    entries.sort_by_key(|(a, _)| *a);
65
66    let mut buf = Vec::new();
67    buf.extend_from_slice(&(entries.len() as u32).to_le_bytes());
68    for (key, value) in entries {
69        write_string(&mut buf, key);
70        write_metadata_value(&mut buf, value);
71    }
72    buf
73}
74
75pub fn deserialize_metadata(data: &[u8]) -> Result<Metadata, String> {
76    let mut pos = 0usize;
77    let count = read_count(data, &mut pos, 5)?;
78    let mut metadata = Metadata::new();
79    for _ in 0..count {
80        let key = read_string(data, &mut pos)?;
81        let value = read_metadata_value(data, &mut pos, 0)?;
82        metadata.set(key, value);
83    }
84    Ok(metadata)
85}
86
87fn write_string(buf: &mut Vec<u8>, value: &str) {
88    reddb_file::encode_native_len_prefixed_str(buf, value);
89}
90
91fn write_bytes(buf: &mut Vec<u8>, value: &[u8]) {
92    reddb_file::encode_native_len_prefixed_bytes(buf, value);
93}
94
95fn write_ref_target(buf: &mut Vec<u8>, target: &super::metadata::RefTarget) {
96    use super::metadata::RefTarget;
97
98    match target {
99        RefTarget::TableRow { table, row_id } => {
100            buf.push(0);
101            write_string(buf, table);
102            buf.extend_from_slice(&row_id.to_le_bytes());
103        }
104        RefTarget::Node {
105            collection,
106            node_id,
107        } => {
108            buf.push(1);
109            write_string(buf, collection);
110            buf.extend_from_slice(&node_id.raw().to_le_bytes());
111        }
112        RefTarget::Edge {
113            collection,
114            edge_id,
115        } => {
116            buf.push(2);
117            write_string(buf, collection);
118            buf.extend_from_slice(&edge_id.raw().to_le_bytes());
119        }
120        RefTarget::Vector {
121            collection,
122            vector_id,
123        } => {
124            buf.push(3);
125            write_string(buf, collection);
126            buf.extend_from_slice(&vector_id.raw().to_le_bytes());
127        }
128        RefTarget::Entity {
129            collection,
130            entity_id,
131        } => {
132            buf.push(4);
133            write_string(buf, collection);
134            buf.extend_from_slice(&entity_id.raw().to_le_bytes());
135        }
136    }
137}
138
139fn write_metadata_value(buf: &mut Vec<u8>, value: &MetadataValue) {
140    match value {
141        MetadataValue::Null => buf.push(0),
142        MetadataValue::Bool(v) => {
143            buf.push(1);
144            buf.push(u8::from(*v));
145        }
146        MetadataValue::Int(v) => {
147            buf.push(2);
148            buf.extend_from_slice(&v.to_le_bytes());
149        }
150        MetadataValue::Float(v) => {
151            buf.push(3);
152            buf.extend_from_slice(&v.to_le_bytes());
153        }
154        MetadataValue::String(v) => {
155            buf.push(4);
156            write_string(buf, v);
157        }
158        MetadataValue::Bytes(v) => {
159            buf.push(5);
160            write_bytes(buf, v);
161        }
162        MetadataValue::Array(values) => {
163            buf.push(6);
164            buf.extend_from_slice(&(values.len() as u32).to_le_bytes());
165            for value in values {
166                write_metadata_value(buf, value);
167            }
168        }
169        MetadataValue::Object(values) => {
170            buf.push(7);
171            let mut entries: Vec<_> = values.iter().collect();
172            entries.sort_by_key(|(a, _)| *a);
173            buf.extend_from_slice(&(entries.len() as u32).to_le_bytes());
174            for (key, value) in entries {
175                write_string(buf, key);
176                write_metadata_value(buf, value);
177            }
178        }
179        MetadataValue::Timestamp(v) => {
180            buf.push(8);
181            buf.extend_from_slice(&v.to_le_bytes());
182        }
183        MetadataValue::Geo { lat, lon } => {
184            buf.push(9);
185            buf.extend_from_slice(&lat.to_le_bytes());
186            buf.extend_from_slice(&lon.to_le_bytes());
187        }
188        MetadataValue::Reference(target) => {
189            buf.push(10);
190            write_ref_target(buf, target);
191        }
192        MetadataValue::References(targets) => {
193            buf.push(11);
194            buf.extend_from_slice(&(targets.len() as u32).to_le_bytes());
195            for target in targets {
196                write_ref_target(buf, target);
197            }
198        }
199    }
200}
201
202fn read_exact_slice<'a>(data: &'a [u8], pos: &mut usize, len: usize) -> Result<&'a [u8], String> {
203    if *pos > data.len() || len > data.len() - *pos {
204        return Err(invalid("truncated metadata payload".to_string()));
205    }
206    let slice = &data[*pos..*pos + len];
207    *pos += len;
208    Ok(slice)
209}
210
211fn read_u32(data: &[u8], pos: &mut usize) -> Result<u32, String> {
212    let bytes = read_exact_slice(data, pos, 4)?;
213    let mut raw = [0u8; 4];
214    raw.copy_from_slice(bytes);
215    Ok(u32::from_le_bytes(raw))
216}
217
218fn read_u64(data: &[u8], pos: &mut usize) -> Result<u64, String> {
219    let bytes = read_exact_slice(data, pos, 8)?;
220    let mut raw = [0u8; 8];
221    raw.copy_from_slice(bytes);
222    Ok(u64::from_le_bytes(raw))
223}
224
225fn read_i64(data: &[u8], pos: &mut usize) -> Result<i64, String> {
226    let bytes = read_exact_slice(data, pos, 8)?;
227    let mut raw = [0u8; 8];
228    raw.copy_from_slice(bytes);
229    Ok(i64::from_le_bytes(raw))
230}
231
232fn read_f64(data: &[u8], pos: &mut usize) -> Result<f64, String> {
233    let bytes = read_exact_slice(data, pos, 8)?;
234    let mut raw = [0u8; 8];
235    raw.copy_from_slice(bytes);
236    Ok(f64::from_le_bytes(raw))
237}
238
239fn read_u8(data: &[u8], pos: &mut usize) -> Result<u8, String> {
240    let bytes = read_exact_slice(data, pos, 1)?;
241    Ok(bytes[0])
242}
243
244fn read_string(data: &[u8], pos: &mut usize) -> Result<String, String> {
245    reddb_file::decode_native_len_prefixed_string(data, pos).map_err(|err| invalid(err.to_string()))
246}
247
248fn read_bytes(data: &[u8], pos: &mut usize) -> Result<Vec<u8>, String> {
249    reddb_file::decode_native_len_prefixed_bytes(data, pos)
250        .map(|bytes| bytes.to_vec())
251        .map_err(|err| invalid(err.to_string()))
252}
253
254fn read_ref_target(data: &[u8], pos: &mut usize) -> Result<super::metadata::RefTarget, String> {
255    use super::metadata::RefTarget;
256
257    match read_u8(data, pos)? {
258        0 => Ok(RefTarget::TableRow {
259            table: read_string(data, pos)?,
260            row_id: read_u64(data, pos)?,
261        }),
262        1 => Ok(RefTarget::Node {
263            collection: read_string(data, pos)?,
264            node_id: EntityId::new(read_u64(data, pos)?),
265        }),
266        2 => Ok(RefTarget::Edge {
267            collection: read_string(data, pos)?,
268            edge_id: EntityId::new(read_u64(data, pos)?),
269        }),
270        3 => Ok(RefTarget::Vector {
271            collection: read_string(data, pos)?,
272            vector_id: EntityId::new(read_u64(data, pos)?),
273        }),
274        4 => Ok(RefTarget::Entity {
275            collection: read_string(data, pos)?,
276            entity_id: EntityId::new(read_u64(data, pos)?),
277        }),
278        tag => Err(invalid(format!("unknown metadata ref target tag {tag}"))),
279    }
280}
281
282fn read_metadata_value(data: &[u8], pos: &mut usize, depth: u32) -> Result<MetadataValue, String> {
283    if depth > reddb_rql::limits::JSON_LITERAL_MAX_DEPTH {
284        return Err(invalid("metadata nesting exceeds the maximum depth"));
285    }
286    match read_u8(data, pos)? {
287        0 => Ok(MetadataValue::Null),
288        1 => Ok(MetadataValue::Bool(read_u8(data, pos)? != 0)),
289        2 => Ok(MetadataValue::Int(read_i64(data, pos)?)),
290        3 => Ok(MetadataValue::Float(read_f64(data, pos)?)),
291        4 => Ok(MetadataValue::String(read_string(data, pos)?)),
292        5 => Ok(MetadataValue::Bytes(read_bytes(data, pos)?)),
293        6 => {
294            let count = read_count(data, pos, 1)?;
295            let mut values = Vec::with_capacity(count);
296            for _ in 0..count {
297                values.push(read_metadata_value(data, pos, depth + 1)?);
298            }
299            Ok(MetadataValue::Array(values))
300        }
301        7 => {
302            let count = read_count(data, pos, 5)?;
303            let mut values = std::collections::HashMap::with_capacity(count);
304            for _ in 0..count {
305                let key = read_string(data, pos)?;
306                let value = read_metadata_value(data, pos, depth + 1)?;
307                values.insert(key, value);
308            }
309            Ok(MetadataValue::Object(values))
310        }
311        8 => Ok(MetadataValue::Timestamp(read_u64(data, pos)?)),
312        9 => Ok(MetadataValue::Geo {
313            lat: read_f64(data, pos)?,
314            lon: read_f64(data, pos)?,
315        }),
316        10 => Ok(MetadataValue::Reference(read_ref_target(data, pos)?)),
317        11 => {
318            let count = read_count(data, pos, 13)?;
319            let mut targets = Vec::with_capacity(count);
320            for _ in 0..count {
321                targets.push(read_ref_target(data, pos)?);
322            }
323            Ok(MetadataValue::References(targets))
324        }
325        tag => Err(invalid(format!("unknown metadata value tag {tag}"))),
326    }
327}