Skip to main content

nodedb_query/msgpack_scan/
writer.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Msgpack scalar writers for constructing response messages, aggregate results,
4//! projections, and other output data — all in raw msgpack without intermediary types.
5//!
6//! These are the building blocks for zero-serialization response construction.
7//! Follow the timeseries ingest pattern: `SqlValue → row_to_msgpack()` at ingress,
8//! raw msgpack throughout, `msgpack_to_json_string()` at outermost pgwire/HTTP layer.
9
10use nodedb_types::InstantKind;
11
12/// Write a msgpack map header.
13#[inline]
14pub fn write_map_header(buf: &mut Vec<u8>, len: usize) {
15    if len < 16 {
16        buf.push(0x80 | len as u8);
17    } else if len <= u16::MAX as usize {
18        buf.push(0xDE);
19        buf.extend_from_slice(&(len as u16).to_be_bytes());
20    } else {
21        buf.push(0xDF);
22        buf.extend_from_slice(&(len as u32).to_be_bytes());
23    }
24}
25
26/// Write a msgpack array header.
27#[inline]
28pub fn write_array_header(buf: &mut Vec<u8>, len: usize) {
29    if len < 16 {
30        buf.push(0x90 | len as u8);
31    } else if len <= u16::MAX as usize {
32        buf.push(0xDC);
33        buf.extend_from_slice(&(len as u16).to_be_bytes());
34    } else {
35        buf.push(0xDD);
36        buf.extend_from_slice(&(len as u32).to_be_bytes());
37    }
38}
39
40/// Write a msgpack string (header + UTF-8 bytes).
41#[inline]
42pub fn write_str(buf: &mut Vec<u8>, s: &str) {
43    let bytes = s.as_bytes();
44    let len = bytes.len();
45    if len < 32 {
46        buf.push(0xA0 | len as u8);
47    } else if len <= u8::MAX as usize {
48        buf.push(0xD9);
49        buf.push(len as u8);
50    } else if len <= u16::MAX as usize {
51        buf.push(0xDA);
52        buf.extend_from_slice(&(len as u16).to_be_bytes());
53    } else {
54        buf.push(0xDB);
55        buf.extend_from_slice(&(len as u32).to_be_bytes());
56    }
57    buf.extend_from_slice(bytes);
58}
59
60/// Write a msgpack integer using the most compact encoding.
61#[inline]
62pub fn write_i64(buf: &mut Vec<u8>, i: i64) {
63    if (0..=127).contains(&i) {
64        buf.push(i as u8);
65    } else if (-32..0).contains(&i) {
66        buf.push(i as u8); // negative fixint
67    } else if i >= i8::MIN as i64 && i <= i8::MAX as i64 {
68        buf.push(0xD0);
69        buf.push(i as i8 as u8);
70    } else if i >= i16::MIN as i64 && i <= i16::MAX as i64 {
71        buf.push(0xD1);
72        buf.extend_from_slice(&(i as i16).to_be_bytes());
73    } else if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
74        buf.push(0xD2);
75        buf.extend_from_slice(&(i as i32).to_be_bytes());
76    } else {
77        buf.push(0xD3);
78        buf.extend_from_slice(&i.to_be_bytes());
79    }
80}
81
82/// Write a msgpack float64.
83#[inline]
84pub fn write_f64(buf: &mut Vec<u8>, f: f64) {
85    buf.push(0xCB);
86    buf.extend_from_slice(&f.to_be_bytes());
87}
88
89/// Write a msgpack boolean.
90#[inline]
91pub fn write_bool(buf: &mut Vec<u8>, b: bool) {
92    buf.push(if b { 0xC3 } else { 0xC2 });
93}
94
95/// Write a msgpack null.
96#[inline]
97pub fn write_null(buf: &mut Vec<u8>) {
98    buf.push(0xC0);
99}
100
101/// Write a typed instant as the ten-byte instant ext (`fixext8` type 1 / 2).
102#[inline]
103pub fn write_instant(buf: &mut Vec<u8>, kind: InstantKind, micros: i64) {
104    nodedb_types::write_instant(buf, kind, micros);
105}
106
107/// Write a msgpack binary blob (bin 8/16/32).
108#[inline]
109pub fn write_bin(buf: &mut Vec<u8>, data: &[u8]) {
110    let len = data.len();
111    if len <= u8::MAX as usize {
112        buf.push(0xC4);
113        buf.push(len as u8);
114    } else if len <= u16::MAX as usize {
115        buf.push(0xC5);
116        buf.extend_from_slice(&(len as u16).to_be_bytes());
117    } else {
118        buf.push(0xC6);
119        buf.extend_from_slice(&(len as u32).to_be_bytes());
120    }
121    buf.extend_from_slice(data);
122}
123
124/// Write a key-value pair into a msgpack map being built.
125///
126/// Caller is responsible for writing the map header first via [`write_map_header`].
127#[inline]
128pub fn write_kv_str(buf: &mut Vec<u8>, key: &str, value: &str) {
129    write_str(buf, key);
130    write_str(buf, value);
131}
132
133/// Write a key-value pair (string key, i64 value).
134#[inline]
135pub fn write_kv_i64(buf: &mut Vec<u8>, key: &str, value: i64) {
136    write_str(buf, key);
137    write_i64(buf, value);
138}
139
140/// Write a key-value pair (string key, f64 value).
141#[inline]
142pub fn write_kv_f64(buf: &mut Vec<u8>, key: &str, value: f64) {
143    write_str(buf, key);
144    write_f64(buf, value);
145}
146
147/// Write a key-value pair (string key, bool value).
148#[inline]
149pub fn write_kv_bool(buf: &mut Vec<u8>, key: &str, value: bool) {
150    write_str(buf, key);
151    write_bool(buf, value);
152}
153
154/// Write a key-value pair (string key, raw msgpack value bytes).
155///
156/// The value bytes must be valid msgpack. This enables splicing raw
157/// field bytes extracted via `msgpack_scan::extract_field` directly
158/// into a new map without decode/re-encode.
159#[inline]
160pub fn write_kv_raw(buf: &mut Vec<u8>, key: &str, raw_value: &[u8]) {
161    write_str(buf, key);
162    buf.extend_from_slice(raw_value);
163}
164
165/// Write a key-value pair (string key, null value).
166#[inline]
167pub fn write_kv_null(buf: &mut Vec<u8>, key: &str) {
168    write_str(buf, key);
169    write_null(buf);
170}
171
172/// Write a key-value pair (string key, instant value).
173#[inline]
174pub fn write_kv_instant(buf: &mut Vec<u8>, key: &str, kind: InstantKind, micros: i64) {
175    write_str(buf, key);
176    write_instant(buf, kind, micros);
177}
178
179/// Inject a string field into a msgpack map without full decode.
180///
181/// If `value` is a valid msgpack map that already contains `field_name`,
182/// the existing value is preserved and the input is returned unchanged —
183/// the caller's body is authoritative for its own primary key. If the map
184/// does not contain the field, it is prepended. If `value` is not a map,
185/// wraps as `{field_name: field_value, "value": raw}`, with `raw` encoded as
186/// a msgpack STRING: appending non-msgpack bytes verbatim produces a body a
187/// decoder reads as one scalar and silently truncates (`"v1"` becomes the
188/// integer 118), which is worse than an error because it reports success.
189/// Callers holding a genuine map never reach this branch.
190pub fn inject_str_field(value: &[u8], field_name: &str, field_value: &str) -> Vec<u8> {
191    if let Some((count, body_start)) = crate::msgpack_scan::reader::map_header(value, 0) {
192        if crate::msgpack_scan::extract_field(value, 0, field_name).is_some() {
193            return value.to_vec();
194        }
195        let mut buf = Vec::with_capacity(value.len() + field_name.len() + field_value.len() + 16);
196        write_map_header(&mut buf, count + 1);
197        write_kv_str(&mut buf, field_name, field_value);
198        buf.extend_from_slice(&value[body_start..]);
199        buf
200    } else {
201        let mut buf = Vec::with_capacity(value.len() + field_name.len() + field_value.len() + 16);
202        write_map_header(&mut buf, 2);
203        write_kv_str(&mut buf, field_name, field_value);
204        write_str(&mut buf, "value");
205        write_str(&mut buf, &String::from_utf8_lossy(value));
206        buf
207    }
208}
209
210/// Merge field updates into a msgpack map without full decode.
211///
212/// Takes a base msgpack map and a list of `(field_name, raw_msgpack_value)` updates.
213/// Returns a new msgpack map with updated fields replaced and new fields appended.
214/// Fields not in `updates` are copied from the original.
215pub fn merge_fields(base: &[u8], updates: &[(&str, &[u8])]) -> Vec<u8> {
216    use std::collections::HashSet;
217
218    let update_names: HashSet<&str> = updates.iter().map(|(k, _)| *k).collect();
219
220    let (count, body_start) = match crate::msgpack_scan::reader::map_header(base, 0) {
221        Some(v) => v,
222        None => {
223            // Not a valid map — build from updates only.
224            let mut buf = Vec::with_capacity(updates.len() * 32);
225            write_map_header(&mut buf, updates.len());
226            for (k, v) in updates {
227                write_str(&mut buf, k);
228                buf.extend_from_slice(v);
229            }
230            return buf;
231        }
232    };
233
234    // Count fields: existing (not overwritten) + updates.
235    let mut kept = 0usize;
236    let mut pos = body_start;
237    for _ in 0..count {
238        let key = crate::msgpack_scan::reader::read_str(base, pos);
239        pos = match crate::msgpack_scan::reader::skip_value(base, pos) {
240            Some(p) => p,
241            None => break,
242        };
243        pos = match crate::msgpack_scan::reader::skip_value(base, pos) {
244            Some(p) => p,
245            None => break,
246        };
247        if let Some(k) = &key {
248            if !update_names.contains(&k[..]) {
249                kept += 1;
250            }
251        } else {
252            kept += 1;
253        }
254    }
255
256    let new_count = kept + updates.len();
257    let mut buf = Vec::with_capacity(
258        base.len()
259            + updates
260                .iter()
261                .map(|(k, v)| k.len() + v.len() + 4)
262                .sum::<usize>(),
263    );
264    write_map_header(&mut buf, new_count);
265
266    // Copy non-overwritten fields from base.
267    pos = body_start;
268    for _ in 0..count {
269        let key_start = pos;
270        let key = crate::msgpack_scan::reader::read_str(base, pos);
271        pos = match crate::msgpack_scan::reader::skip_value(base, pos) {
272            Some(p) => p,
273            None => break,
274        };
275        pos = match crate::msgpack_scan::reader::skip_value(base, pos) {
276            Some(p) => p,
277            None => break,
278        };
279        if let Some(k) = &key
280            && update_names.contains(&k[..])
281        {
282            continue; // Will be written from updates.
283        }
284        // Copy key + value bytes.
285        buf.extend_from_slice(&base[key_start..pos]);
286    }
287
288    // Write updated/new fields.
289    for (k, v) in updates {
290        write_str(&mut buf, k);
291        buf.extend_from_slice(v);
292    }
293
294    buf
295}
296
297/// Build a simple msgpack map from string key-value pairs.
298pub fn build_str_map(pairs: &[(&str, &str)]) -> Vec<u8> {
299    let mut buf = Vec::with_capacity(pairs.len() * 32);
300    write_map_header(&mut buf, pairs.len());
301    for (k, v) in pairs {
302        write_kv_str(&mut buf, k, v);
303    }
304    buf
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    #[test]
312    fn roundtrip_map() {
313        let mut buf = Vec::new();
314        write_map_header(&mut buf, 2);
315        write_kv_str(&mut buf, "name", "alice");
316        write_kv_i64(&mut buf, "age", 30);
317
318        // Verify it's valid msgpack by reading back via reader
319        let (count, mut pos) = crate::msgpack_scan::reader::map_header(&buf, 0).unwrap();
320        assert_eq!(count, 2);
321
322        let k1 = crate::msgpack_scan::reader::read_str(&buf, pos).unwrap();
323        assert_eq!(k1, "name");
324        pos = crate::msgpack_scan::reader::skip_value(&buf, pos).unwrap();
325        let v1 = crate::msgpack_scan::reader::read_str(&buf, pos).unwrap();
326        assert_eq!(v1, "alice");
327        pos = crate::msgpack_scan::reader::skip_value(&buf, pos).unwrap();
328
329        let k2 = crate::msgpack_scan::reader::read_str(&buf, pos).unwrap();
330        assert_eq!(k2, "age");
331        pos = crate::msgpack_scan::reader::skip_value(&buf, pos).unwrap();
332        let v2 = crate::msgpack_scan::reader::read_i64(&buf, pos).unwrap();
333        assert_eq!(v2, 30);
334    }
335
336    #[test]
337    fn write_kv_raw_splices_correctly() {
338        // Build a value as raw msgpack
339        let mut val_buf = Vec::new();
340        write_str(&mut val_buf, "hello");
341
342        // Build a map with raw splice
343        let mut buf = Vec::new();
344        write_map_header(&mut buf, 1);
345        write_kv_raw(&mut buf, "greeting", &val_buf);
346
347        // Verify
348        let field = crate::msgpack_scan::field::extract_field(&buf, 0, "greeting").unwrap();
349        let s = crate::msgpack_scan::reader::read_str(&buf, field.0).unwrap();
350        assert_eq!(s, "hello");
351    }
352
353    #[test]
354    fn compact_integer_encoding() {
355        // Positive fixint
356        let mut buf = Vec::new();
357        write_i64(&mut buf, 42);
358        assert_eq!(buf, vec![42]);
359
360        // Negative fixint
361        buf.clear();
362        write_i64(&mut buf, -1);
363        assert_eq!(buf, vec![0xFF]);
364
365        // int8
366        buf.clear();
367        write_i64(&mut buf, -100);
368        assert_eq!(buf, vec![0xD0, (-100i8) as u8]);
369    }
370
371    #[test]
372    fn instant_cell_reads_back() {
373        let mut buf = Vec::new();
374        write_map_header(&mut buf, 1);
375        write_kv_instant(&mut buf, "ts", InstantKind::Naive, -42);
376
377        let field = crate::msgpack_scan::field::extract_field(&buf, 0, "ts").unwrap();
378        assert_eq!(field.1 - field.0, 10);
379        assert_eq!(
380            crate::msgpack_scan::reader::read_value(&buf, field.0),
381            Some(nodedb_types::Value::NaiveDateTime(
382                nodedb_types::NdbDateTime::from_micros(-42)
383            ))
384        );
385    }
386}