nodedb_array/codec/
column_codec.rs1use nodedb_codec::error::CodecError;
12use nodedb_types::Surrogate;
13
14use crate::codec::limits::{MAX_COLUMN_ENTRIES, checked_capacity};
15use crate::error::{ArrayError, ArrayResult};
16use crate::types::cell_value::value::CellValue;
17
18fn codec_err(e: CodecError) -> ArrayError {
23 ArrayError::SegmentCorruption {
24 detail: format!("codec error: {e}"),
25 }
26}
27
28pub fn encode_surrogates(surrogates: &[Surrogate]) -> ArrayResult<Vec<u8>> {
33 let as_i64: Vec<i64> = surrogates.iter().map(|s| s.as_u32() as i64).collect();
34 nodedb_codec::fastlanes::encode(&as_i64).map_err(codec_err)
35}
36
37pub fn decode_surrogates(data: &[u8]) -> ArrayResult<Vec<Surrogate>> {
38 let as_i64 = nodedb_codec::fastlanes::decode(data).map_err(codec_err)?;
39 Ok(as_i64
40 .into_iter()
41 .map(|v| Surrogate::new(v as u32))
42 .collect())
43}
44
45pub fn encode_row_kinds(row_kinds: &[u8]) -> Vec<u8> {
50 let mut out = Vec::with_capacity(4 + row_kinds.len());
51 out.extend_from_slice(&(row_kinds.len() as u32).to_le_bytes());
52 out.extend_from_slice(row_kinds);
53 out
54}
55
56pub fn decode_row_kinds(data: &[u8]) -> ArrayResult<Vec<u8>> {
57 if data.len() < 4 {
58 return Err(ArrayError::SegmentCorruption {
59 detail: "row_kinds: truncated count".into(),
60 });
61 }
62 let count = u32::from_le_bytes(
63 data[0..4]
64 .try_into()
65 .expect("invariant: bounds-checked above (data.len() >= 4)"),
66 ) as usize;
67 if data.len() < 4 + count {
68 return Err(ArrayError::SegmentCorruption {
69 detail: "row_kinds: truncated body".into(),
70 });
71 }
72 Ok(data[4..4 + count].to_vec())
73}
74
75pub fn encode_timestamps_col(timestamps: &[i64]) -> Vec<u8> {
80 nodedb_codec::gorilla::encode_timestamps(timestamps)
81}
82
83pub fn decode_timestamps_col(data: &[u8]) -> ArrayResult<Vec<i64>> {
84 nodedb_codec::gorilla::decode_timestamps(data).map_err(codec_err)
85}
86
87const ATTR_TAG_INT64: u8 = 0;
93const ATTR_TAG_FLOAT64: u8 = 1;
94const ATTR_TAG_MSGPACK: u8 = 2; pub fn encode_attr_col(values: &[CellValue]) -> ArrayResult<Vec<u8>> {
97 if values.is_empty() {
98 let mut out = vec![ATTR_TAG_MSGPACK];
99 out.extend_from_slice(&0u32.to_le_bytes());
100 return Ok(out);
101 }
102
103 let all_int = values
105 .iter()
106 .all(|v| matches!(v, CellValue::Int64(_) | CellValue::Null));
107 let all_float = values
108 .iter()
109 .all(|v| matches!(v, CellValue::Float64(_) | CellValue::Null));
110
111 if all_int {
112 let ints: Vec<i64> = values
113 .iter()
114 .map(|v| match v {
115 CellValue::Int64(i) => *i,
116 _ => 0,
117 })
118 .collect();
119 let encoded = nodedb_codec::fastlanes::encode(&ints).map_err(codec_err)?;
120 let mut out = vec![ATTR_TAG_INT64];
121 out.extend_from_slice(&encoded);
122 return Ok(out);
123 }
124
125 if all_float {
126 let floats: Vec<f64> = values
131 .iter()
132 .map(|v| match v {
133 CellValue::Float64(f) => *f,
134 _ => 0.0,
135 })
136 .collect();
137 let encoded = nodedb_codec::gorilla::encode_f64(&floats);
138 let mut out = vec![ATTR_TAG_FLOAT64];
139 out.extend_from_slice(&encoded);
140 return Ok(out);
141 }
142
143 let mut out = vec![ATTR_TAG_MSGPACK];
145 out.extend_from_slice(&(values.len() as u32).to_le_bytes());
146 for v in values {
147 let bytes = zerompk::to_msgpack_vec(v).map_err(|e| ArrayError::SegmentCorruption {
148 detail: format!("attr col encode: {e}"),
149 })?;
150 out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
151 out.extend_from_slice(&bytes);
152 }
153 Ok(out)
154}
155
156pub fn decode_attr_col(data: &[u8]) -> ArrayResult<Vec<CellValue>> {
157 if data.is_empty() {
158 return Err(ArrayError::SegmentCorruption {
159 detail: "attr col: empty payload".into(),
160 });
161 }
162 let tag = data[0];
163 let body = &data[1..];
164
165 match tag {
166 ATTR_TAG_INT64 => {
167 let ints = nodedb_codec::fastlanes::decode(body).map_err(codec_err)?;
168 Ok(ints.into_iter().map(CellValue::Int64).collect())
169 }
170 ATTR_TAG_FLOAT64 => {
171 let floats = nodedb_codec::gorilla::decode_f64(body).map_err(codec_err)?;
172 Ok(floats.into_iter().map(CellValue::Float64).collect())
173 }
174 ATTR_TAG_MSGPACK => {
175 if body.len() < 4 {
176 return Err(ArrayError::SegmentCorruption {
177 detail: "attr col msgpack: truncated count".into(),
178 });
179 }
180 let count = u32::from_le_bytes(
181 body[0..4]
182 .try_into()
183 .expect("invariant: bounds-checked above (body.len() >= 4)"),
184 ) as usize;
185 let capacity = checked_capacity(
186 count,
187 MAX_COLUMN_ENTRIES,
188 body.len() - 4,
189 4,
190 "attr_col_msgpack count",
191 )?;
192 let mut pos = 4;
193 let mut values = Vec::with_capacity(capacity);
194 for _ in 0..count {
195 if pos + 4 > body.len() {
196 return Err(ArrayError::SegmentCorruption {
197 detail: "attr col msgpack: truncated entry len".into(),
198 });
199 }
200 let len = u32::from_le_bytes(
201 body[pos..pos + 4]
202 .try_into()
203 .expect("invariant: bounds-checked above (pos + 4 <= body.len())"),
204 ) as usize;
205 pos += 4;
206 if pos + len > body.len() {
207 return Err(ArrayError::SegmentCorruption {
208 detail: "attr col msgpack: truncated entry bytes".into(),
209 });
210 }
211 let v: CellValue = zerompk::from_msgpack(&body[pos..pos + len]).map_err(|e| {
212 ArrayError::SegmentCorruption {
213 detail: format!("attr col decode: {e}"),
214 }
215 })?;
216 pos += len;
217 values.push(v);
218 }
219 Ok(values)
220 }
221 other => Err(ArrayError::SegmentCorruption {
222 detail: format!("attr col: unknown tag {other:#04x}"),
223 }),
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 fn encode_surrogates(values: &[Surrogate]) -> Vec<u8> {
232 super::encode_surrogates(values).expect("test surrogate encode")
233 }
234
235 #[test]
236 fn surrogates_empty_roundtrip() {
237 let data = encode_surrogates(&[]);
238 let out = decode_surrogates(&data).unwrap();
239 assert!(out.is_empty());
240 }
241
242 #[test]
243 fn surrogates_roundtrip() {
244 let vals = vec![
245 Surrogate::new(0),
246 Surrogate::new(1),
247 Surrogate::new(1000),
248 Surrogate::new(9999),
249 ];
250 let data = encode_surrogates(&vals);
251 let out = decode_surrogates(&data).unwrap();
252 assert_eq!(out, vals);
253 }
254
255 #[test]
256 fn row_kinds_roundtrip() {
257 let kinds = vec![0u8, 1, 2, 0, 0, 1];
258 let data = encode_row_kinds(&kinds);
259 let out = decode_row_kinds(&data).unwrap();
260 assert_eq!(out, kinds);
261 }
262
263 #[test]
264 fn row_kinds_empty_roundtrip() {
265 let data = encode_row_kinds(&[]);
266 let out = decode_row_kinds(&data).unwrap();
267 assert!(out.is_empty());
268 }
269
270 #[test]
271 fn timestamps_roundtrip() {
272 let ts = vec![1_000_000i64, 1_001_000, 1_002_000, 1_100_000];
273 let data = encode_timestamps_col(&ts);
274 let out = decode_timestamps_col(&data).unwrap();
275 assert_eq!(out, ts);
276 }
277
278 #[test]
279 fn attr_col_int64_roundtrip() {
280 let vals = vec![
281 CellValue::Int64(10),
282 CellValue::Int64(-5),
283 CellValue::Int64(0),
284 ];
285 let data = encode_attr_col(&vals).unwrap();
286 let out = decode_attr_col(&data).unwrap();
287 assert_eq!(out, vals);
288 }
289
290 #[test]
291 fn attr_col_float64_roundtrip() {
292 let vals = vec![CellValue::Float64(1.5), CellValue::Float64(-2.5)];
293 let data = encode_attr_col(&vals).unwrap();
294 let out = decode_attr_col(&data).unwrap();
295 assert_eq!(out, vals);
296 }
297
298 #[test]
299 fn attr_col_string_roundtrip() {
300 let vals = vec![
301 CellValue::String("hello".into()),
302 CellValue::String("world".into()),
303 ];
304 let data = encode_attr_col(&vals).unwrap();
305 let out = decode_attr_col(&data).unwrap();
306 assert_eq!(out, vals);
307 }
308
309 #[test]
310 fn huge_msgpack_count_in_tiny_payload_is_rejected_before_allocation() {
311 let mut data = vec![ATTR_TAG_MSGPACK];
312 data.extend_from_slice(&(MAX_COLUMN_ENTRIES as u32).to_le_bytes());
313 let result = decode_attr_col(&data);
314 assert!(matches!(
315 result,
316 Err(crate::error::ArrayError::SegmentCorruption { .. })
317 ));
318 }
319
320 #[test]
321 fn attr_col_empty_roundtrip() {
322 let data = encode_attr_col(&[]).unwrap();
323 let out = decode_attr_col(&data).unwrap();
324 assert!(out.is_empty());
325 }
326
327 #[test]
328 fn attr_col_mixed_types_roundtrip() {
329 let vals = vec![
330 CellValue::String("x".into()),
331 CellValue::Null,
332 CellValue::Bytes(vec![1, 2, 3]),
333 ];
334 let data = encode_attr_col(&vals).unwrap();
335 let out = decode_attr_col(&data).unwrap();
336 assert_eq!(out, vals);
337 }
338
339 #[test]
340 fn surrogates_large_roundtrip() {
341 let vals: Vec<Surrogate> = (0u32..1000).map(|i| Surrogate::new(i * 7)).collect();
342 let data = encode_surrogates(&vals);
343 let out = decode_surrogates(&data).unwrap();
344 assert_eq!(out, vals);
345 }
346}