1use hyle::{ModelResult, Row, Source, Value};
2use indexmap::IndexMap;
3use qmap::Qmap;
4use std::ffi::CStr;
5use libc::c_char;
6use serde::{Serialize, Deserialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum FieldType {
10 String = 0,
11 Int = 1,
12 Bool = 2,
13 NullableString = 3,
14 Reference = 4,
15 MultiReference = 5,
16}
17
18pub struct FieldDef {
19 pub name: String,
20 pub field_type: FieldType,
21 pub target_dataset: Option<String>,
22 pub inverse_name: Option<String>,
23}
24
25pub struct DatasetDef {
26 pub id: String,
27 pub fields: Vec<FieldDef>,
28 pub source_hd: u32,
29}
30
31#[derive(Serialize, Deserialize, Debug)]
32pub struct RelationItem {
33 pub id: String,
34 #[serde(flatten)]
35 pub inverse: IndexMap<String, Vec<String>>,
36}
37
38#[derive(Serialize, Deserialize, Debug)]
39pub struct Relation {
40 pub target: String,
41 pub inverse: Option<String>,
42 pub items: Vec<RelationItem>,
43}
44
45pub fn build_source_from_json_qmap(qmap: &Qmap, def: &DatasetDef) -> Source {
48 let mut rows = Vec::new();
49 let mut cursor = qmap.iter(std::ptr::null(), 0);
50
51 while let Some((_key_ptr, val_ptr)) = cursor.next() {
52 let c_str = unsafe { CStr::from_ptr(val_ptr as *const c_char) };
53 if let Ok(json_str) = c_str.to_str() {
54 if let Ok(serde_json::Value::Object(map)) = serde_json::from_str(json_str) {
55 let mut row: Row = IndexMap::new();
56 for (k, v) in map {
57 row.insert(k, json_to_hyle_value(v));
58 }
59 rows.push(row);
60 }
61 }
62 }
63
64 let mut source = Source::new();
65 source.insert(def.id.clone(), ModelResult::many(rows));
66
67 let mut relations = IndexMap::new();
69 for field in &def.fields {
70 if let Some(target) = &field.target_dataset {
71 if let Some(rel) = build_relation(qmap, def, field, target) {
72 relations.insert(field.name.clone(), rel);
73 }
74 }
75 }
76
77 if !relations.is_empty() {
78 if let Ok(rel_val) = serde_json::to_value(relations) {
79 source.insert("relations".to_string(), ModelResult::one(json_to_hyle_row(rel_val)));
80 }
81 }
82
83 source
84}
85
86fn build_relation(qmap: &Qmap, _def: &DatasetDef, field: &FieldDef, target: &str) -> Option<Relation> {
87 let mut inverse_map: IndexMap<String, Vec<String>> = IndexMap::new();
92 let mut cursor = qmap.iter(std::ptr::null(), 0);
93 while let Some((key_ptr, val_ptr)) = cursor.next() {
94 let id = unsafe { CStr::from_ptr(key_ptr as *const c_char) }.to_string_lossy().into_owned();
95 let row_json = unsafe { CStr::from_ptr(val_ptr as *const c_char) }.to_string_lossy();
96
97 if let Ok(serde_json::Value::Object(map)) = serde_json::from_str(&row_json) {
98 if let Some(val) = map.get(&field.name) {
99 match val {
100 serde_json::Value::String(s) => {
101 if !s.is_empty() {
102 inverse_map.entry(s.clone()).or_default().push(id);
103 }
104 }
105 serde_json::Value::Array(arr) => {
106 for v in arr {
107 if let serde_json::Value::String(s) = v {
108 if !s.is_empty() {
109 inverse_map.entry(s.clone()).or_default().push(id.clone());
110 }
111 }
112 }
113 }
114 _ => {}
115 }
116 }
117 }
118 }
119
120 if inverse_map.is_empty() {
121 return None;
122 }
123
124 let mut items = Vec::new();
125 for (target_id, source_ids) in inverse_map {
126 let mut inverse = IndexMap::new();
127 if let Some(inv_name) = &field.inverse_name {
128 inverse.insert(inv_name.clone(), source_ids);
129 }
130 items.push(RelationItem {
131 id: target_id,
132 inverse,
133 });
134 }
135
136 Some(Relation {
137 target: target.to_string(),
138 inverse: field.inverse_name.clone(),
139 items,
140 })
141}
142
143fn json_to_hyle_value(v: serde_json::Value) -> Value {
144 match v {
145 serde_json::Value::String(s) => Value::String(s),
146 serde_json::Value::Number(n) => Value::String(n.to_string()),
147 serde_json::Value::Bool(b) => Value::String(b.to_string()),
148 serde_json::Value::Array(arr) => {
149 Value::Array(arr.into_iter().map(json_to_hyle_value).collect())
150 }
151 serde_json::Value::Null => Value::String(String::new()),
152 serde_json::Value::Object(obj) => Value::String(serde_json::to_string(&obj).unwrap_or_default()),
153 }
154}
155
156fn json_to_hyle_row(v: serde_json::Value) -> Row {
157 if let serde_json::Value::Object(map) = v {
158 let mut row = IndexMap::new();
159 for (k, val) in map {
160 row.insert(k, json_to_hyle_value(val));
161 }
162 row
163 } else {
164 IndexMap::new()
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_build_source_from_json_qmap() {
174 let q = Qmap::open(None, None, 2, 2, 0xFF, 0).unwrap();
175 q.put_str("song1", "{\"id\":\"song1\", \"title\":\"Song One\", \"type\":\"Rock\"}");
176 q.put_str("song2", "{\"id\":\"song2\", \"title\":\"Song Two\", \"type\":[\"Jazz\",\"Blues\"]}");
177
178 let def = DatasetDef {
179 id: "song.items".to_string(),
180 fields: vec![
181 FieldDef {
182 name: "id".to_string(),
183 field_type: FieldType::String,
184 target_dataset: None,
185 inverse_name: None,
186 },
187 FieldDef {
188 name: "title".to_string(),
189 field_type: FieldType::String,
190 target_dataset: None,
191 inverse_name: None,
192 },
193 FieldDef {
194 name: "type".to_string(),
195 field_type: FieldType::MultiReference,
196 target_dataset: Some("song.types".to_string()),
197 inverse_name: Some("songs".to_string()),
198 },
199 ],
200 source_hd: q.handle(),
201 };
202
203 let source = build_source_from_json_qmap(&q, &def);
204 assert!(source.contains_key("song.items"));
205 assert!(source.contains_key("relations"));
206
207 let result = source.get("song.items").unwrap();
208 let rows = result.rows();
209 assert_eq!(rows.len(), 2);
210
211 let song2 = rows.iter().find(|r| r.get("id") == Some(&Value::String("song2".to_string()))).unwrap();
212 assert_eq!(song2.get("type"), Some(&Value::Array(vec![Value::String("Jazz".to_string()), Value::String("Blues".to_string())])));
213
214 let rel_result = source.get("relations").unwrap();
215 let rel_row = &rel_result.rows()[0];
216 let type_rel_str = match rel_row.get("type").unwrap() {
217 Value::String(s) => s,
218 _ => panic!("Expected string JSON for relation"),
219 };
220 let type_rel: Relation = serde_json::from_str(type_rel_str).unwrap();
221 assert_eq!(type_rel.target, "song.types");
222 assert_eq!(type_rel.items.len(), 3); }
224
225 #[test]
226 fn test_single_reference() {
227 let q = Qmap::open(None, None, 2, 2, 0xFF, 0).unwrap();
228 q.put_str("song1", "{\"id\":\"song1\", \"author\":\"author1\"}");
229 q.put_str("song2", "{\"id\":\"song2\", \"author\":\"author1\"}");
230
231 let def = DatasetDef {
232 id: "song.items".to_string(),
233 fields: vec![
234 FieldDef {
235 name: "id".to_string(),
236 field_type: FieldType::String,
237 target_dataset: None,
238 inverse_name: None,
239 },
240 FieldDef {
241 name: "author".to_string(),
242 field_type: FieldType::Reference,
243 target_dataset: Some("author.items".to_string()),
244 inverse_name: Some("songs".to_string()),
245 },
246 ],
247 source_hd: q.handle(),
248 };
249
250 let source = build_source_from_json_qmap(&q, &def);
251 let rel_result = source.get("relations").unwrap();
252 let rel_row = &rel_result.rows()[0];
253 let author_rel_str = match rel_row.get("author").unwrap() {
254 Value::String(s) => s,
255 _ => panic!("Expected string JSON for relation"),
256 };
257 let author_rel: Relation = serde_json::from_str(author_rel_str).unwrap();
258 assert_eq!(author_rel.target, "author.items");
259 assert_eq!(author_rel.items.len(), 1); assert_eq!(author_rel.items[0].id, "author1");
261 assert_eq!(author_rel.items[0].inverse.get("songs").unwrap(), &vec!["song1".to_string(), "song2".to_string()]);
262 }
263
264 #[test]
265 fn test_primitive_types() {
266 let q = Qmap::open(None, None, 2, 2, 0xFF, 0).unwrap();
267 q.put_str("item1", "{\"id\":\"item1\", \"count\":42, \"active\":true, \"price\":3.14}");
268
269 let def = DatasetDef {
270 id: "items".to_string(),
271 fields: vec![
272 FieldDef { name: "id".to_string(), field_type: FieldType::String, target_dataset: None, inverse_name: None },
273 FieldDef { name: "count".to_string(), field_type: FieldType::Int, target_dataset: None, inverse_name: None },
274 FieldDef { name: "active".to_string(), field_type: FieldType::Bool, target_dataset: None, inverse_name: None },
275 ],
276 source_hd: q.handle(),
277 };
278
279 let source = build_source_from_json_qmap(&q, &def);
280 let result = source.get("items").unwrap();
281 let row = &result.rows()[0];
282 assert_eq!(row.get("count"), Some(&Value::String("42".to_string())));
283 assert_eq!(row.get("active"), Some(&Value::String("true".to_string())));
284 assert_eq!(row.get("price"), Some(&Value::String("3.14".to_string())));
285 }
286
287 #[test]
288 fn test_null_and_empty_values() {
289 let q = Qmap::open(None, None, 2, 2, 0xFF, 0).unwrap();
290 q.put_str("item1", "{\"id\":\"item1\", \"ref\":null, \"multi_ref\":[], \"str\":\"\"}");
291 q.put_str("item2", "{\"id\":\"item2\", \"multi_ref\":[\"\"]}");
292
293 let def = DatasetDef {
294 id: "items".to_string(),
295 fields: vec![
296 FieldDef { name: "ref".to_string(), field_type: FieldType::Reference, target_dataset: Some("target".to_string()), inverse_name: None },
297 FieldDef { name: "multi_ref".to_string(), field_type: FieldType::MultiReference, target_dataset: Some("target".to_string()), inverse_name: None },
298 ],
299 source_hd: q.handle(),
300 };
301
302 let source = build_source_from_json_qmap(&q, &def);
303 let result = source.get("items").unwrap();
304 let rows = result.rows();
305 let item1 = rows.iter().find(|r| r.get("id") == Some(&Value::String("item1".to_string()))).unwrap();
306 assert_eq!(item1.get("ref"), Some(&Value::String("".to_string())));
307 assert_eq!(item1.get("str"), Some(&Value::String("".to_string())));
308
309 assert!(!source.contains_key("relations"));
310 }
311
312 #[test]
313 fn test_multiple_relations() {
314 let q = Qmap::open(None, None, 2, 2, 0xFF, 0).unwrap();
315 q.put_str("item1", "{\"id\":\"item1\", \"ref1\":\"targetA1\", \"ref2\":\"targetB1\"}");
316
317 let def = DatasetDef {
318 id: "items".to_string(),
319 fields: vec![
320 FieldDef { name: "ref1".to_string(), field_type: FieldType::Reference, target_dataset: Some("targetA".to_string()), inverse_name: None },
321 FieldDef { name: "ref2".to_string(), field_type: FieldType::Reference, target_dataset: Some("targetB".to_string()), inverse_name: None },
322 ],
323 source_hd: q.handle(),
324 };
325
326 let source = build_source_from_json_qmap(&q, &def);
327 let rel_result = source.get("relations").unwrap();
328 let rel_row = &rel_result.rows()[0];
329
330 assert!(rel_row.contains_key("ref1"));
331 assert!(rel_row.contains_key("ref2"));
332 }
333
334 #[test]
335 fn test_fault_tolerance() {
336 let q = Qmap::open(None, None, 2, 2, 0xFF, 0).unwrap();
337 q.put_str("item1", "{\"id\":\"item1\"}");
338 q.put_str("item2", "INVALID JSON");
339 q.put_str("item3", "{\"id\":\"item3\"}");
340
341 let def = DatasetDef {
342 id: "items".to_string(),
343 fields: vec![],
344 source_hd: q.handle(),
345 };
346
347 let source = build_source_from_json_qmap(&q, &def);
348 let result = source.get("items").unwrap();
349 assert_eq!(result.rows().len(), 2);
350 assert_eq!(result.rows()[0].get("id"), Some(&Value::String("item1".to_string())));
351 assert_eq!(result.rows()[1].get("id"), Some(&Value::String("item3".to_string())));
352 }
353}