qualia_core_db/sparql_library/parsers/
csv_parser.rs1use atoi::atoi;
7use chrono::{DateTime, NaiveDate, NaiveDateTime, TimeZone, Utc};
8use csv::ReaderBuilder;
9use std::io::Read;
10
11use crate::mini_parser::hash_token;
12use crate::NQuin;
13
14#[derive(Debug, Clone, Copy)]
16pub enum CsvDatatype {
17 Integer,
18 Float,
19 DateTime,
20 StringRef,
21}
22
23#[derive(Debug, Clone)]
25pub struct CsvColumnMapping {
26 pub source_key: String,
27 pub column_index: Option<usize>,
28 pub predicate_hash: u64,
29 pub datatype: CsvDatatype,
30}
31
32#[derive(Debug, Clone)]
34pub struct CsvMappingProfile {
35 pub base_class_hash: u64,
36 pub fields: Vec<CsvColumnMapping>,
37}
38
39pub fn parse_csv_to_quins<R: Read>(
41 reader: R,
42 profile: &mut CsvMappingProfile,
43 mut on_quin: impl FnMut(NQuin),
44) -> Result<(), String> {
45 let mut rdr = ReaderBuilder::new().has_headers(true).from_reader(reader);
46
47 let headers = rdr
49 .byte_headers()
50 .map_err(|e| format!("Failed to read headers: {}", e))?
51 .clone();
52 for field in profile.fields.iter_mut() {
53 field.column_index = headers
54 .iter()
55 .position(|h| h == field.source_key.as_bytes());
56 }
57
58 let mut record = csv::ByteRecord::new();
59
60 while rdr
62 .read_byte_record(&mut record)
63 .map_err(|e| format!("CSV read error: {}", e))?
64 {
65 let subject_hash: u64 = rand::random(); for field in &profile.fields {
68 if let Some(idx) = field.column_index {
69 if let Some(raw_bytes) = record.get(idx) {
70 match field.datatype {
72 CsvDatatype::Integer => {
73 let val: u64 = atoi::<u64>(raw_bytes).unwrap_or(0);
74 let quin = NQuin {
75 subject: subject_hash,
76 predicate: field.predicate_hash,
77 object: val | (0b001 << 60), context: 0,
79 metadata: 0,
80 parity: NQuin::calculate_parity(
81 subject_hash,
82 field.predicate_hash,
83 val | (0b001 << 60),
84 0,
85 0,
86 ),
87 };
88 on_quin(quin);
89 }
90 CsvDatatype::Float => {
91 let str_slice = std::str::from_utf8(raw_bytes).unwrap_or("0.0");
92 let float_val: f32 = str_slice.parse::<f32>().unwrap_or(0.0);
93 let float_bits: u32 = float_val.to_bits();
94 let inline_tag: u64 = 0b010 << 60;
95 let packed_object: u64 = inline_tag | (float_bits as u64);
96
97 let quin = NQuin {
98 subject: subject_hash,
99 predicate: field.predicate_hash,
100 object: packed_object,
101 context: 0,
102 metadata: 0,
103 parity: NQuin::calculate_parity(
104 subject_hash,
105 field.predicate_hash,
106 packed_object,
107 0,
108 0,
109 ),
110 };
111 on_quin(quin);
112 }
113 CsvDatatype::StringRef => {
114 let s = std::str::from_utf8(raw_bytes).unwrap_or("");
115 let quin = NQuin {
116 subject: subject_hash,
117 predicate: field.predicate_hash,
118 object: hash_token(s),
119 context: 0,
120 metadata: 0,
121 parity: NQuin::calculate_parity(
122 subject_hash,
123 field.predicate_hash,
124 hash_token(s),
125 0,
126 0,
127 ),
128 };
129 on_quin(quin);
130 }
131 CsvDatatype::DateTime => {
132 let s = std::str::from_utf8(raw_bytes).unwrap_or("");
133 let millis: u64 = parse_datetime_millis(s).unwrap_or(0);
134 let quin = NQuin {
135 subject: subject_hash,
136 predicate: field.predicate_hash,
137 object: (0b011u64 << 60) | millis,
138 context: 0,
139 metadata: 0,
140 parity: NQuin::calculate_parity(
141 subject_hash,
142 field.predicate_hash,
143 (0b011u64 << 60) | millis,
144 0,
145 0,
146 ),
147 };
148 on_quin(quin);
149 }
150 }
151 }
152 }
153 }
154 }
155
156 Ok(())
157}
158
159fn parse_datetime_millis(s: &str) -> Option<u64> {
161 if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
162 return Some(dt.timestamp_millis() as u64);
163 }
164 for fmt in &[
165 "%Y-%m-%dT%H:%M:%S",
166 "%Y-%m-%d %H:%M:%S",
167 "%Y-%m-%dT%H:%M:%SZ",
168 ] {
169 if let Ok(nd) = NaiveDateTime::parse_from_str(s, fmt) {
170 return Some(Utc.from_utc_datetime(&nd).timestamp_millis() as u64);
171 }
172 }
173 if let Ok(d) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
174 let nd = d.and_hms_opt(0, 0, 0)?;
175 return Some(Utc.from_utc_datetime(&nd).timestamp_millis() as u64);
176 }
177 None
178}