sql_cli/data/adapters/
csv_client_adapter.rs1use crate::api_client::QueryResponse;
7use crate::data::csv_datasource::CsvApiClient;
8use crate::data::data_provider::DataProvider;
9use std::fmt::Debug;
10
11pub struct CsvClientAdapter<'a> {
15 client: &'a CsvApiClient,
16 cached_response: Option<QueryResponse>,
17}
18
19impl<'a> CsvClientAdapter<'a> {
20 #[must_use]
23 pub fn new(client: &'a CsvApiClient) -> Self {
24 Self {
25 client,
26 cached_response: None,
27 }
28 }
29
30 pub fn execute_query(&mut self, sql: &str) -> anyhow::Result<()> {
32 let response = self.client.query_csv(sql)?;
33 self.cached_response = Some(response);
34 Ok(())
35 }
36}
37
38impl Debug for CsvClientAdapter<'_> {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("CsvClientAdapter")
41 .field("row_count", &self.get_row_count())
42 .field("column_count", &self.get_column_count())
43 .field("has_data", &self.cached_response.is_some())
44 .finish()
45 }
46}
47
48impl DataProvider for CsvClientAdapter<'_> {
49 fn get_row(&self, index: usize) -> Option<Vec<String>> {
50 self.cached_response.as_ref().and_then(|response| {
51 response.data.get(index).map(|json_value| {
52 if let Some(obj) = json_value.as_object() {
54 let columns = self.get_column_names();
56 columns
57 .iter()
58 .map(|col| {
59 obj.get(col)
60 .map(|v| {
61 match v {
63 serde_json::Value::String(s) => s.clone(),
64 serde_json::Value::Null => String::new(),
65 other => other.to_string(),
66 }
67 })
68 .unwrap_or_default()
69 })
70 .collect()
71 } else {
72 vec![json_value.to_string()]
74 }
75 })
76 })
77 }
78
79 fn get_column_names(&self) -> Vec<String> {
80 if let Some(ref response) = self.cached_response {
82 if let Some(first_row) = response.data.first() {
84 if let Some(obj) = first_row.as_object() {
85 return obj.keys().map(std::string::ToString::to_string).collect();
86 }
87 }
88 }
89
90 self.client
92 .get_schema()
93 .and_then(|schema| schema.values().next().cloned())
94 .unwrap_or_default()
95 }
96
97 fn get_row_count(&self) -> usize {
98 self.cached_response.as_ref().map_or(0, |r| r.data.len())
99 }
100
101 fn get_column_count(&self) -> usize {
102 self.get_column_names().len()
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::data::csv_datasource::CsvApiClient;
110 use serde_json::json;
111 use std::fs;
112 use tempfile::tempdir;
113
114 #[test]
115 fn test_csv_client_adapter_basic() {
116 let temp_dir = tempdir().unwrap();
118 let json_path = temp_dir.path().join("test_data.json");
119
120 let test_data = json!([
121 {
122 "id": 1,
123 "name": "Alice",
124 "age": 30
125 },
126 {
127 "id": 2,
128 "name": "Bob",
129 "age": 25
130 },
131 {
132 "id": 3,
133 "name": "Charlie",
134 "age": 35
135 }
136 ]);
137
138 fs::write(&json_path, serde_json::to_string(&test_data).unwrap()).unwrap();
139
140 let mut client = CsvApiClient::new();
141 client.load_json(&json_path, "test").unwrap();
142
143 let mut adapter = CsvClientAdapter::new(&client);
145 adapter.execute_query("SELECT * FROM test").unwrap();
146
147 assert_eq!(adapter.get_row_count(), 3);
149 assert_eq!(adapter.get_column_count(), 3);
150
151 let col_names = adapter.get_column_names();
152 assert!(col_names.contains(&"id".to_string()));
153 assert!(col_names.contains(&"name".to_string()));
154 assert!(col_names.contains(&"age".to_string()));
155
156 let row = adapter.get_row(0).unwrap();
158 assert!(row.contains(&"1".to_string()));
159 assert!(row.contains(&"Alice".to_string()));
160 assert!(row.contains(&"30".to_string()));
161
162 let row = adapter.get_row(2).unwrap();
163 assert!(row.contains(&"3".to_string()));
164 assert!(row.contains(&"Charlie".to_string()));
165 assert!(row.contains(&"35".to_string()));
166
167 assert!(adapter.get_row(3).is_none());
169 }
170
171 #[test]
172 fn test_csv_client_adapter_empty() {
173 let client = CsvApiClient::new();
174 let adapter = CsvClientAdapter::new(&client);
175
176 assert_eq!(adapter.get_row_count(), 0);
178 assert_eq!(adapter.get_column_count(), 0);
179 assert!(adapter.get_row(0).is_none());
180 }
181
182 #[test]
183 fn test_csv_client_adapter_with_filter() {
184 let temp_dir = tempdir().unwrap();
186 let json_path = temp_dir.path().join("test_data.json");
187
188 let test_data = json!([
189 {
190 "id": 1,
191 "name": "Alice",
192 "status": "active"
193 },
194 {
195 "id": 2,
196 "name": "Bob",
197 "status": "inactive"
198 },
199 {
200 "id": 3,
201 "name": "Charlie",
202 "status": "active"
203 }
204 ]);
205
206 fs::write(&json_path, serde_json::to_string(&test_data).unwrap()).unwrap();
207
208 let mut client = CsvApiClient::new();
209 client.load_json(&json_path, "test").unwrap();
210
211 let mut adapter = CsvClientAdapter::new(&client);
213 adapter
214 .execute_query("SELECT * FROM test WHERE status = 'active'")
215 .unwrap();
216
217 assert_eq!(adapter.get_row_count(), 2);
219
220 for i in 0..adapter.get_row_count() {
222 let row = adapter.get_row(i).unwrap();
223 assert!(row.contains(&"active".to_string()));
224 }
225 }
226}