sql_cli/data/
csv_fixes.rs

1// Helper functions for CSV handling improvements
2
3use std::collections::HashMap;
4
5/// Check if a column name needs quoting (contains spaces or special characters)
6pub fn needs_quoting(column_name: &str) -> bool {
7    column_name.contains(' ')
8        || column_name.contains('-')
9        || column_name.contains('.')
10        || column_name.contains('(')
11        || column_name.contains(')')
12        || column_name.contains('[')
13        || column_name.contains(']')
14        || column_name.contains('"')
15        || column_name.contains('\'')
16}
17
18/// Quote a column name if necessary
19pub fn quote_if_needed(column_name: &str) -> String {
20    if needs_quoting(column_name) {
21        format!("\"{}\"", column_name.replace('"', "\"\""))
22    } else {
23        column_name.to_string()
24    }
25}
26
27/// Build a case-insensitive lookup map for column names
28/// Maps lowercase column names to their original case versions
29pub fn build_column_lookup(headers: &[String]) -> HashMap<String, String> {
30    let mut lookup = HashMap::new();
31    for header in headers {
32        lookup.insert(header.to_lowercase(), header.clone());
33    }
34    lookup
35}
36
37/// Find a column by name (case-insensitive)
38pub fn find_column_case_insensitive<'a>(
39    obj: &'a serde_json::Map<String, serde_json::Value>,
40    column_name: &str,
41    lookup: &HashMap<String, String>,
42) -> Option<(&'a String, &'a serde_json::Value)> {
43    // First try exact match
44    if let Some(value) = obj.get_key_value(column_name) {
45        return Some(value);
46    }
47
48    // Then try case-insensitive match using lookup
49    if let Some(actual_name) = lookup.get(&column_name.to_lowercase()) {
50        obj.get_key_value(actual_name)
51    } else {
52        // Fallback: linear search (for quoted columns)
53        let column_unquoted = column_name.trim_matches('"');
54        for (key, value) in obj {
55            if key == column_unquoted || key.to_lowercase() == column_unquoted.to_lowercase() {
56                return Some((key, value));
57            }
58        }
59        None
60    }
61}
62
63/// Parse a column name that might be quoted
64pub fn parse_column_name(column: &str) -> &str {
65    column.trim().trim_matches('"').trim_matches('\'')
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_needs_quoting() {
74        assert!(!needs_quoting("City"));
75        assert!(!needs_quoting("customer_id"));
76        assert!(needs_quoting("Phone 1"));
77        assert!(needs_quoting("Customer-ID"));
78        assert!(needs_quoting("Price ($)"));
79    }
80
81    #[test]
82    fn test_quote_if_needed() {
83        assert_eq!(quote_if_needed("City"), "City");
84        assert_eq!(quote_if_needed("Phone 1"), "\"Phone 1\"");
85        assert_eq!(quote_if_needed("Has\"Quote"), "\"Has\"\"Quote\"");
86    }
87}