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::config::config::Config;
9use crate::data::data_view::DataView;
10use crate::data::datatable::{DataTable, DataValue};
11use crate::data::datatable_loaders::{load_csv_to_datatable, load_json_to_datatable};
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 use crate::sql::recursive_parser::Parser;
56 let mut parser = Parser::new(&config.query);
57 let statement = parser
58 .parse()
59 .map_err(|e| anyhow::anyhow!("Parse error: {}", e))?;
60
61 let uses_dual = statement
62 .from_table
63 .as_ref()
64 .map(|t| t.to_uppercase() == "DUAL")
65 .unwrap_or(false);
66
67 let no_from_clause = statement.from_table.is_none();
68
69 let (data_table, is_dual) = if uses_dual || no_from_clause || config.data_file.is_empty() {
71 info!("Using DUAL table for expression evaluation");
72 (crate::data::datatable::DataTable::dual(), true)
73 } else {
74 info!("Loading data from: {}", config.data_file);
75 let table = load_data_file(&config.data_file)?;
76 info!(
77 "Loaded {} rows with {} columns",
78 table.row_count(),
79 table.column_count()
80 );
81 (table, false)
82 };
83 let table_name = data_table.name.clone();
84
85 let dataview = DataView::new(std::sync::Arc::new(data_table));
87
88 info!("Executing query: {}", config.query);
90
91 if config.query_plan {
93 use crate::sql::recursive_parser::Parser;
94 let mut parser = Parser::new(&config.query);
95 match parser.parse() {
96 Ok(statement) => {
97 println!("\n=== QUERY PLAN (AST) ===");
98 println!("{:#?}", statement);
99 println!("=== END QUERY PLAN ===\n");
100 }
101 Err(e) => {
102 eprintln!("Failed to parse query for plan: {}", e);
103 }
104 }
105 }
106
107 let query_start = Instant::now();
108
109 let app_config = Config::load().unwrap_or_else(|e| {
111 debug!("Could not load config file: {}. Using defaults.", e);
112 Config::default()
113 });
114
115 let mut behavior_config = app_config.behavior.clone();
117 debug!(
118 "Using date notation: {}",
119 behavior_config.default_date_notation
120 );
121 if config.case_insensitive {
123 behavior_config.case_insensitive_default = true;
124 }
125 if config.auto_hide_empty {
126 behavior_config.hide_empty_columns = true;
127 }
128
129 let query_service = QueryExecutionService::with_behavior_config(behavior_config);
130 let result = query_service.execute(&config.query, Some(&dataview), Some(dataview.source()))?;
131
132 let query_time = query_start.elapsed();
133 info!("Query executed in {:?}", query_time);
134 info!(
135 "Result: {} rows, {} columns",
136 result.dataview.row_count(),
137 result.dataview.column_count()
138 );
139
140 let final_view = if let Some(limit) = config.limit {
142 let limited_table = limit_results(&result.dataview, limit)?;
143 DataView::new(std::sync::Arc::new(limited_table))
144 } else {
145 result.dataview
146 };
147
148 let output_result = match config.output_file {
150 Some(ref path) => {
151 let mut file = fs::File::create(path)
152 .with_context(|| format!("Failed to create output file: {}", path))?;
153 output_results(&final_view, config.output_format, &mut file)?;
154 info!("Results written to: {}", path);
155 Ok(())
156 }
157 None => {
158 output_results(&final_view, config.output_format, &mut io::stdout())?;
159 Ok(())
160 }
161 };
162
163 let total_time = start_time.elapsed();
164 debug!("Total execution time: {:?}", total_time);
165
166 if config.output_file.is_none() {
168 eprintln!(
169 "\n# Query completed: {} rows in {:?}",
170 final_view.row_count(),
171 query_time
172 );
173 }
174
175 output_result
176}
177
178fn load_data_file(path: &str) -> Result<DataTable> {
180 let path = Path::new(path);
181
182 if !path.exists() {
183 return Err(anyhow::anyhow!("File not found: {}", path.display()));
184 }
185
186 let extension = path
188 .extension()
189 .and_then(|ext| ext.to_str())
190 .map(|s| s.to_lowercase())
191 .unwrap_or_default();
192
193 let table_name = path
194 .file_stem()
195 .and_then(|stem| stem.to_str())
196 .unwrap_or("data")
197 .to_string();
198
199 match extension.as_str() {
200 "csv" => load_csv_to_datatable(path, &table_name)
201 .with_context(|| format!("Failed to load CSV file: {}", path.display())),
202 "json" => load_json_to_datatable(path, &table_name)
203 .with_context(|| format!("Failed to load JSON file: {}", path.display())),
204 _ => Err(anyhow::anyhow!(
205 "Unsupported file type: {}. Use .csv or .json",
206 extension
207 )),
208 }
209}
210
211fn limit_results(dataview: &DataView, limit: usize) -> Result<DataTable> {
213 let source = dataview.source();
214 let mut limited_table = DataTable::new(&source.name);
215
216 for col in source.columns.iter() {
218 limited_table.add_column(col.clone());
219 }
220
221 let rows_to_copy = dataview.row_count().min(limit);
223 for i in 0..rows_to_copy {
224 if let Some(row) = dataview.get_row(i) {
225 limited_table.add_row(row.clone());
226 }
227 }
228
229 Ok(limited_table)
230}
231
232fn output_results<W: Write>(
234 dataview: &DataView,
235 format: OutputFormat,
236 writer: &mut W,
237) -> Result<()> {
238 match format {
239 OutputFormat::Csv => output_csv(dataview, writer, ','),
240 OutputFormat::Tsv => output_csv(dataview, writer, '\t'),
241 OutputFormat::Json => output_json(dataview, writer),
242 OutputFormat::Table => output_table(dataview, writer),
243 }
244}
245
246fn output_csv<W: Write>(dataview: &DataView, writer: &mut W, delimiter: char) -> Result<()> {
248 let columns = dataview.column_names();
250 for (i, col) in columns.iter().enumerate() {
251 if i > 0 {
252 write!(writer, "{}", delimiter)?;
253 }
254 write!(writer, "{}", escape_csv_field(col, delimiter))?;
255 }
256 writeln!(writer)?;
257
258 for row_idx in 0..dataview.row_count() {
260 if let Some(row) = dataview.get_row(row_idx) {
261 for (i, value) in row.values.iter().enumerate() {
262 if i > 0 {
263 write!(writer, "{}", delimiter)?;
264 }
265 write!(
266 writer,
267 "{}",
268 escape_csv_field(&format_value(value), delimiter)
269 )?;
270 }
271 writeln!(writer)?;
272 }
273 }
274
275 Ok(())
276}
277
278fn output_json<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
280 let columns = dataview.column_names();
281 let mut rows = Vec::new();
282
283 for row_idx in 0..dataview.row_count() {
284 if let Some(row) = dataview.get_row(row_idx) {
285 let mut json_row = serde_json::Map::new();
286 for (col_idx, value) in row.values.iter().enumerate() {
287 if col_idx < columns.len() {
288 json_row.insert(columns[col_idx].clone(), value_to_json(value));
289 }
290 }
291 rows.push(serde_json::Value::Object(json_row));
292 }
293 }
294
295 let json = serde_json::to_string_pretty(&rows)?;
296 writeln!(writer, "{}", json)?;
297
298 Ok(())
299}
300
301fn output_table<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
303 let columns = dataview.column_names();
304
305 let mut widths = vec![0; columns.len()];
307 for (i, col) in columns.iter().enumerate() {
308 widths[i] = col.len();
309 }
310
311 let sample_size = dataview.row_count().min(100);
313 for row_idx in 0..sample_size {
314 if let Some(row) = dataview.get_row(row_idx) {
315 for (i, value) in row.values.iter().enumerate() {
316 if i < widths.len() {
317 let value_str = format_value(value);
318 widths[i] = widths[i].max(value_str.len());
319 }
320 }
321 }
322 }
323
324 for width in widths.iter_mut() {
326 *width = (*width).min(50);
327 }
328
329 write!(writer, "+")?;
331 for width in &widths {
332 write!(writer, "-{}-+", "-".repeat(*width))?;
333 }
334 writeln!(writer)?;
335
336 write!(writer, "|")?;
338 for (i, col) in columns.iter().enumerate() {
339 write!(writer, " {:^width$} |", col, width = widths[i])?;
340 }
341 writeln!(writer)?;
342
343 write!(writer, "+")?;
345 for width in &widths {
346 write!(writer, "-{}-+", "-".repeat(*width))?;
347 }
348 writeln!(writer)?;
349
350 for row_idx in 0..dataview.row_count() {
352 if let Some(row) = dataview.get_row(row_idx) {
353 write!(writer, "|")?;
354 for (i, value) in row.values.iter().enumerate() {
355 if i < widths.len() {
356 let value_str = format_value(value);
357 let truncated = if value_str.len() > widths[i] {
358 format!("{}...", &value_str[..widths[i] - 3])
359 } else {
360 value_str
361 };
362 write!(writer, " {:<width$} |", truncated, width = widths[i])?;
363 }
364 }
365 writeln!(writer)?;
366 }
367 }
368
369 write!(writer, "+")?;
371 for width in &widths {
372 write!(writer, "-{}-+", "-".repeat(*width))?;
373 }
374 writeln!(writer)?;
375
376 Ok(())
377}
378
379fn format_value(value: &DataValue) -> String {
381 match value {
382 DataValue::Null => "".to_string(),
383 DataValue::Integer(i) => i.to_string(),
384 DataValue::Float(f) => f.to_string(),
385 DataValue::String(s) => s.clone(),
386 DataValue::InternedString(s) => s.to_string(),
387 DataValue::Boolean(b) => b.to_string(),
388 DataValue::DateTime(dt) => dt.to_string(),
389 }
390}
391
392fn value_to_json(value: &DataValue) -> serde_json::Value {
394 match value {
395 DataValue::Null => serde_json::Value::Null,
396 DataValue::Integer(i) => serde_json::Value::Number((*i).into()),
397 DataValue::Float(f) => {
398 if let Some(n) = serde_json::Number::from_f64(*f) {
399 serde_json::Value::Number(n)
400 } else {
401 serde_json::Value::Null
402 }
403 }
404 DataValue::String(s) => serde_json::Value::String(s.clone()),
405 DataValue::InternedString(s) => serde_json::Value::String(s.to_string()),
406 DataValue::Boolean(b) => serde_json::Value::Bool(*b),
407 DataValue::DateTime(dt) => serde_json::Value::String(dt.to_string()),
408 }
409}
410
411fn escape_csv_field(field: &str, delimiter: char) -> String {
413 if field.contains(delimiter)
414 || field.contains('"')
415 || field.contains('\n')
416 || field.contains('\r')
417 {
418 format!("\"{}\"", field.replace('"', "\"\""))
419 } else {
420 field.to_string()
421 }
422}