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 pub query_plan: bool,
48}
49
50pub fn execute_non_interactive(config: NonInteractiveConfig) -> Result<()> {
52 let start_time = Instant::now();
53
54 info!("Loading data from: {}", config.data_file);
56 let data_table = load_data_file(&config.data_file)?;
57 let table_name = data_table.name.clone();
58
59 info!(
60 "Loaded {} rows with {} columns",
61 data_table.row_count(),
62 data_table.column_count()
63 );
64
65 let dataview = DataView::new(std::sync::Arc::new(data_table));
67
68 info!("Executing query: {}", config.query);
70
71 if config.query_plan {
73 use crate::sql::recursive_parser::Parser;
74 let mut parser = Parser::new(&config.query);
75 match parser.parse() {
76 Ok(statement) => {
77 println!("\n=== QUERY PLAN (AST) ===");
78 println!("{:#?}", statement);
79 println!("=== END QUERY PLAN ===\n");
80 }
81 Err(e) => {
82 eprintln!("Failed to parse query for plan: {}", e);
83 }
84 }
85 }
86
87 let query_start = Instant::now();
88
89 let query_service = QueryExecutionService::new(config.case_insensitive, config.auto_hide_empty);
91 let result = query_service.execute(&config.query, Some(&dataview), Some(dataview.source()))?;
92
93 let query_time = query_start.elapsed();
94 info!("Query executed in {:?}", query_time);
95 info!(
96 "Result: {} rows, {} columns",
97 result.dataview.row_count(),
98 result.dataview.column_count()
99 );
100
101 let final_view = if let Some(limit) = config.limit {
103 let limited_table = limit_results(&result.dataview, limit)?;
104 DataView::new(std::sync::Arc::new(limited_table))
105 } else {
106 result.dataview
107 };
108
109 let output_result = match config.output_file {
111 Some(ref path) => {
112 let mut file = fs::File::create(path)
113 .with_context(|| format!("Failed to create output file: {}", path))?;
114 output_results(&final_view, config.output_format, &mut file)?;
115 info!("Results written to: {}", path);
116 Ok(())
117 }
118 None => {
119 output_results(&final_view, config.output_format, &mut io::stdout())?;
120 Ok(())
121 }
122 };
123
124 let total_time = start_time.elapsed();
125 debug!("Total execution time: {:?}", total_time);
126
127 if config.output_file.is_none() {
129 eprintln!(
130 "\n# Query completed: {} rows in {:?}",
131 final_view.row_count(),
132 query_time
133 );
134 }
135
136 output_result
137}
138
139fn load_data_file(path: &str) -> Result<DataTable> {
141 let path = Path::new(path);
142
143 if !path.exists() {
144 return Err(anyhow::anyhow!("File not found: {}", path.display()));
145 }
146
147 let extension = path
149 .extension()
150 .and_then(|ext| ext.to_str())
151 .map(|s| s.to_lowercase())
152 .unwrap_or_default();
153
154 let table_name = path
155 .file_stem()
156 .and_then(|stem| stem.to_str())
157 .unwrap_or("data")
158 .to_string();
159
160 match extension.as_str() {
161 "csv" => load_csv_to_datatable(path, &table_name)
162 .with_context(|| format!("Failed to load CSV file: {}", path.display())),
163 "json" => load_json_to_datatable(path, &table_name)
164 .with_context(|| format!("Failed to load JSON file: {}", path.display())),
165 _ => Err(anyhow::anyhow!(
166 "Unsupported file type: {}. Use .csv or .json",
167 extension
168 )),
169 }
170}
171
172fn limit_results(dataview: &DataView, limit: usize) -> Result<DataTable> {
174 let source = dataview.source();
175 let mut limited_table = DataTable::new(&source.name);
176
177 for col in source.columns.iter() {
179 limited_table.add_column(col.clone());
180 }
181
182 let rows_to_copy = dataview.row_count().min(limit);
184 for i in 0..rows_to_copy {
185 if let Some(row) = dataview.get_row(i) {
186 limited_table.add_row(row.clone());
187 }
188 }
189
190 Ok(limited_table)
191}
192
193fn output_results<W: Write>(
195 dataview: &DataView,
196 format: OutputFormat,
197 writer: &mut W,
198) -> Result<()> {
199 match format {
200 OutputFormat::Csv => output_csv(dataview, writer, ','),
201 OutputFormat::Tsv => output_csv(dataview, writer, '\t'),
202 OutputFormat::Json => output_json(dataview, writer),
203 OutputFormat::Table => output_table(dataview, writer),
204 }
205}
206
207fn output_csv<W: Write>(dataview: &DataView, writer: &mut W, delimiter: char) -> Result<()> {
209 let columns = dataview.column_names();
211 for (i, col) in columns.iter().enumerate() {
212 if i > 0 {
213 write!(writer, "{}", delimiter)?;
214 }
215 write!(writer, "{}", escape_csv_field(col, delimiter))?;
216 }
217 writeln!(writer)?;
218
219 for row_idx in 0..dataview.row_count() {
221 if let Some(row) = dataview.get_row(row_idx) {
222 for (i, value) in row.values.iter().enumerate() {
223 if i > 0 {
224 write!(writer, "{}", delimiter)?;
225 }
226 write!(
227 writer,
228 "{}",
229 escape_csv_field(&format_value(value), delimiter)
230 )?;
231 }
232 writeln!(writer)?;
233 }
234 }
235
236 Ok(())
237}
238
239fn output_json<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
241 let columns = dataview.column_names();
242 let mut rows = Vec::new();
243
244 for row_idx in 0..dataview.row_count() {
245 if let Some(row) = dataview.get_row(row_idx) {
246 let mut json_row = serde_json::Map::new();
247 for (col_idx, value) in row.values.iter().enumerate() {
248 if col_idx < columns.len() {
249 json_row.insert(columns[col_idx].clone(), value_to_json(value));
250 }
251 }
252 rows.push(serde_json::Value::Object(json_row));
253 }
254 }
255
256 let json = serde_json::to_string_pretty(&rows)?;
257 writeln!(writer, "{}", json)?;
258
259 Ok(())
260}
261
262fn output_table<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
264 let columns = dataview.column_names();
265
266 let mut widths = vec![0; columns.len()];
268 for (i, col) in columns.iter().enumerate() {
269 widths[i] = col.len();
270 }
271
272 let sample_size = dataview.row_count().min(100);
274 for row_idx in 0..sample_size {
275 if let Some(row) = dataview.get_row(row_idx) {
276 for (i, value) in row.values.iter().enumerate() {
277 if i < widths.len() {
278 let value_str = format_value(value);
279 widths[i] = widths[i].max(value_str.len());
280 }
281 }
282 }
283 }
284
285 for width in widths.iter_mut() {
287 *width = (*width).min(50);
288 }
289
290 write!(writer, "+")?;
292 for width in &widths {
293 write!(writer, "-{}-+", "-".repeat(*width))?;
294 }
295 writeln!(writer)?;
296
297 write!(writer, "|")?;
299 for (i, col) in columns.iter().enumerate() {
300 write!(writer, " {:^width$} |", col, width = widths[i])?;
301 }
302 writeln!(writer)?;
303
304 write!(writer, "+")?;
306 for width in &widths {
307 write!(writer, "-{}-+", "-".repeat(*width))?;
308 }
309 writeln!(writer)?;
310
311 for row_idx in 0..dataview.row_count() {
313 if let Some(row) = dataview.get_row(row_idx) {
314 write!(writer, "|")?;
315 for (i, value) in row.values.iter().enumerate() {
316 if i < widths.len() {
317 let value_str = format_value(value);
318 let truncated = if value_str.len() > widths[i] {
319 format!("{}...", &value_str[..widths[i] - 3])
320 } else {
321 value_str
322 };
323 write!(writer, " {:<width$} |", truncated, width = widths[i])?;
324 }
325 }
326 writeln!(writer)?;
327 }
328 }
329
330 write!(writer, "+")?;
332 for width in &widths {
333 write!(writer, "-{}-+", "-".repeat(*width))?;
334 }
335 writeln!(writer)?;
336
337 Ok(())
338}
339
340fn format_value(value: &DataValue) -> String {
342 match value {
343 DataValue::Null => "".to_string(),
344 DataValue::Integer(i) => i.to_string(),
345 DataValue::Float(f) => f.to_string(),
346 DataValue::String(s) => s.clone(),
347 DataValue::InternedString(s) => s.to_string(),
348 DataValue::Boolean(b) => b.to_string(),
349 DataValue::DateTime(dt) => dt.to_string(),
350 }
351}
352
353fn value_to_json(value: &DataValue) -> serde_json::Value {
355 match value {
356 DataValue::Null => serde_json::Value::Null,
357 DataValue::Integer(i) => serde_json::Value::Number((*i).into()),
358 DataValue::Float(f) => {
359 if let Some(n) = serde_json::Number::from_f64(*f) {
360 serde_json::Value::Number(n)
361 } else {
362 serde_json::Value::Null
363 }
364 }
365 DataValue::String(s) => serde_json::Value::String(s.clone()),
366 DataValue::InternedString(s) => serde_json::Value::String(s.to_string()),
367 DataValue::Boolean(b) => serde_json::Value::Bool(*b),
368 DataValue::DateTime(dt) => serde_json::Value::String(dt.to_string()),
369 }
370}
371
372fn escape_csv_field(field: &str, delimiter: char) -> String {
374 if field.contains(delimiter)
375 || field.contains('"')
376 || field.contains('\n')
377 || field.contains('\r')
378 {
379 format!("\"{}\"", field.replace('"', "\"\""))
380 } else {
381 field.to_string()
382 }
383}