Skip to main content

nodedb_query/msgpack_scan/
group_key.rs

1//! Binary group key construction for GROUP BY on raw MessagePack documents.
2//!
3//! Builds a deterministic string key from field values extracted directly
4//! from msgpack bytes, avoiding full document decode.
5
6use crate::msgpack_scan::field::extract_field;
7use crate::msgpack_scan::index::FieldIndex;
8use crate::msgpack_scan::reader::{read_f64, read_i64, read_null, read_str};
9
10/// Build a GROUP BY key string from raw msgpack bytes.
11///
12/// Format: `[val1,val2,...]` where values are formatted as JSON literals.
13/// This is compatible with the legacy `sonic_rs::to_string(&key_parts)` format,
14/// so the result-construction code can parse it back with `sonic_rs::from_str`.
15pub fn build_group_key(doc: &[u8], group_fields: &[String]) -> String {
16    if group_fields.is_empty() {
17        return "__all__".to_string();
18    }
19
20    let mut key_buf = String::new();
21    key_buf.push('[');
22    for (i, field) in group_fields.iter().enumerate() {
23        if i > 0 {
24            key_buf.push(',');
25        }
26        append_field_value(&mut key_buf, doc, field);
27    }
28    key_buf.push(']');
29    key_buf
30}
31
32/// Build a GROUP BY key using a pre-built `FieldIndex` for O(1) lookups.
33pub fn build_group_key_indexed(doc: &[u8], group_fields: &[String], idx: &FieldIndex) -> String {
34    if group_fields.is_empty() {
35        return "__all__".to_string();
36    }
37
38    let mut key_buf = String::new();
39    key_buf.push('[');
40    for (i, field) in group_fields.iter().enumerate() {
41        if i > 0 {
42            key_buf.push(',');
43        }
44        let range = idx.get(field);
45        append_field_value_range(&mut key_buf, doc, range);
46    }
47    key_buf.push(']');
48    key_buf
49}
50
51/// Append a single field's value to the key buffer as a JSON literal.
52fn append_field_value(buf: &mut String, doc: &[u8], field: &str) {
53    let Some((start, end)) = extract_field(doc, 0, field) else {
54        buf.push_str("null");
55        return;
56    };
57
58    if read_null(doc, start) {
59        buf.push_str("null");
60    } else if let Some(s) = read_str(doc, start) {
61        buf.push('"');
62        buf.push_str(s);
63        buf.push('"');
64    } else if let Some(n) = read_i64(doc, start) {
65        use std::fmt::Write;
66        let _ = write!(buf, "{n}");
67    } else if let Some(n) = read_f64(doc, start) {
68        use std::fmt::Write;
69        let _ = write!(buf, "{n}");
70    } else {
71        // Complex value (array/map/bin) — hex-encode raw bytes as key.
72        let bytes = &doc[start..end];
73        for b in bytes {
74            use std::fmt::Write;
75            let _ = write!(buf, "{b:02x}");
76        }
77    }
78}
79
80/// Append a field value from a pre-resolved range.
81fn append_field_value_range(buf: &mut String, doc: &[u8], range: Option<(usize, usize)>) {
82    let Some((start, end)) = range else {
83        buf.push_str("null");
84        return;
85    };
86
87    if read_null(doc, start) {
88        buf.push_str("null");
89    } else if let Some(s) = read_str(doc, start) {
90        buf.push('"');
91        buf.push_str(s);
92        buf.push('"');
93    } else if let Some(n) = read_i64(doc, start) {
94        use std::fmt::Write;
95        let _ = write!(buf, "{n}");
96    } else if let Some(n) = read_f64(doc, start) {
97        use std::fmt::Write;
98        let _ = write!(buf, "{n}");
99    } else {
100        let bytes = &doc[start..end];
101        for b in bytes {
102            use std::fmt::Write;
103            let _ = write!(buf, "{b:02x}");
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use serde_json::json;
112
113    fn encode(v: &serde_json::Value) -> Vec<u8> {
114        nodedb_types::json_msgpack::json_to_msgpack(v).expect("encode")
115    }
116
117    #[test]
118    fn single_string_field() {
119        let doc = encode(&json!({"name": "alice", "age": 30}));
120        let key = build_group_key(&doc, &["name".into()]);
121        assert_eq!(key, r#"["alice"]"#);
122    }
123
124    #[test]
125    fn single_int_field() {
126        let doc = encode(&json!({"status": 200}));
127        let key = build_group_key(&doc, &["status".into()]);
128        assert_eq!(key, "[200]");
129    }
130
131    #[test]
132    fn multiple_fields() {
133        let doc = encode(&json!({"city": "ny", "year": 2024}));
134        let key = build_group_key(&doc, &["city".into(), "year".into()]);
135        assert_eq!(key, r#"["ny",2024]"#);
136    }
137
138    #[test]
139    fn missing_field_is_null() {
140        let doc = encode(&json!({"x": 1}));
141        let key = build_group_key(&doc, &["missing".into()]);
142        assert_eq!(key, "[null]");
143    }
144
145    #[test]
146    fn empty_group_fields() {
147        let doc = encode(&json!({"x": 1}));
148        let key = build_group_key(&doc, &[]);
149        assert_eq!(key, "__all__");
150    }
151
152    #[test]
153    fn null_field_value() {
154        let doc = encode(&json!({"v": null}));
155        let key = build_group_key(&doc, &["v".into()]);
156        assert_eq!(key, "[null]");
157    }
158
159    #[test]
160    fn float_field() {
161        let doc = encode(&json!({"temp": 36.6}));
162        let key = build_group_key(&doc, &["temp".into()]);
163        assert_eq!(key, "[36.6]");
164    }
165}