1use crate::fb::ColumnType;
2use byteorder::{ByteOrder, LittleEndian};
3use chrono::{DateTime, Utc};
4use cjseq::CityJSONFeature;
5use serde_json::Value;
6use std::collections::BTreeMap;
7use tracing::{debug, warn};
8
9pub type AttributeSchema = BTreeMap<String, (u16, ColumnType)>;
21
22pub trait AttributeSchemaMethods {
23 fn add_attributes(&mut self, attrs: &Value);
24}
25
26impl AttributeSchemaMethods for AttributeSchema {
27 fn add_attributes(&mut self, attrs: &Value) {
28 if !attrs.is_object() {
29 self.insert("json".to_string(), (self.len() as u16, ColumnType::Json));
30 return;
31 }
32
33 let map = attrs.as_object().unwrap();
34 for (key, val) in map.iter() {
35 if !self.contains_key(key) && !val.is_null() {
36 if let Some(coltype) = guess_type(val) {
37 self.insert(key.clone(), (self.len() as u16, coltype));
38 }
39 }
40 }
41 }
42}
43
44fn guess_type(value: &Value) -> Option<ColumnType> {
46 match value {
47 Value::Bool(_) => Some(ColumnType::Bool),
48 Value::Number(n) => {
49 if n.is_f64() {
50 Some(ColumnType::Double)
51 } else if n.is_u64() {
52 Some(ColumnType::ULong)
53 } else if n.is_i64() {
54 Some(ColumnType::Long)
55 } else {
56 Some(ColumnType::ULong) }
58 }
59 Value::String(s) => {
60 if chrono::DateTime::parse_from_rfc3339(s).is_ok() {
62 Some(ColumnType::DateTime)
63 } else {
64 Some(ColumnType::String)
65 }
66 }
67 Value::Array(_) => Some(ColumnType::Json),
68 Value::Object(_) => Some(ColumnType::Json),
69 _ => None,
70 }
71}
72
73pub(crate) fn attr_size(coltype: &ColumnType, colval: &Value) -> usize {
74 match *coltype {
75 ColumnType::Byte => size_of::<i8>(),
76 ColumnType::UByte => size_of::<u8>(),
77 ColumnType::Bool => size_of::<u8>(),
78 ColumnType::Short => size_of::<i16>(),
79 ColumnType::UShort => size_of::<u16>(),
80 ColumnType::Int => size_of::<i32>(),
81 ColumnType::UInt => size_of::<u32>(),
82 ColumnType::Long => size_of::<i64>(),
83 ColumnType::ULong => size_of::<u64>(),
84 ColumnType::Float => size_of::<f32>(),
85 ColumnType::Double => size_of::<f64>(),
86 ColumnType::String | ColumnType::DateTime => {
87 size_of::<u32>() + colval.as_str().unwrap().len()
88 }
89 ColumnType::Json => {
90 let json = serde_json::to_string(colval).unwrap_or_default();
91 size_of::<u32>() + json.len()
92 }
93 ColumnType::Binary => size_of::<u32>() + colval.as_str().unwrap().len(), _ => unreachable!(),
95 }
96}
97
98pub(crate) fn encode_attributes_with_schema(attr: &Value, schema: &AttributeSchema) -> Vec<u8> {
99 if !attr.is_object() || attr.as_object().unwrap().is_empty() || attr.is_null() {
100 return Vec::new();
101 }
102
103 let mut out = Vec::new();
104 let mut sorted_schema: Vec<_> = schema.iter().collect();
105 sorted_schema.sort_by_key(|(_, (index, _))| *index);
106
107 for (name, (index, coltype)) in sorted_schema {
108 let (_, val) = {
109 let attr_obj = attr.as_object();
110 if let Some(attr_obj) = attr_obj {
111 let value = attr_obj.iter().find(|(k, _)| *k == name);
112 if let Some(value) = value {
113 (value.0, value.1)
114 } else {
115 continue;
116 }
117 } else {
118 return Vec::new();
119 }
120 };
121
122 if val.is_null() {
123 continue;
124 }
125
126 let mut offset = out.len();
127 let attr_size = attr_size(coltype, val);
128
129 out.resize(offset + size_of::<u16>() + attr_size, 0);
131
132 LittleEndian::write_u16(&mut out[offset..], *index);
134 offset += size_of::<u16>();
135
136 match *coltype {
137 ColumnType::Bool => {
138 let b = val.as_bool().unwrap_or(false);
139 out[offset] = b as u8;
140 }
141 ColumnType::Int => {
142 let i = val.as_i64().unwrap_or(0);
143 LittleEndian::write_i32(&mut out[offset..], i as i32);
144 }
145 ColumnType::UInt => {
146 let i = val.as_u64().unwrap_or(0);
147 LittleEndian::write_u32(&mut out[offset..], i as u32);
148 }
149 ColumnType::Byte => {
150 let b = val.as_i64().unwrap_or(0);
151 out[offset] = b as u8;
152 }
153 ColumnType::UByte => {
154 let b = val.as_u64().unwrap_or(0);
155 out[offset] = b as u8;
156 }
157
158 ColumnType::Short => {
159 let i = val.as_i64().unwrap_or(0);
160 LittleEndian::write_i16(&mut out[offset..], i as i16);
161 }
162 ColumnType::UShort => {
163 let i = val.as_u64().unwrap_or(0);
164 LittleEndian::write_u16(&mut out[offset..], i as u16);
165 }
166
167 ColumnType::Long => {
168 let i = val.as_i64().unwrap_or(0);
169 LittleEndian::write_i64(&mut out[offset..], i);
170 }
171 ColumnType::ULong => {
172 let i = val.as_u64().unwrap_or(0);
173 LittleEndian::write_u64(&mut out[offset..], i);
174 }
175 ColumnType::Float => {
176 let f = val.as_f64().unwrap_or(0.0);
177 LittleEndian::write_f32(&mut out[offset..], f as f32);
178 }
179 ColumnType::Double => {
180 let f = val.as_f64().unwrap_or(0.0);
181 LittleEndian::write_f64(&mut out[offset..], f);
182 }
183 ColumnType::String | ColumnType::DateTime => {
184 let s = val.as_str().unwrap_or("");
185 LittleEndian::write_u32(&mut out[offset..], s.len() as u32);
186 out[offset + size_of::<u32>()..offset + size_of::<u32>() + s.len()]
187 .copy_from_slice(s.as_bytes());
188 }
189 ColumnType::Json => {
190 let json = serde_json::to_string(val).unwrap_or_default();
191 LittleEndian::write_u32(&mut out[offset..], json.len() as u32);
192 out[offset + size_of::<u32>()..offset + size_of::<u32>() + json.len()]
193 .copy_from_slice(json.as_bytes());
194 }
195 ColumnType::Binary => {
196 let s = val.as_str().unwrap_or("");
197 LittleEndian::write_u32(&mut out[offset..], s.len() as u32);
198 out[offset + size_of::<u32>()..offset + size_of::<u32>() + s.len()]
199 .copy_from_slice(s.as_bytes());
200 }
201 _ => unreachable!(),
202 }
203 }
204 out
205}
206
207#[derive(Clone, PartialEq, Debug)]
208pub enum AttributeIndexEntry {
209 Bool { index: u16, val: bool },
210 Int { index: u16, val: i32 },
211 UInt { index: u16, val: u32 },
212 Long { index: u16, val: i64 },
213 ULong { index: u16, val: u64 },
214 Float { index: u16, val: f32 },
215 Double { index: u16, val: f64 },
216 String { index: u16, val: String },
217 DateTime { index: u16, val: DateTime<Utc> },
218 Short { index: u16, val: i16 },
219 UShort { index: u16, val: u16 },
220 Byte { index: u16, val: u8 },
221 UByte { index: u16, val: u8 },
222 Json { index: u16, val: String },
223 Binary { index: u16, val: String },
224}
225
226pub fn cityfeature_to_index_entries(
227 cityfeature: &CityJSONFeature,
228 schema: &AttributeSchema,
229 indexing_attr: &[String],
230) -> Vec<AttributeIndexEntry> {
231 let mut index_entries = Vec::new();
232 let mut object_ids: Vec<_> = cityfeature.city_objects.keys().collect();
236 object_ids.sort_unstable();
237 for object in object_ids
238 .into_iter()
239 .filter_map(|id| cityfeature.city_objects.get(id))
240 {
241 if let Some(attr) = &object.attributes {
242 let attr_index_entries = attribute_to_index_entries(attr, schema, indexing_attr);
243 index_entries.extend(attr_index_entries);
244 }
245 }
246
247 index_entries
248}
249
250pub fn attribute_to_index_entries(
252 attr: &Value,
253 schema: &AttributeSchema,
254 indexing_attr: &[String],
255) -> Vec<AttributeIndexEntry> {
256 if !attr.is_object() || attr.is_null() || attr.as_object().unwrap().is_empty() {
257 return Vec::new();
258 }
259
260 let mut index_entries = Vec::new();
261
262 let map = attr.as_object().unwrap();
263 for attr in indexing_attr {
264 let val: &Value = match map.get(attr) {
265 Some(val) => val,
266 None => {
267 debug!("feature is missing indexed attribute {attr}");
270 continue;
271 }
272 };
273
274 let index_coltype = schema.get(attr);
275 if let Some((index, coltype)) = index_coltype {
276 match *coltype {
277 ColumnType::Bool => {
278 let b = val.as_bool().unwrap_or(false);
279 index_entries.push(AttributeIndexEntry::Bool {
280 index: *index,
281 val: b,
282 });
283 }
284 ColumnType::Int => {
285 let i = val.as_i64().unwrap_or(0);
286 index_entries.push(AttributeIndexEntry::Int {
287 index: *index,
288 val: i as i32,
289 });
290 }
291 ColumnType::UInt => {
292 let i = val.as_u64().unwrap_or(0);
293 index_entries.push(AttributeIndexEntry::UInt {
294 index: *index,
295 val: i as u32,
296 });
297 }
298 ColumnType::Long => {
299 let i = val.as_i64().unwrap_or(0);
300 index_entries.push(AttributeIndexEntry::Long {
301 index: *index,
302 val: i as i64,
303 });
304 }
305 ColumnType::ULong => {
306 let i = val.as_u64().unwrap_or(0);
307 index_entries.push(AttributeIndexEntry::ULong {
308 index: *index,
309 val: i as u64,
310 });
311 }
312 ColumnType::Float => {
313 let f = val.as_f64().unwrap_or(0.0);
314 index_entries.push(AttributeIndexEntry::Float {
315 index: *index,
316 val: f as f32,
317 });
318 }
319 ColumnType::Double => {
320 let f = val.as_f64().unwrap_or(0.0);
321 index_entries.push(AttributeIndexEntry::Double {
322 index: *index,
323 val: f,
324 });
325 }
326 ColumnType::String => {
327 index_entries.push(AttributeIndexEntry::String {
328 index: *index,
329 val: val.as_str().unwrap_or("").to_string(),
330 });
331 }
332 ColumnType::DateTime => {
333 let dt = match chrono::DateTime::parse_from_rfc3339(val.as_str().unwrap_or(""))
334 {
335 Ok(dt) => dt.to_utc(),
336 Err(e) => {
337 warn!("failed to parse DateTime for {attr}, defaulting to epoch: {e}");
338 DateTime::<Utc>::from_timestamp(0, 0).unwrap()
341 }
342 };
343 index_entries.push(AttributeIndexEntry::DateTime {
344 index: *index,
345 val: dt,
346 });
347 }
348 _ => {
349 debug!("attribute {attr} is not supported for indexing");
351 }
352 }
353 }
354 }
355
356 index_entries
357}
358
359#[cfg(test)]
360mod tests {
361 use crate::{
362 deserializer::decode_attributes,
363 root_as_city_feature, root_as_header,
364 serializer::{to_columns, to_fcb_attribute},
365 CityFeature, CityFeatureArgs, CityObject, CityObjectArgs, Header, HeaderArgs,
366 };
367
368 use super::*;
369
370 use anyhow::Result;
371 use flatbuffers::FlatBufferBuilder;
372 use pretty_assertions::assert_eq;
373 use serde_json::json;
374
375 #[test]
376 fn test_add_attributes() -> Result<()> {
377 let json_data = json!({
378 "attributes": {
379 "int": -10,
380 "uint": 5,
381 "bool": true,
382 "float": 1.0,
383 "string": "hoge",
384 "array": [1, 2, 3],
385 "json": {
386 "hoge": "fuga"
387 },
388 "null": null
389 }
390 });
391
392 let mut attr_schema: AttributeSchema = AttributeSchema::new();
393
394 attr_schema.add_attributes(&json_data["attributes"]);
395
396 assert_eq!(attr_schema.get("int").unwrap().1, ColumnType::Long);
398 assert_eq!(attr_schema.get("uint").unwrap().1, ColumnType::ULong);
399 assert_eq!(attr_schema.get("bool").unwrap().1, ColumnType::Bool);
400 assert_eq!(attr_schema.get("float").unwrap().1, ColumnType::Double);
401 assert_eq!(attr_schema.get("string").unwrap().1, ColumnType::String);
402 assert_eq!(attr_schema.get("array").unwrap().1, ColumnType::Json); assert_eq!(attr_schema.get("json").unwrap().1, ColumnType::Json);
404
405 Ok(())
406 }
407
408 #[test]
409 fn test_attribute_serialization() -> Result<()> {
410 let test_cases = vec![
411 (
413 json!({
414 "int": -10,
415 "uint": 5,
416 "bool": true,
417 "float": 1.0,
418 "string": "hoge",
419 "array": [1, 2, 3],
420 "json": {
421 "hoge": "fuga"
422 }
423 }),
424 json!({
425 "int": -10,
426 "uint": 5,
427 "bool": true,
428 "float": 1.0,
429 "string": "hoge",
430 "array": [1, 2, 3],
431 "json": {
432 "hoge": "fuga"
433 },
434 }),
435 json!({
436 "attributes": {
437 "int": -10,
438 "uint": 5,
439 "bool": true,
440 "float": 1.0,
441 "string": "hoge",
442 "array": [1, 2, 3],
443 "json": {
444 "hoge": "fuga"
445 }
446 }
447 }),
448 "same schema",
449 ),
450 (
452 json!({
453 "int": -10,
454 "uint": 5,
455 "bool": true,
456 "float": 1.0,
457 "string": "hoge",
458 "array": [1, 2, 3],
459 "json": {
460 "hoge": "fuga"
461 },
462 "exception": null
463 }),
464 json!({
465 "int": -10,
466 "uint": 5,
467 "bool": true,
468 "float": 1.0,
469 "string": "hoge",
470 "array": [1, 2, 3],
471 "json": {
472 "hoge": "fuga"
473 },
474 }),
475 json!({
476 "attributes": {
477 "int": -10,
478 "uint": 5,
479 "bool": true,
480 "float": 1.0,
481 "string": "hoge",
482 "array": [1, 2, 3],
483 "json": {
484 "hoge": "fuga"
485 },
486 "exception": 1000
487 }
488 }),
489 "JSON with null value",
490 ),
491 (
493 json!({}),
494 json!({}),
495 json!({
496 "attributes": {
497 "int": -10,
498 "uint": 5,
499 "bool": true,
500 "float": 1.0,
501 "string": "hoge",
502 "array": [1, 2, 3],
503 "json": {
504 "hoge": "fuga"
505 },
506 "exception": 1000
507 }
508 }),
509 "JSON is empty",
510 ),
511 ];
512
513 for (input, expected, schema, test_name) in test_cases {
514 println!("Testing case: {test_name}");
515
516 let attrs = &input;
517 let attr_schema = &schema["attributes"];
518
519 let mut fbb = FlatBufferBuilder::new();
521 let mut common_schema = AttributeSchema::new();
522 common_schema.add_attributes(attr_schema);
523
524 let columns = to_columns(&mut fbb, &common_schema);
525 let header = {
526 let version = fbb.create_string("1.0.0");
527 Header::create(
528 &mut fbb,
529 &HeaderArgs {
530 version: Some(version),
531 columns: Some(columns),
532 ..Default::default()
533 },
534 )
535 };
536 fbb.finish(header, None);
537
538 let finished_data = fbb.finished_data();
540 let header_buf = root_as_header(finished_data).unwrap();
541
542 let mut fbb = FlatBufferBuilder::new();
543 let feature = {
544 let (attr_buf, _) = to_fcb_attribute(&mut fbb, attrs, &common_schema);
545 let city_object = {
546 let id = fbb.create_string("test");
547 CityObject::create(
548 &mut fbb,
549 &CityObjectArgs {
550 id: Some(id),
551 attributes: Some(attr_buf),
552 ..Default::default()
553 },
554 )
555 };
556 let objects = fbb.create_vector(&[city_object]);
557 let cf_id = fbb.create_string("test_feature");
558 CityFeature::create(
559 &mut fbb,
560 &CityFeatureArgs {
561 id: Some(cf_id),
562 objects: Some(objects),
563 ..Default::default()
564 },
565 )
566 };
567
568 fbb.finish(feature, None);
569
570 let finished_data = fbb.finished_data();
571 let feature_buf = root_as_city_feature(finished_data).unwrap();
572 let attributes = feature_buf.objects().unwrap().get(0).attributes().unwrap();
573
574 let decoded = decode_attributes(&header_buf.columns().unwrap(), attributes);
575
576 assert_eq!(
577 expected, decoded,
578 "decoded data should match original for {}",
579 test_name
580 );
581 }
582
583 Ok(())
584 }
585}