1use crate::buffer::BufferAPI;
2use crate::data::data_provider::DataProvider;
3use anyhow::{anyhow, Result};
4use chrono::Local;
5use serde_json::Value;
6use std::fs::File;
7use std::io::Write;
8
9pub struct DataExporter;
11
12impl DataExporter {
13 pub fn export_provider_to_csv(provider: &dyn DataProvider) -> Result<String> {
15 let row_count = provider.get_row_count();
16 if row_count == 0 {
17 return Err(anyhow!("No data to export"));
18 }
19
20 let timestamp = Local::now().format("%Y%m%d_%H%M%S");
22 let filename = format!("query_results_{}.csv", timestamp);
23
24 let mut file = File::create(&filename)?;
25
26 let headers = provider.get_column_names();
28 let header_line = headers.join(",");
29 writeln!(file, "{}", header_line)?;
30
31 for i in 0..row_count {
33 if let Some(row) = provider.get_row(i) {
34 let escaped_row: Vec<String> = row
35 .iter()
36 .map(|field| Self::escape_csv_field(field))
37 .collect();
38 let row_line = escaped_row.join(",");
39 writeln!(file, "{}", row_line)?;
40 }
41 }
42
43 Ok(format!(
44 "✓ Exported {} rows to CSV file: {}",
45 row_count, filename
46 ))
47 }
48
49 pub fn export_provider_to_json(provider: &dyn DataProvider) -> Result<String> {
51 let row_count = provider.get_row_count();
52 if row_count == 0 {
53 return Err(anyhow!("No data to export"));
54 }
55
56 let timestamp = Local::now().format("%Y%m%d_%H%M%S");
58 let filename = format!("query_results_{}.json", timestamp);
59
60 let headers = provider.get_column_names();
62 let mut json_array = Vec::new();
63
64 for i in 0..row_count {
65 if let Some(row) = provider.get_row(i) {
66 let mut json_obj = serde_json::Map::new();
67 for (j, value) in row.iter().enumerate() {
68 if j < headers.len() {
69 json_obj.insert(headers[j].clone(), Value::String(value.clone()));
70 }
71 }
72 json_array.push(Value::Object(json_obj));
73 }
74 }
75
76 let file = File::create(&filename)?;
77 serde_json::to_writer_pretty(file, &json_array)?;
78
79 Ok(format!(
80 "✓ Exported {} rows to JSON file: {}",
81 row_count, filename
82 ))
83 }
84
85 pub fn export_to_csv(buffer: &dyn BufferAPI) -> Result<String> {
87 let datatable = buffer
88 .get_datatable()
89 .ok_or_else(|| anyhow!("No results to export - run a query first"))?;
90
91 if datatable.row_count() == 0 {
92 return Err(anyhow!("No data to export"));
93 }
94
95 let timestamp = Local::now().format("%Y%m%d_%H%M%S");
97 let filename = format!("query_results_{}.csv", timestamp);
98
99 let mut file = File::create(&filename)?;
100
101 let headers = datatable.column_names();
103 let header_line = headers.join(",");
104 writeln!(file, "{}", header_line)?;
105
106 let mut row_count = 0;
108 for row_data in datatable.to_string_table() {
109 let row: Vec<String> = row_data.iter().map(|s| Self::escape_csv_field(s)).collect();
110 let row_line = row.join(",");
111 writeln!(file, "{}", row_line)?;
112 row_count += 1;
113 }
114
115 Ok(format!(
116 "✓ Exported {} rows to CSV file: {}",
117 row_count, filename
118 ))
119 }
120
121 pub fn export_to_json(buffer: &dyn BufferAPI, include_filtered: bool) -> Result<String> {
123 let datatable = buffer
126 .get_datatable()
127 .ok_or_else(|| anyhow!("No results to export - run a query first"))?;
128
129 let data = Self::datatable_to_json_values(datatable);
131
132 let data_to_export = if include_filtered && buffer.is_filter_active() {
134 Self::get_filtered_data(buffer)?
135 } else if include_filtered && buffer.is_fuzzy_filter_active() {
136 Self::get_fuzzy_filtered_data(buffer)?
137 } else {
138 data.clone()
139 };
140
141 let timestamp = Local::now().format("%Y%m%d_%H%M%S");
143 let filename = format!("query_results_{}.json", timestamp);
144
145 let file = File::create(&filename)?;
146 serde_json::to_writer_pretty(file, &data_to_export)?;
147
148 let filter_info =
149 if include_filtered && (buffer.is_filter_active() || buffer.is_fuzzy_filter_active()) {
150 " (filtered)"
151 } else {
152 ""
153 };
154
155 Ok(format!(
156 "✓ Exported{} {} rows to JSON file: {}",
157 filter_info,
158 data_to_export.len(),
159 filename
160 ))
161 }
162
163 pub fn export_selected_to_csv(
165 buffer: &dyn BufferAPI,
166 selected_rows: &[usize],
167 ) -> Result<String> {
168 let datatable = buffer
169 .get_datatable()
170 .ok_or_else(|| anyhow!("No results to export"))?;
171 let data = Self::datatable_to_json_values(datatable);
172
173 if selected_rows.is_empty() {
174 return Err(anyhow!("No rows selected"));
175 }
176
177 let first_row_idx = selected_rows[0];
179 let first_row = data
180 .get(first_row_idx)
181 .ok_or_else(|| anyhow!("Invalid row index"))?;
182
183 let obj = first_row
184 .as_object()
185 .ok_or_else(|| anyhow!("Invalid data format"))?;
186
187 let timestamp = Local::now().format("%Y%m%d_%H%M%S");
189 let filename = format!("selected_rows_{}.csv", timestamp);
190
191 let mut file = File::create(&filename)?;
192
193 let headers: Vec<&str> = obj.keys().map(|k| k.as_str()).collect();
195 let header_line = headers.join(",");
196 writeln!(file, "{}", header_line)?;
197
198 let mut row_count = 0;
200 for &row_idx in selected_rows {
201 if let Some(item) = data.get(row_idx) {
202 if let Some(obj) = item.as_object() {
203 let row: Vec<String> = headers
204 .iter()
205 .map(|&header| match obj.get(header) {
206 Some(Value::String(s)) => Self::escape_csv_field(s),
207 Some(Value::Number(n)) => n.to_string(),
208 Some(Value::Bool(b)) => b.to_string(),
209 Some(Value::Null) => String::new(),
210 Some(other) => Self::escape_csv_field(&other.to_string()),
211 None => String::new(),
212 })
213 .collect();
214
215 let row_line = row.join(",");
216 writeln!(file, "{}", row_line)?;
217 row_count += 1;
218 }
219 }
220 }
221
222 Ok(format!(
223 "Exported {} selected rows to {}",
224 row_count, filename
225 ))
226 }
227
228 fn escape_csv_field(field: &str) -> String {
230 if field.contains(',') || field.contains('"') || field.contains('\n') {
231 format!("\"{}\"", field.replace('"', "\"\""))
233 } else {
234 field.to_string()
235 }
236 }
237
238 fn get_filtered_data(buffer: &dyn BufferAPI) -> Result<Vec<Value>> {
240 let datatable = buffer
241 .get_datatable()
242 .ok_or_else(|| anyhow!("No results available"))?;
243 let data = Self::datatable_to_json_values(datatable);
244
245 let filter_pattern = buffer.get_filter_pattern();
246 if filter_pattern.is_empty() {
247 return Ok(data.clone());
248 }
249
250 let regex = regex::Regex::new(&filter_pattern)
251 .map_err(|e| anyhow!("Invalid filter pattern: {}", e))?;
252
253 let filtered: Vec<Value> = data
254 .iter()
255 .filter(|item| {
256 if let Some(obj) = item.as_object() {
257 obj.values().any(|v| {
258 let text = match v {
259 Value::String(s) => s.clone(),
260 Value::Number(n) => n.to_string(),
261 Value::Bool(b) => b.to_string(),
262 _ => String::new(),
263 };
264 regex.is_match(&text)
265 })
266 } else {
267 false
268 }
269 })
270 .cloned()
271 .collect();
272
273 Ok(filtered)
274 }
275
276 fn get_fuzzy_filtered_data(buffer: &dyn BufferAPI) -> Result<Vec<Value>> {
278 let datatable = buffer
279 .get_datatable()
280 .ok_or_else(|| anyhow!("No results available"))?;
281 let data = Self::datatable_to_json_values(datatable);
282
283 let indices = buffer.get_fuzzy_filter_indices();
284 if indices.is_empty() {
285 return Ok(data.clone());
286 }
287
288 let filtered: Vec<Value> = indices
289 .iter()
290 .filter_map(|&idx| data.get(idx).cloned())
291 .collect();
292
293 Ok(filtered)
294 }
295
296 pub fn format_for_clipboard(value: &Value, _header: &str) -> String {
298 match value {
299 Value::String(s) => s.clone(),
300 Value::Number(n) => n.to_string(),
301 Value::Bool(b) => b.to_string(),
302 Value::Null => "NULL".to_string(),
303 other => other.to_string(),
304 }
305 }
306
307 pub fn format_row_for_clipboard(row: &serde_json::Map<String, Value>) -> String {
309 let values: Vec<String> = row
310 .values()
311 .map(|v| Self::format_for_clipboard(v, ""))
312 .collect();
313 values.join("\t")
314 }
315
316 pub fn convert_json_to_strings(data: &[Value]) -> Vec<Vec<String>> {
318 if let Some(first_row) = data.first() {
319 if let Some(obj) = first_row.as_object() {
320 let headers: Vec<&str> = obj.keys().map(|k| k.as_str()).collect();
321
322 data.iter()
323 .map(|item| {
324 if let Some(obj) = item.as_object() {
325 headers
326 .iter()
327 .map(|&header| match obj.get(header) {
328 Some(Value::String(s)) => s.clone(),
329 Some(Value::Number(n)) => n.to_string(),
330 Some(Value::Bool(b)) => b.to_string(),
331 Some(Value::Null) => "".to_string(),
332 Some(other) => other.to_string(),
333 None => "".to_string(),
334 })
335 .collect()
336 } else {
337 vec![]
338 }
339 })
340 .collect()
341 } else {
342 vec![]
343 }
344 } else {
345 vec![]
346 }
347 }
348
349 pub fn generate_csv_text(data: &[Value]) -> Option<String> {
351 let first_row = data.first()?;
352 let obj = first_row.as_object()?;
353 let headers: Vec<&str> = obj.keys().map(|k| k.as_str()).collect();
354
355 let mut csv_text = headers.join(",") + "\n";
357
358 for row in data {
359 if let Some(obj) = row.as_object() {
360 let values: Vec<String> = headers
361 .iter()
362 .map(|&header| match obj.get(header) {
363 Some(Value::String(s)) => Self::escape_csv_field(s),
364 Some(Value::Number(n)) => n.to_string(),
365 Some(Value::Bool(b)) => b.to_string(),
366 Some(Value::Null) => String::new(),
367 Some(other) => Self::escape_csv_field(&other.to_string()),
368 None => String::new(),
369 })
370 .collect();
371 csv_text.push_str(&values.join(","));
372 csv_text.push('\n');
373 }
374 }
375
376 Some(csv_text)
377 }
378
379 pub fn generate_tsv_text(data: &[Value]) -> Option<String> {
381 let first_row = data.first()?;
382 let obj = first_row.as_object()?;
383 let headers: Vec<&str> = obj.keys().map(|k| k.as_str()).collect();
384
385 let mut tsv_text = headers.join("\t") + "\r\n";
387
388 for row in data {
389 if let Some(obj) = row.as_object() {
390 let values: Vec<String> = headers
391 .iter()
392 .map(|&header| match obj.get(header) {
393 Some(Value::String(s)) => {
394 s.replace('\t', " ").replace('\n', " ").replace('\r', "")
395 }
396 Some(Value::Number(n)) => n.to_string(),
397 Some(Value::Bool(b)) => b.to_string(),
398 Some(Value::Null) => String::new(),
399 Some(other) => other
400 .to_string()
401 .replace('\t', " ")
402 .replace('\n', " ")
403 .replace('\r', ""),
404 None => String::new(),
405 })
406 .collect();
407 tsv_text.push_str(&values.join("\t"));
408 tsv_text.push_str("\r\n");
409 }
410 }
411
412 Some(tsv_text)
413 }
414
415 pub fn datatable_to_json_values(datatable: &crate::data::datatable::DataTable) -> Vec<Value> {
417 use serde_json::json;
418
419 let headers = datatable.column_names();
420 let mut result = Vec::new();
421
422 for row_data in datatable.to_string_table() {
423 let mut obj = serde_json::Map::new();
424 for (i, header) in headers.iter().enumerate() {
425 if let Some(value) = row_data.get(i) {
426 obj.insert(header.clone(), json!(value));
427 }
428 }
429 result.push(Value::Object(obj));
430 }
431
432 result
433 }
434}