1use anyhow::{Context, Result};
2use std::fs;
3use std::io::{self, Write};
4use std::path::Path;
5use std::time::Instant;
6use tracing::{debug, info};
7
8use crate::data::data_view::DataView;
9use crate::data::datatable::{DataTable, DataValue};
10use crate::data::datatable_loaders::{load_csv_to_datatable, load_json_to_datatable};
11use crate::data::query_engine::QueryEngine;
12use crate::services::query_execution_service::QueryExecutionService;
13
14#[derive(Debug, Clone)]
16pub enum OutputFormat {
17 Csv,
18 Json,
19 Table,
20 Tsv,
21}
22
23impl OutputFormat {
24 pub fn from_str(s: &str) -> Result<Self> {
25 match s.to_lowercase().as_str() {
26 "csv" => Ok(OutputFormat::Csv),
27 "json" => Ok(OutputFormat::Json),
28 "table" => Ok(OutputFormat::Table),
29 "tsv" => Ok(OutputFormat::Tsv),
30 _ => Err(anyhow::anyhow!(
31 "Invalid output format: {}. Use csv, json, table, or tsv",
32 s
33 )),
34 }
35 }
36}
37
38pub struct NonInteractiveConfig {
40 pub data_file: String,
41 pub query: String,
42 pub output_format: OutputFormat,
43 pub output_file: Option<String>,
44 pub case_insensitive: bool,
45 pub auto_hide_empty: bool,
46 pub limit: Option<usize>,
47}
48
49pub fn execute_non_interactive(config: NonInteractiveConfig) -> Result<()> {
51 let start_time = Instant::now();
52
53 info!("Loading data from: {}", config.data_file);
55 let data_table = load_data_file(&config.data_file)?;
56 let table_name = data_table.name.clone();
57
58 info!(
59 "Loaded {} rows with {} columns",
60 data_table.row_count(),
61 data_table.column_count()
62 );
63
64 let dataview = DataView::new(std::sync::Arc::new(data_table));
66
67 info!("Executing query: {}", config.query);
69 let query_start = Instant::now();
70
71 let query_service = QueryExecutionService::new(config.case_insensitive, config.auto_hide_empty);
73 let result = query_service.execute(&config.query, Some(&dataview), Some(dataview.source()))?;
74
75 let query_time = query_start.elapsed();
76 info!("Query executed in {:?}", query_time);
77 info!(
78 "Result: {} rows, {} columns",
79 result.dataview.row_count(),
80 result.dataview.column_count()
81 );
82
83 let final_view = if let Some(limit) = config.limit {
85 let limited_table = limit_results(&result.dataview, limit)?;
86 DataView::new(std::sync::Arc::new(limited_table))
87 } else {
88 result.dataview
89 };
90
91 let output_result = match config.output_file {
93 Some(ref path) => {
94 let mut file = fs::File::create(path)
95 .with_context(|| format!("Failed to create output file: {}", path))?;
96 output_results(&final_view, config.output_format, &mut file)?;
97 info!("Results written to: {}", path);
98 Ok(())
99 }
100 None => {
101 output_results(&final_view, config.output_format, &mut io::stdout())?;
102 Ok(())
103 }
104 };
105
106 let total_time = start_time.elapsed();
107 debug!("Total execution time: {:?}", total_time);
108
109 if config.output_file.is_none() {
111 eprintln!(
112 "\n# Query completed: {} rows in {:?}",
113 final_view.row_count(),
114 query_time
115 );
116 }
117
118 output_result
119}
120
121fn load_data_file(path: &str) -> Result<DataTable> {
123 let path = Path::new(path);
124
125 if !path.exists() {
126 return Err(anyhow::anyhow!("File not found: {}", path.display()));
127 }
128
129 let extension = path
131 .extension()
132 .and_then(|ext| ext.to_str())
133 .map(|s| s.to_lowercase())
134 .unwrap_or_default();
135
136 let table_name = path
137 .file_stem()
138 .and_then(|stem| stem.to_str())
139 .unwrap_or("data")
140 .to_string();
141
142 match extension.as_str() {
143 "csv" => load_csv_to_datatable(path, &table_name)
144 .with_context(|| format!("Failed to load CSV file: {}", path.display())),
145 "json" => load_json_to_datatable(path, &table_name)
146 .with_context(|| format!("Failed to load JSON file: {}", path.display())),
147 _ => Err(anyhow::anyhow!(
148 "Unsupported file type: {}. Use .csv or .json",
149 extension
150 )),
151 }
152}
153
154fn limit_results(dataview: &DataView, limit: usize) -> Result<DataTable> {
156 let source = dataview.source();
157 let mut limited_table = DataTable::new(&source.name);
158
159 for col in source.columns.iter() {
161 limited_table.add_column(col.clone());
162 }
163
164 let rows_to_copy = dataview.row_count().min(limit);
166 for i in 0..rows_to_copy {
167 if let Some(row) = dataview.get_row(i) {
168 limited_table.add_row(row.clone());
169 }
170 }
171
172 Ok(limited_table)
173}
174
175fn output_results<W: Write>(
177 dataview: &DataView,
178 format: OutputFormat,
179 writer: &mut W,
180) -> Result<()> {
181 match format {
182 OutputFormat::Csv => output_csv(dataview, writer, ','),
183 OutputFormat::Tsv => output_csv(dataview, writer, '\t'),
184 OutputFormat::Json => output_json(dataview, writer),
185 OutputFormat::Table => output_table(dataview, writer),
186 }
187}
188
189fn output_csv<W: Write>(dataview: &DataView, writer: &mut W, delimiter: char) -> Result<()> {
191 let columns = dataview.column_names();
193 for (i, col) in columns.iter().enumerate() {
194 if i > 0 {
195 write!(writer, "{}", delimiter)?;
196 }
197 write!(writer, "{}", escape_csv_field(col, delimiter))?;
198 }
199 writeln!(writer)?;
200
201 for row_idx in 0..dataview.row_count() {
203 if let Some(row) = dataview.get_row(row_idx) {
204 for (i, value) in row.values.iter().enumerate() {
205 if i > 0 {
206 write!(writer, "{}", delimiter)?;
207 }
208 write!(
209 writer,
210 "{}",
211 escape_csv_field(&format_value(value), delimiter)
212 )?;
213 }
214 writeln!(writer)?;
215 }
216 }
217
218 Ok(())
219}
220
221fn output_json<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
223 let columns = dataview.column_names();
224 let mut rows = Vec::new();
225
226 for row_idx in 0..dataview.row_count() {
227 if let Some(row) = dataview.get_row(row_idx) {
228 let mut json_row = serde_json::Map::new();
229 for (col_idx, value) in row.values.iter().enumerate() {
230 if col_idx < columns.len() {
231 json_row.insert(columns[col_idx].clone(), value_to_json(value));
232 }
233 }
234 rows.push(serde_json::Value::Object(json_row));
235 }
236 }
237
238 let json = serde_json::to_string_pretty(&rows)?;
239 writeln!(writer, "{}", json)?;
240
241 Ok(())
242}
243
244fn output_table<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
246 let columns = dataview.column_names();
247
248 let mut widths = vec![0; columns.len()];
250 for (i, col) in columns.iter().enumerate() {
251 widths[i] = col.len();
252 }
253
254 let sample_size = dataview.row_count().min(100);
256 for row_idx in 0..sample_size {
257 if let Some(row) = dataview.get_row(row_idx) {
258 for (i, value) in row.values.iter().enumerate() {
259 if i < widths.len() {
260 let value_str = format_value(value);
261 widths[i] = widths[i].max(value_str.len());
262 }
263 }
264 }
265 }
266
267 for width in widths.iter_mut() {
269 *width = (*width).min(50);
270 }
271
272 write!(writer, "+")?;
274 for width in &widths {
275 write!(writer, "-{}-+", "-".repeat(*width))?;
276 }
277 writeln!(writer)?;
278
279 write!(writer, "|")?;
281 for (i, col) in columns.iter().enumerate() {
282 write!(writer, " {:^width$} |", col, width = widths[i])?;
283 }
284 writeln!(writer)?;
285
286 write!(writer, "+")?;
288 for width in &widths {
289 write!(writer, "-{}-+", "-".repeat(*width))?;
290 }
291 writeln!(writer)?;
292
293 for row_idx in 0..dataview.row_count() {
295 if let Some(row) = dataview.get_row(row_idx) {
296 write!(writer, "|")?;
297 for (i, value) in row.values.iter().enumerate() {
298 if i < widths.len() {
299 let value_str = format_value(value);
300 let truncated = if value_str.len() > widths[i] {
301 format!("{}...", &value_str[..widths[i] - 3])
302 } else {
303 value_str
304 };
305 write!(writer, " {:<width$} |", truncated, width = widths[i])?;
306 }
307 }
308 writeln!(writer)?;
309 }
310 }
311
312 write!(writer, "+")?;
314 for width in &widths {
315 write!(writer, "-{}-+", "-".repeat(*width))?;
316 }
317 writeln!(writer)?;
318
319 Ok(())
320}
321
322fn format_value(value: &DataValue) -> String {
324 match value {
325 DataValue::Null => "".to_string(),
326 DataValue::Integer(i) => i.to_string(),
327 DataValue::Float(f) => f.to_string(),
328 DataValue::String(s) => s.clone(),
329 DataValue::InternedString(s) => s.to_string(),
330 DataValue::Boolean(b) => b.to_string(),
331 DataValue::DateTime(dt) => dt.to_string(),
332 }
333}
334
335fn value_to_json(value: &DataValue) -> serde_json::Value {
337 match value {
338 DataValue::Null => serde_json::Value::Null,
339 DataValue::Integer(i) => serde_json::Value::Number((*i).into()),
340 DataValue::Float(f) => {
341 if let Some(n) = serde_json::Number::from_f64(*f) {
342 serde_json::Value::Number(n)
343 } else {
344 serde_json::Value::Null
345 }
346 }
347 DataValue::String(s) => serde_json::Value::String(s.clone()),
348 DataValue::InternedString(s) => serde_json::Value::String(s.to_string()),
349 DataValue::Boolean(b) => serde_json::Value::Bool(*b),
350 DataValue::DateTime(dt) => serde_json::Value::String(dt.to_string()),
351 }
352}
353
354fn escape_csv_field(field: &str, delimiter: char) -> String {
356 if field.contains(delimiter)
357 || field.contains('"')
358 || field.contains('\n')
359 || field.contains('\r')
360 {
361 format!("\"{}\"", field.replace('"', "\"\""))
362 } else {
363 field.to_string()
364 }
365}