Skip to main content

qualia_core_db/sparql_library/serialisers/
csv_serializer.rs

1//! CSV Serializer for QualiaDB
2//!
3//! Serializes NQuin data to CSV format with proper type handling.
4
5use std::io::Write;
6
7use crate::NQuin;
8
9/// CSV serialization configuration
10#[derive(Debug, Clone)]
11pub struct CsvSerializationProfile {
12    pub headers: Vec<String>,
13    pub predicate_hashes: Vec<u64>,
14    pub datatypes: Vec<CsvDatatype>,
15}
16
17/// Data type for CSV serialization
18#[derive(Debug, Clone, Copy)]
19pub enum CsvDatatype {
20    Integer,
21    Float,
22    DateTime,
23    StringRef,
24}
25
26/// Serialize Quins to CSV format
27pub fn serialize_quins_to_csv<W: Write>(
28    writer: &mut W,
29    quins: &[NQuin],
30    profile: &CsvSerializationProfile,
31) -> Result<(), String> {
32    // Write headers
33    writeln!(writer, "{}", profile.headers.join(","))
34        .map_err(|e| format!("Failed to write CSV headers: {}", e))?;
35
36    // Group quins by subject (assuming each subject represents a row)
37    let mut subjects: std::collections::HashMap<u64, Vec<&NQuin>> =
38        std::collections::HashMap::new();
39    for quin in quins {
40        subjects
41            .entry(quin.subject)
42            .or_insert_with(Vec::new)
43            .push(quin);
44    }
45
46    // Write data rows
47    for (_subject, row_quins) in subjects {
48        let mut row_values = Vec::with_capacity(profile.headers.len());
49
50        for (i, &pred_hash) in profile.predicate_hashes.iter().enumerate() {
51            let datatype = profile
52                .datatypes
53                .get(i)
54                .copied()
55                .unwrap_or(CsvDatatype::StringRef);
56
57            // Find quin with matching predicate
58            let value = row_quins
59                .iter()
60                .find(|q| q.predicate == pred_hash)
61                .map(|quin| format_quin_value(quin, datatype))
62                .unwrap_or(String::new());
63
64            row_values.push(value);
65        }
66
67        writeln!(writer, "{}", row_values.join(","))
68            .map_err(|e| format!("Failed to write CSV row: {}", e))?;
69    }
70
71    Ok(())
72}
73
74/// Format a Quin value for CSV output
75fn format_quin_value(quin: &NQuin, datatype: CsvDatatype) -> String {
76    match datatype {
77        CsvDatatype::Integer => {
78            let val = quin.object & 0x0FFF_FFFF_FFFF_FFFF; // Remove type tag
79            val.to_string()
80        }
81        CsvDatatype::Float => {
82            let tag = quin.object & 0xF000_0000_0000_0000;
83            if tag == (0b010 << 60) {
84                let bits = (quin.object & 0xFFFF_FFFF) as u32;
85                let float_val = f32::from_bits(bits);
86                float_val.to_string()
87            } else {
88                String::new()
89            }
90        }
91        CsvDatatype::DateTime => {
92            let tag = quin.object & 0xF000_0000_0000_0000;
93            if tag == (0b011 << 60) {
94                let millis = quin.object & 0x0FFF_FFFF_FFFF_FFFF;
95                let dt = chrono::DateTime::from_timestamp(millis as i64 / 1000, 0);
96                dt.map(|d| d.to_rfc3339()).unwrap_or_default()
97            } else {
98                String::new()
99            }
100        }
101        CsvDatatype::StringRef => {
102            // For strings, we just output the hash (in practice, you'd want lexicon lookup)
103            format!("{:x}", quin.object)
104        }
105    }
106}