nodedb_query/msgpack_scan/
group_key.rs1use 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
10pub 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
32pub 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
51fn 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 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
80fn 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}