sql_cli/data/
datasource_adapter.rs

1use crate::api_client::QueryResponse;
2use crate::csv_datasource::CsvApiClient;
3use crate::datasource_trait::{DataSource, DataSourceQueryResponse};
4use anyhow::Result;
5use std::collections::HashMap;
6use tracing::{debug, info, trace};
7
8/// Adapter to make CsvApiClient implement DataSource trait
9pub struct CsvDataSourceAdapter {
10    client: CsvApiClient,
11}
12
13impl CsvDataSourceAdapter {
14    pub fn new(client: CsvApiClient) -> Self {
15        Self { client }
16    }
17
18    pub fn from_csv_path(path: &str, table_name: &str) -> Result<Self> {
19        info!(target: "datasource", "Loading CSV from path: {}", path);
20        let mut client = CsvApiClient::new();
21        client.load_csv(path, table_name)?;
22
23        // Log metadata about loaded data
24        if let Some(schema) = client.get_schema() {
25            if let Some(columns) = schema.get(table_name) {
26                info!(target: "datasource", "CSV loaded successfully: table='{}', columns={}", 
27                    table_name, columns.len());
28                debug!(target: "datasource", "Column names: {:?}", columns);
29            }
30        }
31
32        Ok(Self { client })
33    }
34
35    pub fn from_json_path(path: &str, table_name: &str) -> Result<Self> {
36        info!(target: "datasource", "Loading JSON from path: {}", path);
37        let mut client = CsvApiClient::new();
38        client.load_json(path, table_name)?;
39
40        // Log metadata about loaded data
41        if let Some(schema) = client.get_schema() {
42            if let Some(columns) = schema.get(table_name) {
43                info!(target: "datasource", "JSON loaded successfully: table='{}', columns={}", 
44                    table_name, columns.len());
45                debug!(target: "datasource", "Column names: {:?}", columns);
46            }
47        }
48
49        Ok(Self { client })
50    }
51
52    /// Get access to the underlying CSV client if needed
53    pub fn inner(&self) -> &CsvApiClient {
54        &self.client
55    }
56
57    pub fn inner_mut(&mut self) -> &mut CsvApiClient {
58        &mut self.client
59    }
60}
61
62impl DataSource for CsvDataSourceAdapter {
63    fn query(&self, sql: &str) -> Result<DataSourceQueryResponse> {
64        debug!(target: "datasource", "Executing query: {}", sql);
65        let response = self.client.query_csv(sql)?;
66        let converted = convert_query_response(response);
67        info!(target: "datasource", "Query returned {} rows with {} columns", 
68            converted.count, converted.columns.len());
69        trace!(target: "datasource", "Query columns: {:?}", converted.columns);
70        Ok(converted)
71    }
72
73    fn query_with_options(
74        &self,
75        sql: &str,
76        case_insensitive: bool,
77    ) -> Result<DataSourceQueryResponse> {
78        // Temporarily set case insensitive for this query
79        let mut temp_client = self.client.clone();
80        temp_client.set_case_insensitive(case_insensitive);
81        let response = temp_client.query_csv(sql)?;
82        Ok(convert_query_response(response))
83    }
84
85    fn get_schema(&self) -> Option<HashMap<String, Vec<String>>> {
86        self.client.get_schema()
87    }
88
89    fn get_table_name(&self) -> String {
90        // Get first table name from schema, or default
91        self.get_schema()
92            .and_then(|schema| schema.keys().next().cloned())
93            .unwrap_or_else(|| "data".to_string())
94    }
95
96    fn get_row_count(&self) -> usize {
97        // This is a bit hacky but works for now
98        // In the future, CsvApiClient should expose row count directly
99        self.query("SELECT * FROM data")
100            .map(|r| r.count)
101            .unwrap_or(0)
102    }
103
104    fn is_case_insensitive(&self) -> bool {
105        // CsvApiClient doesn't expose this, so we track it separately
106        // For now, return false as default
107        false
108    }
109
110    fn set_case_insensitive(&mut self, case_insensitive: bool) {
111        self.client.set_case_insensitive(case_insensitive);
112    }
113
114    fn clone_box(&self) -> Box<dyn DataSource> {
115        Box::new(Self {
116            client: self.client.clone(),
117        })
118    }
119}
120
121/// Convert CsvApiClient's QueryResponse to our DataSourceQueryResponse
122fn convert_query_response(response: QueryResponse) -> DataSourceQueryResponse {
123    // Extract columns from the first row if available
124    let columns = if let Some(first_row) = response.data.first() {
125        if let Some(obj) = first_row.as_object() {
126            obj.keys().cloned().collect()
127        } else {
128            vec![]
129        }
130    } else {
131        vec![]
132    };
133
134    DataSourceQueryResponse {
135        data: response.data,
136        count: response.count,
137        columns,
138        table_name: "data".to_string(), // Default table name
139    }
140}