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;
13use crate::sql::script_parser::{ScriptParser, ScriptResult};
14
15#[derive(Debug, Clone)]
17pub enum OutputFormat {
18 Csv,
19 Json,
20 Table,
21 Tsv,
22}
23
24impl OutputFormat {
25 pub fn from_str(s: &str) -> Result<Self> {
26 match s.to_lowercase().as_str() {
27 "csv" => Ok(OutputFormat::Csv),
28 "json" => Ok(OutputFormat::Json),
29 "table" => Ok(OutputFormat::Table),
30 "tsv" => Ok(OutputFormat::Tsv),
31 _ => Err(anyhow::anyhow!(
32 "Invalid output format: {}. Use csv, json, table, or tsv",
33 s
34 )),
35 }
36 }
37}
38
39pub struct NonInteractiveConfig {
41 pub data_file: String,
42 pub query: String,
43 pub output_format: OutputFormat,
44 pub output_file: Option<String>,
45 pub case_insensitive: bool,
46 pub auto_hide_empty: bool,
47 pub limit: Option<usize>,
48 pub query_plan: bool,
49}
50
51pub fn execute_non_interactive(config: NonInteractiveConfig) -> Result<()> {
53 let start_time = Instant::now();
54
55 use crate::sql::recursive_parser::Parser;
57 let mut parser = Parser::new(&config.query);
58 let statement = parser
59 .parse()
60 .map_err(|e| anyhow::anyhow!("Parse error: {}", e))?;
61
62 let uses_dual = statement
63 .from_table
64 .as_ref()
65 .is_some_and(|t| t.to_uppercase() == "DUAL");
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 crate::config::global::init_config(app_config.clone());
117
118 let mut behavior_config = app_config.behavior.clone();
120 debug!(
121 "Using date notation: {}",
122 behavior_config.default_date_notation
123 );
124 if config.case_insensitive {
126 behavior_config.case_insensitive_default = true;
127 }
128 if config.auto_hide_empty {
129 behavior_config.hide_empty_columns = true;
130 }
131
132 let query_service = QueryExecutionService::with_behavior_config(behavior_config);
133 let result = query_service.execute(&config.query, Some(&dataview), Some(dataview.source()))?;
134
135 let query_time = query_start.elapsed();
136 info!("Query executed in {:?}", query_time);
137 info!(
138 "Result: {} rows, {} columns",
139 result.dataview.row_count(),
140 result.dataview.column_count()
141 );
142
143 let final_view = if let Some(limit) = config.limit {
145 let limited_table = limit_results(&result.dataview, limit)?;
146 DataView::new(std::sync::Arc::new(limited_table))
147 } else {
148 result.dataview
149 };
150
151 let output_result = if let Some(ref path) = config.output_file {
153 let mut file = fs::File::create(path)
154 .with_context(|| format!("Failed to create output file: {path}"))?;
155 output_results(&final_view, config.output_format, &mut file)?;
156 info!("Results written to: {}", path);
157 Ok(())
158 } else {
159 output_results(&final_view, config.output_format, &mut io::stdout())?;
160 Ok(())
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
178pub fn execute_script(config: NonInteractiveConfig) -> Result<()> {
180 let start_time = Instant::now();
181
182 let parser = ScriptParser::new(&config.query);
184 let statements = parser.parse_and_validate()?;
185
186 info!("Found {} statements in script", statements.len());
187
188 let (data_table, is_dual) = if config.data_file.is_empty() {
190 info!("Using DUAL table for script execution");
191 (DataTable::dual(), true)
192 } else {
193 info!("Loading data from: {}", config.data_file);
194 let table = load_data_file(&config.data_file)?;
195 info!(
196 "Loaded {} rows with {} columns",
197 table.row_count(),
198 table.column_count()
199 );
200 (table, false)
201 };
202
203 let mut script_result = ScriptResult::new();
205 let mut output = Vec::new();
206
207 for (idx, statement) in statements.iter().enumerate() {
209 let statement_num = idx + 1;
210 let stmt_start = Instant::now();
211
212 if matches!(config.output_format, OutputFormat::Table) {
214 if idx > 0 {
215 output.push(String::new()); }
217 output.push(format!("-- Query {} --", statement_num));
218 }
219
220 let dataview = DataView::new(std::sync::Arc::new(data_table.clone()));
222
223 let service = QueryExecutionService::new(config.case_insensitive, config.auto_hide_empty);
225 match service.execute(statement, Some(&dataview), None) {
226 Ok(result) => {
227 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
228 let final_view = result.dataview;
229
230 let mut statement_output = Vec::new();
232 match config.output_format {
233 OutputFormat::Csv => {
234 output_csv(&final_view, &mut statement_output, ',')?;
235 }
236 OutputFormat::Json => {
237 output_json(&final_view, &mut statement_output)?;
238 }
239 OutputFormat::Table => {
240 output_table(&final_view, &mut statement_output)?;
241 writeln!(
242 &mut statement_output,
243 "Query completed: {} rows in {:.2}ms",
244 final_view.row_count(),
245 exec_time
246 )?;
247 }
248 OutputFormat::Tsv => {
249 output_csv(&final_view, &mut statement_output, '\t')?;
250 }
251 }
252
253 output.extend(
255 String::from_utf8_lossy(&statement_output)
256 .lines()
257 .map(String::from),
258 );
259
260 script_result.add_success(
261 statement_num,
262 statement.clone(),
263 final_view.row_count(),
264 exec_time,
265 );
266 }
267 Err(e) => {
268 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
269 let error_msg = format!("Query {} failed: {}", statement_num, e);
270
271 if matches!(config.output_format, OutputFormat::Table) {
272 output.push(error_msg.clone());
273 }
274
275 script_result.add_failure(
276 statement_num,
277 statement.clone(),
278 e.to_string(),
279 exec_time,
280 );
281
282 }
284 }
285 }
286
287 if let Some(ref output_file) = config.output_file {
289 let mut file = fs::File::create(output_file)?;
290 for line in &output {
291 writeln!(file, "{}", line)?;
292 }
293 info!("Results written to: {}", output_file);
294 } else {
295 for line in &output {
296 println!("{}", line);
297 }
298 }
299
300 if matches!(config.output_format, OutputFormat::Table) {
302 println!("\n=== Script Summary ===");
303 println!("Total statements: {}", script_result.total_statements);
304 println!("Successful: {}", script_result.successful_statements);
305 println!("Failed: {}", script_result.failed_statements);
306 println!(
307 "Total execution time: {:.2}ms",
308 script_result.total_execution_time_ms
309 );
310 }
311
312 if !script_result.all_successful() {
313 return Err(anyhow::anyhow!(
314 "{} of {} statements failed",
315 script_result.failed_statements,
316 script_result.total_statements
317 ));
318 }
319
320 Ok(())
321}
322
323fn load_data_file(path: &str) -> Result<DataTable> {
325 let path = Path::new(path);
326
327 if !path.exists() {
328 return Err(anyhow::anyhow!("File not found: {}", path.display()));
329 }
330
331 let extension = path
333 .extension()
334 .and_then(|ext| ext.to_str())
335 .map(str::to_lowercase)
336 .unwrap_or_default();
337
338 let table_name = path
339 .file_stem()
340 .and_then(|stem| stem.to_str())
341 .unwrap_or("data")
342 .to_string();
343
344 match extension.as_str() {
345 "csv" => load_csv_to_datatable(path, &table_name)
346 .with_context(|| format!("Failed to load CSV file: {}", path.display())),
347 "json" => load_json_to_datatable(path, &table_name)
348 .with_context(|| format!("Failed to load JSON file: {}", path.display())),
349 _ => Err(anyhow::anyhow!(
350 "Unsupported file type: {}. Use .csv or .json",
351 extension
352 )),
353 }
354}
355
356fn limit_results(dataview: &DataView, limit: usize) -> Result<DataTable> {
358 let source = dataview.source();
359 let mut limited_table = DataTable::new(&source.name);
360
361 for col in &source.columns {
363 limited_table.add_column(col.clone());
364 }
365
366 let rows_to_copy = dataview.row_count().min(limit);
368 for i in 0..rows_to_copy {
369 if let Some(row) = dataview.get_row(i) {
370 limited_table.add_row(row.clone());
371 }
372 }
373
374 Ok(limited_table)
375}
376
377fn output_results<W: Write>(
379 dataview: &DataView,
380 format: OutputFormat,
381 writer: &mut W,
382) -> Result<()> {
383 match format {
384 OutputFormat::Csv => output_csv(dataview, writer, ','),
385 OutputFormat::Tsv => output_csv(dataview, writer, '\t'),
386 OutputFormat::Json => output_json(dataview, writer),
387 OutputFormat::Table => output_table(dataview, writer),
388 }
389}
390
391fn output_csv<W: Write>(dataview: &DataView, writer: &mut W, delimiter: char) -> Result<()> {
393 let columns = dataview.column_names();
395 for (i, col) in columns.iter().enumerate() {
396 if i > 0 {
397 write!(writer, "{delimiter}")?;
398 }
399 write!(writer, "{}", escape_csv_field(col, delimiter))?;
400 }
401 writeln!(writer)?;
402
403 for row_idx in 0..dataview.row_count() {
405 if let Some(row) = dataview.get_row(row_idx) {
406 for (i, value) in row.values.iter().enumerate() {
407 if i > 0 {
408 write!(writer, "{delimiter}")?;
409 }
410 write!(
411 writer,
412 "{}",
413 escape_csv_field(&format_value(value), delimiter)
414 )?;
415 }
416 writeln!(writer)?;
417 }
418 }
419
420 Ok(())
421}
422
423fn output_json<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
425 let columns = dataview.column_names();
426 let mut rows = Vec::new();
427
428 for row_idx in 0..dataview.row_count() {
429 if let Some(row) = dataview.get_row(row_idx) {
430 let mut json_row = serde_json::Map::new();
431 for (col_idx, value) in row.values.iter().enumerate() {
432 if col_idx < columns.len() {
433 json_row.insert(columns[col_idx].clone(), value_to_json(value));
434 }
435 }
436 rows.push(serde_json::Value::Object(json_row));
437 }
438 }
439
440 let json = serde_json::to_string_pretty(&rows)?;
441 writeln!(writer, "{json}")?;
442
443 Ok(())
444}
445
446fn output_table<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
448 let columns = dataview.column_names();
449
450 let mut widths = vec![0; columns.len()];
452 for (i, col) in columns.iter().enumerate() {
453 widths[i] = col.len();
454 }
455
456 let sample_size = dataview.row_count().min(100);
458 for row_idx in 0..sample_size {
459 if let Some(row) = dataview.get_row(row_idx) {
460 for (i, value) in row.values.iter().enumerate() {
461 if i < widths.len() {
462 let value_str = format_value(value);
463 widths[i] = widths[i].max(value_str.len());
464 }
465 }
466 }
467 }
468
469 for width in &mut widths {
471 *width = (*width).min(50);
472 }
473
474 write!(writer, "+")?;
476 for width in &widths {
477 write!(writer, "-{}-+", "-".repeat(*width))?;
478 }
479 writeln!(writer)?;
480
481 write!(writer, "|")?;
483 for (i, col) in columns.iter().enumerate() {
484 write!(writer, " {:^width$} |", col, width = widths[i])?;
485 }
486 writeln!(writer)?;
487
488 write!(writer, "+")?;
490 for width in &widths {
491 write!(writer, "-{}-+", "-".repeat(*width))?;
492 }
493 writeln!(writer)?;
494
495 for row_idx in 0..dataview.row_count() {
497 if let Some(row) = dataview.get_row(row_idx) {
498 write!(writer, "|")?;
499 for (i, value) in row.values.iter().enumerate() {
500 if i < widths.len() {
501 let value_str = format_value(value);
502 let truncated = if value_str.len() > widths[i] {
503 format!("{}...", &value_str[..widths[i] - 3])
504 } else {
505 value_str
506 };
507 write!(writer, " {:<width$} |", truncated, width = widths[i])?;
508 }
509 }
510 writeln!(writer)?;
511 }
512 }
513
514 write!(writer, "+")?;
516 for width in &widths {
517 write!(writer, "-{}-+", "-".repeat(*width))?;
518 }
519 writeln!(writer)?;
520
521 Ok(())
522}
523
524fn format_value(value: &DataValue) -> String {
526 match value {
527 DataValue::Null => String::new(),
528 DataValue::Integer(i) => i.to_string(),
529 DataValue::Float(f) => f.to_string(),
530 DataValue::String(s) => s.clone(),
531 DataValue::InternedString(s) => s.to_string(),
532 DataValue::Boolean(b) => b.to_string(),
533 DataValue::DateTime(dt) => dt.to_string(),
534 }
535}
536
537fn value_to_json(value: &DataValue) -> serde_json::Value {
539 match value {
540 DataValue::Null => serde_json::Value::Null,
541 DataValue::Integer(i) => serde_json::Value::Number((*i).into()),
542 DataValue::Float(f) => {
543 if let Some(n) = serde_json::Number::from_f64(*f) {
544 serde_json::Value::Number(n)
545 } else {
546 serde_json::Value::Null
547 }
548 }
549 DataValue::String(s) => serde_json::Value::String(s.clone()),
550 DataValue::InternedString(s) => serde_json::Value::String(s.to_string()),
551 DataValue::Boolean(b) => serde_json::Value::Bool(*b),
552 DataValue::DateTime(dt) => serde_json::Value::String(dt.to_string()),
553 }
554}
555
556fn escape_csv_field(field: &str, delimiter: char) -> String {
558 if field.contains(delimiter)
559 || field.contains('"')
560 || field.contains('\n')
561 || field.contains('\r')
562 {
563 format!("\"{}\"", field.replace('"', "\"\""))
564 } else {
565 field.to_string()
566 }
567}