1use anyhow::{Context, Result};
2use comfy_table::presets::*;
3use comfy_table::{ContentArrangement, Table};
4use std::fs;
5use std::io::{self, Write};
6use std::path::Path;
7use std::time::Instant;
8use tracing::{debug, info};
9
10use crate::config::config::Config;
11use crate::data::data_view::DataView;
12use crate::data::datatable::{DataTable, DataValue};
13use crate::data::datatable_loaders::{load_csv_to_datatable, load_json_to_datatable};
14use crate::data::temp_table_registry::TempTableRegistry;
15use crate::services::query_execution_service::QueryExecutionService;
16use crate::sql::parser::ast::{CTEType, TableSource, CTE};
17use crate::sql::recursive_parser::Parser;
18use crate::sql::script_parser::{ScriptParser, ScriptResult};
19
20fn check_temp_table_usage(query: &str) -> Result<()> {
23 use crate::sql::recursive_parser::Parser;
24
25 let mut parser = Parser::new(query);
26 match parser.parse() {
27 Ok(stmt) => {
28 if let Some(from_table) = &stmt.from_table {
30 if from_table.starts_with('#') {
31 anyhow::bail!(
32 "Temporary table '{}' cannot be used in single-query mode. \
33 Temporary tables are only available in script mode (using -f flag with GO separators).",
34 from_table
35 );
36 }
37 }
38
39 if let Some(into_table) = &stmt.into_table {
41 anyhow::bail!(
42 "INTO clause for temporary table '{}' is only supported in script mode. \
43 Use -f flag with GO separators to create temporary tables.",
44 into_table.name
45 );
46 }
47
48 Ok(())
49 }
50 Err(_) => {
51 Ok(())
53 }
54 }
55}
56
57fn extract_cte_dependencies(cte: &CTE) -> Vec<String> {
59 let mut deps = Vec::new();
62
63 if let CTEType::Standard(query) = &cte.cte_type {
64 if let Some(from_table) = &query.from_table {
65 deps.push(from_table.clone());
66 }
67
68 for join in &query.joins {
70 match &join.table {
71 TableSource::Table(table_name) => {
72 deps.push(table_name.clone());
73 }
74 TableSource::DerivedTable { alias, .. } => {
75 deps.push(alias.clone());
76 }
77 }
78 }
79 }
80
81 deps
82}
83
84#[derive(Debug, Clone)]
86pub enum OutputFormat {
87 Csv,
88 Json,
89 JsonStructured, Table,
91 Tsv,
92}
93
94impl OutputFormat {
95 pub fn from_str(s: &str) -> Result<Self> {
96 match s.to_lowercase().as_str() {
97 "csv" => Ok(OutputFormat::Csv),
98 "json" => Ok(OutputFormat::Json),
99 "json-structured" => Ok(OutputFormat::JsonStructured),
100 "table" => Ok(OutputFormat::Table),
101 "tsv" => Ok(OutputFormat::Tsv),
102 _ => Err(anyhow::anyhow!(
103 "Invalid output format: {}. Use csv, json, json-structured, table, or tsv",
104 s
105 )),
106 }
107 }
108}
109
110#[derive(Debug, Clone, Copy)]
112pub enum TableStyle {
113 Default,
115 AsciiFull,
117 AsciiCondensed,
119 AsciiBordersOnly,
121 AsciiHorizontalOnly,
123 AsciiNoBorders,
125 Markdown,
127 Utf8Full,
129 Utf8Condensed,
131 Utf8BordersOnly,
133 Utf8HorizontalOnly,
135 Utf8NoBorders,
137 Plain,
139}
140
141impl TableStyle {
142 pub fn from_str(s: &str) -> Result<Self> {
143 match s.to_lowercase().as_str() {
144 "default" => Ok(TableStyle::Default),
145 "ascii" | "ascii-full" => Ok(TableStyle::AsciiFull),
146 "ascii-condensed" => Ok(TableStyle::AsciiCondensed),
147 "ascii-borders" | "ascii-borders-only" => Ok(TableStyle::AsciiBordersOnly),
148 "ascii-horizontal" | "ascii-horizontal-only" => Ok(TableStyle::AsciiHorizontalOnly),
149 "ascii-noborders" | "ascii-no-borders" => Ok(TableStyle::AsciiNoBorders),
150 "markdown" | "md" => Ok(TableStyle::Markdown),
151 "utf8" | "utf8-full" => Ok(TableStyle::Utf8Full),
152 "utf8-condensed" => Ok(TableStyle::Utf8Condensed),
153 "utf8-borders" | "utf8-borders-only" => Ok(TableStyle::Utf8BordersOnly),
154 "utf8-horizontal" | "utf8-horizontal-only" => Ok(TableStyle::Utf8HorizontalOnly),
155 "utf8-noborders" | "utf8-no-borders" => Ok(TableStyle::Utf8NoBorders),
156 "plain" | "none" => Ok(TableStyle::Plain),
157 _ => Err(anyhow::anyhow!(
158 "Invalid table style: {}. Use: default, ascii, ascii-condensed, ascii-borders, ascii-horizontal, ascii-noborders, markdown, utf8, utf8-condensed, utf8-borders, utf8-horizontal, utf8-noborders, plain",
159 s
160 )),
161 }
162 }
163
164 pub fn list_styles() -> &'static str {
165 "Available table styles:
166 default - Current default ASCII style
167 ascii - ASCII with full borders
168 ascii-condensed - ASCII with condensed rows
169 ascii-borders - ASCII with outer borders only
170 ascii-horizontal - ASCII with horizontal lines only
171 ascii-noborders - ASCII with no borders
172 markdown - GitHub-flavored Markdown table
173 utf8 - UTF8 box-drawing characters
174 utf8-condensed - UTF8 with condensed rows
175 utf8-borders - UTF8 with outer borders only
176 utf8-horizontal - UTF8 with horizontal lines only
177 utf8-noborders - UTF8 with no borders
178 plain - No formatting, data only"
179 }
180}
181
182pub struct NonInteractiveConfig {
184 pub data_file: String,
185 pub query: String,
186 pub output_format: OutputFormat,
187 pub output_file: Option<String>,
188 pub case_insensitive: bool,
189 pub auto_hide_empty: bool,
190 pub limit: Option<usize>,
191 pub query_plan: bool,
192 pub show_work_units: bool,
193 pub execution_plan: bool,
194 pub cte_info: bool,
195 pub rewrite_analysis: bool,
196 pub lift_in_expressions: bool,
197 pub script_file: Option<String>, pub debug_trace: bool,
199 pub max_col_width: Option<usize>, pub col_sample_rows: usize, pub table_style: TableStyle, }
203
204pub fn execute_non_interactive(config: NonInteractiveConfig) -> Result<()> {
206 let start_time = Instant::now();
207
208 check_temp_table_usage(&config.query)?;
210
211 let (data_table, _is_dual) = if config.data_file.is_empty() {
213 info!("No data file provided, using DUAL table");
214 (crate::data::datatable::DataTable::dual(), true)
215 } else {
216 info!("Loading data from: {}", config.data_file);
217 let table = load_data_file(&config.data_file)?;
218 info!(
219 "Loaded {} rows with {} columns",
220 table.row_count(),
221 table.column_count()
222 );
223 (table, false)
224 };
225 let _table_name = data_table.name.clone();
226
227 let dataview = DataView::new(std::sync::Arc::new(data_table));
229
230 info!("Executing query: {}", config.query);
232
233 if config.execution_plan {
235 println!("\n=== EXECUTION PLAN ===");
236 println!("Query: {}", config.query);
237 println!("\nExecution Steps:");
238 println!("1. PARSE - Parse SQL query");
239 println!("2. LOAD_DATA - Load data from {}", &config.data_file);
240 println!(
241 " • Loaded {} rows, {} columns",
242 dataview.row_count(),
243 dataview.column_count()
244 );
245 }
246
247 if config.show_work_units {
249 use crate::query_plan::{ExpressionLifter, QueryAnalyzer};
250 use crate::sql::recursive_parser::Parser;
251
252 let mut parser = Parser::new(&config.query);
253 match parser.parse() {
254 Ok(stmt) => {
255 let mut analyzer = QueryAnalyzer::new();
256 let mut lifter = ExpressionLifter::new();
257
258 let mut stmt_copy = stmt.clone();
260 let lifted = lifter.lift_expressions(&mut stmt_copy);
261
262 match analyzer.analyze(&stmt_copy, config.query.clone()) {
264 Ok(plan) => {
265 println!("\n{}", plan.explain());
266
267 if !lifted.is_empty() {
268 println!("\nLifted CTEs:");
269 for cte in &lifted {
270 println!(" - {}", cte.name);
271 }
272 }
273
274 return Ok(());
275 }
276 Err(e) => {
277 eprintln!("Error analyzing query: {}", e);
278 return Err(anyhow::anyhow!("Query analysis failed: {}", e));
279 }
280 }
281 }
282 Err(e) => {
283 eprintln!("Error parsing query: {}", e);
284 return Err(anyhow::anyhow!("Parse error: {}", e));
285 }
286 }
287 }
288
289 if config.query_plan {
291 use crate::sql::recursive_parser::Parser;
292 let mut parser = Parser::new(&config.query);
293 match parser.parse() {
294 Ok(statement) => {
295 println!("\n=== QUERY PLAN (AST) ===");
296 println!("{statement:#?}");
297 println!("=== END QUERY PLAN ===\n");
298 }
299 Err(e) => {
300 eprintln!("Failed to parse query for plan: {e}");
301 }
302 }
303 }
304
305 if config.rewrite_analysis {
307 use crate::sql::query_rewriter::{QueryRewriter, RewriteAnalysis};
308 use crate::sql::recursive_parser::Parser;
309 use serde_json::json;
310
311 let mut parser = Parser::new(&config.query);
312 match parser.parse() {
313 Ok(statement) => {
314 let mut rewriter = QueryRewriter::new();
315 let suggestions = rewriter.analyze(&statement);
316
317 let analysis = RewriteAnalysis::from_suggestions(suggestions);
318 println!("{}", serde_json::to_string_pretty(&analysis).unwrap());
319 return Ok(());
320 }
321 Err(e) => {
322 let output = json!({
323 "success": false,
324 "error": format!("{}", e),
325 "suggestions": [],
326 "can_auto_rewrite": false,
327 });
328 println!("{}", serde_json::to_string_pretty(&output).unwrap());
329 return Ok(());
330 }
331 }
332 }
333
334 if config.cte_info {
336 use crate::sql::recursive_parser::Parser;
337 use serde_json::json;
338
339 let mut parser = Parser::new(&config.query);
340 match parser.parse() {
341 Ok(statement) => {
342 let mut cte_info = Vec::new();
343
344 for (index, cte) in statement.ctes.iter().enumerate() {
346 let cte_json = json!({
347 "index": index,
348 "name": cte.name,
349 "columns": cte.column_list,
350 "dependencies": extract_cte_dependencies(cte),
353 });
354 cte_info.push(cte_json);
355 }
356
357 let output = json!({
358 "success": true,
359 "ctes": cte_info,
360 "total": statement.ctes.len(),
361 "has_final_select": !statement.columns.is_empty() || !statement.select_items.is_empty(),
362 });
363
364 println!("{}", serde_json::to_string_pretty(&output).unwrap());
365 return Ok(());
366 }
367 Err(e) => {
368 let output = json!({
369 "success": false,
370 "error": format!("{}", e),
371 "ctes": [],
372 "total": 0,
373 });
374 println!("{}", serde_json::to_string_pretty(&output).unwrap());
375 return Ok(());
376 }
377 }
378 }
379
380 let query_start = Instant::now();
381
382 let app_config = Config::load().unwrap_or_else(|e| {
384 debug!("Could not load config file: {}. Using defaults.", e);
385 Config::default()
386 });
387
388 crate::config::global::init_config(app_config.clone());
390
391 let mut behavior_config = app_config.behavior.clone();
393 debug!(
394 "Using date notation: {}",
395 behavior_config.default_date_notation
396 );
397 if config.case_insensitive {
399 behavior_config.case_insensitive_default = true;
400 }
401 if config.auto_hide_empty {
402 behavior_config.hide_empty_columns = true;
403 }
404
405 let exec_start = Instant::now();
407 let result = if config.lift_in_expressions {
408 use crate::data::query_engine::QueryEngine;
409 use crate::query_plan::{CTEHoister, InOperatorLifter};
410 use crate::sql::recursive_parser::Parser;
411
412 let mut parser = Parser::new(&config.query);
413 match parser.parse() {
414 Ok(mut stmt) => {
415 use crate::query_plan::ExpressionLifter;
417 let mut expr_lifter = ExpressionLifter::new();
418 let lifted = expr_lifter.lift_expressions(&mut stmt);
419 if !lifted.is_empty() {
420 info!(
421 "Applied expression lifting - {} CTEs generated",
422 lifted.len()
423 );
424 }
425
426 stmt = CTEHoister::hoist_ctes(stmt);
428
429 let mut lifter = InOperatorLifter::new();
431 if lifter.rewrite_query(&mut stmt) {
432 info!("Applied IN expression lifting - query rewritten with CTEs");
433
434 let engine = QueryEngine::with_case_insensitive(config.case_insensitive);
436
437 match engine.execute_statement(dataview.source_arc(), stmt) {
438 Ok(result_view) => {
439 Ok(
441 crate::services::query_execution_service::QueryExecutionResult {
442 dataview: result_view,
443 stats: crate::services::query_execution_service::QueryStats {
444 row_count: 0, column_count: 0,
446 execution_time: exec_start.elapsed(),
447 query_engine_time: exec_start.elapsed(),
448 },
449 hidden_columns: Vec::new(),
450 query: config.query.clone(),
451 execution_plan: None,
452 debug_trace: None,
453 },
454 )
455 }
456 Err(e) => Err(e),
457 }
458 } else {
459 let query_service =
461 QueryExecutionService::with_behavior_config(behavior_config);
462 if config.debug_trace {
463 let debug_ctx = crate::debug_trace::DebugContext::new(
464 crate::debug_trace::DebugLevel::Debug,
465 );
466 query_service.execute_with_debug(
467 &config.query,
468 Some(&dataview),
469 Some(dataview.source()),
470 Some(debug_ctx),
471 )
472 } else {
473 query_service.execute(
474 &config.query,
475 Some(&dataview),
476 Some(dataview.source()),
477 )
478 }
479 }
480 }
481 Err(_) => {
482 let query_service = QueryExecutionService::with_behavior_config(behavior_config);
484 if config.debug_trace {
485 let debug_ctx = crate::debug_trace::DebugContext::new(
486 crate::debug_trace::DebugLevel::Debug,
487 );
488 query_service.execute_with_debug(
489 &config.query,
490 Some(&dataview),
491 Some(dataview.source()),
492 Some(debug_ctx),
493 )
494 } else {
495 query_service.execute(&config.query, Some(&dataview), Some(dataview.source()))
496 }
497 }
498 }
499 } else {
500 let query_service = QueryExecutionService::with_behavior_config(behavior_config);
502
503 if config.debug_trace {
505 let debug_ctx =
506 crate::debug_trace::DebugContext::new(crate::debug_trace::DebugLevel::Debug);
507 query_service.execute_with_debug(
508 &config.query,
509 Some(&dataview),
510 Some(dataview.source()),
511 Some(debug_ctx),
512 )
513 } else {
514 query_service.execute(&config.query, Some(&dataview), Some(dataview.source()))
515 }
516 }?;
517 let exec_time = exec_start.elapsed();
518
519 let query_time = query_start.elapsed();
520 info!("Query executed in {:?}", query_time);
521 info!(
522 "Result: {} rows, {} columns",
523 result.dataview.row_count(),
524 result.dataview.column_count()
525 );
526
527 if config.execution_plan {
529 use crate::data::query_engine::QueryEngine;
531
532 let query_engine = QueryEngine::new();
533
534 match query_engine.execute_with_plan(
535 std::sync::Arc::new(dataview.source().clone()),
536 &config.query,
537 ) {
538 Ok((_view, plan)) => {
539 print!("{}", plan.format_tree());
541 }
542 Err(e) => {
543 eprintln!("Could not generate detailed execution plan: {}", e);
545 println!(
546 "3. QUERY_EXECUTION [{:.3}ms]",
547 exec_time.as_secs_f64() * 1000.0
548 );
549
550 use crate::sql::recursive_parser::Parser;
552 let mut parser = Parser::new(&config.query);
553 if let Ok(stmt) = parser.parse() {
554 if stmt.where_clause.is_some() {
555 println!(" • WHERE clause filtering applied");
556 println!(" • Rows after filter: {}", result.dataview.row_count());
557 }
558
559 if let Some(ref order_by) = stmt.order_by {
560 println!(" • ORDER BY: {} column(s)", order_by.len());
561 }
562
563 if let Some(ref group_by) = stmt.group_by {
564 println!(" • GROUP BY: {} column(s)", group_by.len());
565 }
566
567 if let Some(limit) = stmt.limit {
568 println!(" • LIMIT: {} rows", limit);
569 }
570
571 if stmt.distinct {
572 println!(" • DISTINCT applied");
573 }
574 }
575 }
576 }
577
578 println!("\nExecution Statistics:");
579 println!(
580 " Preparation: {:.3}ms",
581 (exec_start - start_time).as_secs_f64() * 1000.0
582 );
583 println!(
584 " Query time: {:.3}ms",
585 exec_time.as_secs_f64() * 1000.0
586 );
587 println!(
588 " Total time: {:.3}ms",
589 query_time.as_secs_f64() * 1000.0
590 );
591 println!(" Rows returned: {}", result.dataview.row_count());
592 println!(" Columns: {}", result.dataview.column_count());
593 println!("\n=== END EXECUTION PLAN ===");
594 println!();
595 }
596
597 let final_view = if let Some(limit) = config.limit {
599 let limited_table = limit_results(&result.dataview, limit)?;
600 DataView::new(std::sync::Arc::new(limited_table))
601 } else {
602 result.dataview
603 };
604
605 if let Some(ref trace_output) = result.debug_trace {
607 eprintln!("{}", trace_output);
608 }
609
610 let exec_time_ms = exec_time.as_secs_f64() * 1000.0;
612 let output_result = if let Some(ref path) = config.output_file {
613 let mut file = fs::File::create(path)
614 .with_context(|| format!("Failed to create output file: {path}"))?;
615 output_results(
616 &final_view,
617 config.output_format,
618 &mut file,
619 config.max_col_width,
620 config.col_sample_rows,
621 exec_time_ms,
622 config.table_style,
623 )?;
624 info!("Results written to: {}", path);
625 Ok(())
626 } else {
627 output_results(
628 &final_view,
629 config.output_format,
630 &mut io::stdout(),
631 config.max_col_width,
632 config.col_sample_rows,
633 exec_time_ms,
634 config.table_style,
635 )?;
636 Ok(())
637 };
638
639 let total_time = start_time.elapsed();
640 debug!("Total execution time: {:?}", total_time);
641
642 if config.output_file.is_none() {
644 eprintln!(
645 "\n# Query completed: {} rows in {:?}",
646 final_view.row_count(),
647 query_time
648 );
649 }
650
651 output_result
652}
653
654pub fn execute_script(config: NonInteractiveConfig) -> Result<()> {
656 let _start_time = Instant::now();
657
658 let parser = ScriptParser::new(&config.query);
660 let script_statements = parser.parse_script_statements();
661
662 if script_statements.is_empty() {
663 anyhow::bail!("No statements found in script");
664 }
665
666 info!("Found {} statements in script", script_statements.len());
667
668 let data_file = if !config.data_file.is_empty() {
670 config.data_file.clone()
672 } else if let Some(hint) = parser.data_file_hint() {
673 info!("Using data file from script hint: {}", hint);
675
676 if let Some(script_path) = config.script_file.as_ref() {
678 let script_dir = std::path::Path::new(script_path)
679 .parent()
680 .unwrap_or(std::path::Path::new("."));
681 let hint_path = std::path::Path::new(hint);
682
683 if hint_path.is_relative() {
684 script_dir.join(hint_path).to_string_lossy().to_string()
685 } else {
686 hint.to_string()
687 }
688 } else {
689 hint.to_string()
690 }
691 } else {
692 String::new()
693 };
694
695 let (data_table, _is_dual) = if data_file.is_empty() {
697 info!("No data file provided, using DUAL table");
699 (DataTable::dual(), true)
700 } else {
701 if !std::path::Path::new(&data_file).exists() {
703 anyhow::bail!(
704 "Data file not found: {}\n\
705 Please check the path is correct",
706 data_file
707 );
708 }
709
710 info!("Loading data from: {}", data_file);
711 let table = load_data_file(&data_file)?;
712 info!(
713 "Loaded {} rows with {} columns",
714 table.row_count(),
715 table.column_count()
716 );
717 (table, false)
718 };
719
720 let mut script_result = ScriptResult::new();
722 let mut output = Vec::new();
723
724 let arc_data_table = std::sync::Arc::new(data_table);
726
727 let mut temp_tables = TempTableRegistry::new();
729
730 for (idx, script_stmt) in script_statements.iter().enumerate() {
732 let statement_num = idx + 1;
733 let stmt_start = Instant::now();
734
735 if script_stmt.is_exit() {
737 let exit_code = script_stmt.get_exit_code().unwrap_or(0);
738 info!("EXIT statement encountered (code: {})", exit_code);
739
740 if matches!(config.output_format, OutputFormat::Table) {
742 if idx > 0 {
743 output.push(String::new());
744 }
745 output.push(format!("-- Statement {} --", statement_num));
746 output.push(format!("Script execution stopped by EXIT {}", exit_code));
747 }
748
749 script_result.add_success(
751 statement_num,
752 format!("EXIT {}", exit_code),
753 0,
754 stmt_start.elapsed().as_secs_f64() * 1000.0,
755 );
756
757 break;
759 }
760
761 if script_stmt.should_skip() {
763 info!(
764 "Skipping statement {} due to [SKIP] directive",
765 statement_num
766 );
767
768 if matches!(config.output_format, OutputFormat::Table) {
770 if idx > 0 {
771 output.push(String::new());
772 }
773 output.push(format!("-- Statement {} [SKIPPED] --", statement_num));
774 }
775
776 script_result.add_success(
778 statement_num,
779 "[SKIPPED]".to_string(),
780 0,
781 stmt_start.elapsed().as_secs_f64() * 1000.0,
782 );
783
784 continue;
785 }
786
787 let statement = match script_stmt.get_query() {
789 Some(sql) => sql,
790 None => continue, };
792
793 if matches!(config.output_format, OutputFormat::Table) {
795 if idx > 0 {
796 output.push(String::new()); }
798 output.push(format!("-- Query {} --", statement_num));
799 }
800
801 use crate::sql::template_expander::TemplateExpander;
804 let expander = TemplateExpander::new(&temp_tables);
805
806 let expanded_statement = match expander.parse_templates(statement) {
807 Ok(vars) => {
808 if vars.is_empty() {
809 statement.to_string()
810 } else {
811 match expander.expand(statement, &vars) {
812 Ok(expanded) => {
813 debug!("Expanded templates in SQL: {} vars found", vars.len());
814 for var in &vars {
815 debug!(
816 " {} -> expanding from {}",
817 var.placeholder, var.table_name
818 );
819 }
820 expanded
821 }
822 Err(e) => {
823 script_result.add_failure(
824 statement_num,
825 statement.to_string(),
826 format!("Template expansion error: {}", e),
827 stmt_start.elapsed().as_secs_f64() * 1000.0,
828 );
829 continue; }
831 }
832 }
833 }
834 Err(e) => {
835 script_result.add_failure(
836 statement_num,
837 statement.to_string(),
838 format!("Template parse error: {}", e),
839 stmt_start.elapsed().as_secs_f64() * 1000.0,
840 );
841 continue; }
843 };
844
845 let statement = expanded_statement.as_str();
847
848 let mut parser = Parser::new(statement);
850 let parsed_stmt = match parser.parse() {
851 Ok(stmt) => stmt,
852 Err(e) => {
853 script_result.add_failure(
855 statement_num,
856 statement.to_string(),
857 format!("Parse error: {}", e),
858 stmt_start.elapsed().as_secs_f64() * 1000.0,
859 );
860 break;
861 }
862 };
863
864 let source_table = if let Some(from_table) = &parsed_stmt.from_table {
866 if from_table.starts_with('#') {
867 match temp_tables.get(from_table) {
869 Some(temp_table) => temp_table,
870 None => {
871 script_result.add_failure(
872 statement_num,
873 statement.to_string(),
874 format!("Temporary table {} not found", from_table),
875 stmt_start.elapsed().as_secs_f64() * 1000.0,
876 );
877 break;
878 }
879 }
880 } else {
881 arc_data_table.clone()
883 }
884 } else {
885 arc_data_table.clone()
887 };
888
889 let dataview = DataView::new(source_table);
891
892 let executable_sql = if parsed_stmt.into_table.is_some() {
895 use crate::query_plan::IntoClauseRemover;
897 use crate::sql::parser::ast_formatter;
898
899 let cleaned_stmt = IntoClauseRemover::remove_into_clause(parsed_stmt.clone());
900 ast_formatter::format_select_statement(&cleaned_stmt)
901 } else {
902 statement.to_string()
903 };
904
905 let service = QueryExecutionService::new(config.case_insensitive, config.auto_hide_empty);
907 match service.execute_with_temp_tables(
908 &executable_sql,
909 Some(&dataview),
910 None,
911 Some(&temp_tables),
912 ) {
913 Ok(result) => {
914 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
915 let final_view = result.dataview;
916
917 if let Some(into_table) = &parsed_stmt.into_table {
919 let result_table = final_view.source_arc();
921 let row_count = result_table.row_count();
922
923 match temp_tables.insert(into_table.name.clone(), result_table) {
924 Ok(_) => {
925 info!(
926 "Stored {} rows in temporary table {}",
927 row_count, into_table.name
928 );
929
930 let mut statement_output = Vec::new();
932 writeln!(
933 &mut statement_output,
934 "({} rows affected) -> {}",
935 row_count, into_table.name
936 )?;
937 output.extend(
938 String::from_utf8_lossy(&statement_output)
939 .lines()
940 .map(String::from),
941 );
942
943 script_result.add_success(
944 statement_num,
945 statement.to_string(),
946 row_count,
947 exec_time,
948 );
949 continue; }
951 Err(e) => {
952 script_result.add_failure(
953 statement_num,
954 statement.to_string(),
955 e.to_string(),
956 exec_time,
957 );
958 break;
959 }
960 }
961 }
962
963 let mut statement_output = Vec::new();
965 match config.output_format {
966 OutputFormat::Csv => {
967 output_csv(&final_view, &mut statement_output, ',')?;
968 }
969 OutputFormat::Json => {
970 output_json(&final_view, &mut statement_output)?;
971 }
972 OutputFormat::JsonStructured => {
973 output_json_structured(&final_view, &mut statement_output, exec_time)?;
974 }
975 OutputFormat::Table => {
976 output_table(
977 &final_view,
978 &mut statement_output,
979 config.max_col_width,
980 config.col_sample_rows,
981 config.table_style,
982 )?;
983 writeln!(
984 &mut statement_output,
985 "Query completed: {} rows in {:.2}ms",
986 final_view.row_count(),
987 exec_time
988 )?;
989 }
990 OutputFormat::Tsv => {
991 output_csv(&final_view, &mut statement_output, '\t')?;
992 }
993 }
994
995 output.extend(
997 String::from_utf8_lossy(&statement_output)
998 .lines()
999 .map(String::from),
1000 );
1001
1002 script_result.add_success(
1003 statement_num,
1004 statement.to_string(),
1005 final_view.row_count(),
1006 exec_time,
1007 );
1008 }
1009 Err(e) => {
1010 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
1011 let error_msg = format!("Query {} failed: {}", statement_num, e);
1012
1013 if matches!(config.output_format, OutputFormat::Table) {
1014 output.push(error_msg.clone());
1015 }
1016
1017 script_result.add_failure(
1018 statement_num,
1019 statement.to_string(),
1020 e.to_string(),
1021 exec_time,
1022 );
1023
1024 }
1026 }
1027 }
1028
1029 if let Some(ref output_file) = config.output_file {
1031 let mut file = fs::File::create(output_file)?;
1032 for line in &output {
1033 writeln!(file, "{}", line)?;
1034 }
1035 info!("Results written to: {}", output_file);
1036 } else {
1037 for line in &output {
1038 println!("{}", line);
1039 }
1040 }
1041
1042 if matches!(config.output_format, OutputFormat::Table) {
1044 println!("\n=== Script Summary ===");
1045 println!("Total statements: {}", script_result.total_statements);
1046 println!("Successful: {}", script_result.successful_statements);
1047 println!("Failed: {}", script_result.failed_statements);
1048 println!(
1049 "Total execution time: {:.2}ms",
1050 script_result.total_execution_time_ms
1051 );
1052 }
1053
1054 if !script_result.all_successful() {
1055 return Err(anyhow::anyhow!(
1056 "{} of {} statements failed",
1057 script_result.failed_statements,
1058 script_result.total_statements
1059 ));
1060 }
1061
1062 Ok(())
1063}
1064
1065fn load_data_file(path: &str) -> Result<DataTable> {
1067 let path = Path::new(path);
1068
1069 if !path.exists() {
1070 return Err(anyhow::anyhow!("File not found: {}", path.display()));
1071 }
1072
1073 let extension = path
1075 .extension()
1076 .and_then(|ext| ext.to_str())
1077 .map(str::to_lowercase)
1078 .unwrap_or_default();
1079
1080 let table_name = path
1081 .file_stem()
1082 .and_then(|stem| stem.to_str())
1083 .unwrap_or("data")
1084 .to_string();
1085
1086 match extension.as_str() {
1087 "csv" => load_csv_to_datatable(path, &table_name)
1088 .with_context(|| format!("Failed to load CSV file: {}", path.display())),
1089 "json" => load_json_to_datatable(path, &table_name)
1090 .with_context(|| format!("Failed to load JSON file: {}", path.display())),
1091 _ => Err(anyhow::anyhow!(
1092 "Unsupported file type: {}. Use .csv or .json",
1093 extension
1094 )),
1095 }
1096}
1097
1098fn limit_results(dataview: &DataView, limit: usize) -> Result<DataTable> {
1100 let source = dataview.source();
1101 let mut limited_table = DataTable::new(&source.name);
1102
1103 for col in &source.columns {
1105 limited_table.add_column(col.clone());
1106 }
1107
1108 let rows_to_copy = dataview.row_count().min(limit);
1110 for i in 0..rows_to_copy {
1111 if let Some(row) = dataview.get_row(i) {
1112 let _ = limited_table.add_row(row.clone());
1113 }
1114 }
1115
1116 Ok(limited_table)
1117}
1118
1119fn output_results<W: Write>(
1121 dataview: &DataView,
1122 format: OutputFormat,
1123 writer: &mut W,
1124 max_col_width: Option<usize>,
1125 col_sample_rows: usize,
1126 exec_time_ms: f64,
1127 table_style: TableStyle,
1128) -> Result<()> {
1129 match format {
1130 OutputFormat::Csv => output_csv(dataview, writer, ','),
1131 OutputFormat::Tsv => output_csv(dataview, writer, '\t'),
1132 OutputFormat::Json => output_json(dataview, writer),
1133 OutputFormat::JsonStructured => output_json_structured(dataview, writer, exec_time_ms),
1134 OutputFormat::Table => output_table(
1135 dataview,
1136 writer,
1137 max_col_width,
1138 col_sample_rows,
1139 table_style,
1140 ),
1141 }
1142}
1143
1144fn output_csv<W: Write>(dataview: &DataView, writer: &mut W, delimiter: char) -> Result<()> {
1146 let columns = dataview.column_names();
1148 for (i, col) in columns.iter().enumerate() {
1149 if i > 0 {
1150 write!(writer, "{delimiter}")?;
1151 }
1152 write!(writer, "{}", escape_csv_field(col, delimiter))?;
1153 }
1154 writeln!(writer)?;
1155
1156 for row_idx in 0..dataview.row_count() {
1158 if let Some(row) = dataview.get_row(row_idx) {
1159 for (i, value) in row.values.iter().enumerate() {
1160 if i > 0 {
1161 write!(writer, "{delimiter}")?;
1162 }
1163 write!(
1164 writer,
1165 "{}",
1166 escape_csv_field(&format_value(value), delimiter)
1167 )?;
1168 }
1169 writeln!(writer)?;
1170 }
1171 }
1172
1173 Ok(())
1174}
1175
1176fn output_json<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
1178 let columns = dataview.column_names();
1179 let mut rows = Vec::new();
1180
1181 for row_idx in 0..dataview.row_count() {
1182 if let Some(row) = dataview.get_row(row_idx) {
1183 let mut json_row = serde_json::Map::new();
1184 for (col_idx, value) in row.values.iter().enumerate() {
1185 if col_idx < columns.len() {
1186 json_row.insert(columns[col_idx].clone(), value_to_json(value));
1187 }
1188 }
1189 rows.push(serde_json::Value::Object(json_row));
1190 }
1191 }
1192
1193 let json = serde_json::to_string_pretty(&rows)?;
1194 writeln!(writer, "{json}")?;
1195
1196 Ok(())
1197}
1198
1199fn output_json_structured<W: Write>(
1201 dataview: &DataView,
1202 writer: &mut W,
1203 exec_time: f64,
1204) -> Result<()> {
1205 let column_names = dataview.column_names();
1206 let data_table = dataview.source();
1207
1208 let mut columns = Vec::new();
1210 for (idx, name) in column_names.iter().enumerate() {
1211 let col_type = data_table
1212 .columns
1213 .get(idx)
1214 .map(|c| format!("{:?}", c.data_type))
1215 .unwrap_or_else(|| "UNKNOWN".to_string());
1216
1217 let mut max_width = name.len();
1219 for row_idx in 0..dataview.row_count() {
1220 if let Some(row) = dataview.get_row(row_idx) {
1221 if let Some(value) = row.values.get(idx) {
1222 let display_width = match value {
1223 DataValue::Null => 4, DataValue::Integer(i) => i.to_string().len(),
1225 DataValue::Float(f) => format!("{:.2}", f).len(),
1226 DataValue::String(s) => s.len(),
1227 DataValue::InternedString(s) => s.len(),
1228 DataValue::Boolean(b) => {
1229 if *b {
1230 4
1231 } else {
1232 5
1233 }
1234 } DataValue::DateTime(dt) => dt.len(),
1236 };
1237 max_width = max_width.max(display_width);
1238 }
1239 }
1240 }
1241
1242 let alignment = match data_table.columns.get(idx).map(|c| &c.data_type) {
1243 Some(crate::data::datatable::DataType::Integer) => "right",
1244 Some(crate::data::datatable::DataType::Float) => "right",
1245 _ => "left",
1246 };
1247
1248 let col_meta = serde_json::json!({
1249 "name": name,
1250 "type": col_type,
1251 "max_width": max_width,
1252 "alignment": alignment
1253 });
1254 columns.push(col_meta);
1255 }
1256
1257 let mut rows = Vec::new();
1259 for row_idx in 0..dataview.row_count() {
1260 if let Some(row) = dataview.get_row(row_idx) {
1261 let row_values: Vec<String> = row
1262 .values
1263 .iter()
1264 .map(|v| match v {
1265 DataValue::Null => String::new(),
1266 DataValue::Integer(i) => i.to_string(),
1267 DataValue::Float(f) => format!("{:.2}", f),
1268 DataValue::String(s) => s.clone(),
1269 DataValue::InternedString(s) => s.to_string(),
1270 DataValue::Boolean(b) => b.to_string(),
1271 DataValue::DateTime(dt) => dt.clone(),
1272 })
1273 .collect();
1274 rows.push(serde_json::Value::Array(
1275 row_values
1276 .into_iter()
1277 .map(serde_json::Value::String)
1278 .collect(),
1279 ));
1280 }
1281 }
1282
1283 let output = serde_json::json!({
1285 "columns": columns,
1286 "rows": rows,
1287 "metadata": {
1288 "total_rows": dataview.row_count(),
1289 "query_time_ms": exec_time
1290 }
1291 });
1292
1293 let json = serde_json::to_string_pretty(&output)?;
1294 writeln!(writer, "{json}")?;
1295
1296 Ok(())
1297}
1298
1299fn output_table_old_style<W: Write>(
1301 dataview: &DataView,
1302 writer: &mut W,
1303 max_col_width: Option<usize>,
1304) -> Result<()> {
1305 let columns = dataview.column_names();
1306
1307 let mut widths = vec![0; columns.len()];
1309 for (i, col) in columns.iter().enumerate() {
1310 widths[i] = col.len();
1311 }
1312
1313 for row_idx in 0..dataview.row_count() {
1315 if let Some(row) = dataview.get_row(row_idx) {
1316 for (i, value) in row.values.iter().enumerate() {
1317 if i < widths.len() {
1318 let value_str = format_value(value);
1319 widths[i] = widths[i].max(value_str.len());
1320 }
1321 }
1322 }
1323 }
1324
1325 if let Some(max_width) = max_col_width {
1327 for width in &mut widths {
1328 *width = (*width).min(max_width);
1329 }
1330 }
1331
1332 write!(writer, "+")?;
1334 for width in &widths {
1335 write!(writer, "{}", "-".repeat(*width + 2))?;
1336 write!(writer, "+")?;
1337 }
1338 writeln!(writer)?;
1339
1340 write!(writer, "|")?;
1342 for (i, col) in columns.iter().enumerate() {
1343 write!(writer, " {:^width$} |", col, width = widths[i])?;
1344 }
1345 writeln!(writer)?;
1346
1347 write!(writer, "+")?;
1349 for width in &widths {
1350 write!(writer, "{}", "-".repeat(*width + 2))?;
1351 write!(writer, "+")?;
1352 }
1353 writeln!(writer)?;
1354
1355 for row_idx in 0..dataview.row_count() {
1357 if let Some(row) = dataview.get_row(row_idx) {
1358 write!(writer, "|")?;
1359 for (i, value) in row.values.iter().enumerate() {
1360 if i < widths.len() {
1361 let value_str = format_value(value);
1362 let truncated = if value_str.len() > widths[i] {
1363 format!("{}...", &value_str[..widths[i].saturating_sub(3)])
1364 } else {
1365 value_str
1366 };
1367 write!(writer, " {:<width$} |", truncated, width = widths[i])?;
1368 }
1369 }
1370 writeln!(writer)?;
1371 }
1372 }
1373
1374 write!(writer, "+")?;
1376 for width in &widths {
1377 write!(writer, "{}", "-".repeat(*width + 2))?;
1378 write!(writer, "+")?;
1379 }
1380 writeln!(writer)?;
1381
1382 Ok(())
1383}
1384
1385fn output_table<W: Write>(
1387 dataview: &DataView,
1388 writer: &mut W,
1389 max_col_width: Option<usize>,
1390 _col_sample_rows: usize, style: TableStyle,
1392) -> Result<()> {
1393 let mut table = Table::new();
1394
1395 match style {
1397 TableStyle::Default => {
1398 return output_table_old_style(dataview, writer, max_col_width);
1401 }
1402 TableStyle::AsciiFull => {
1403 table.load_preset(ASCII_FULL);
1404 }
1405 TableStyle::AsciiCondensed => {
1406 table.load_preset(ASCII_FULL_CONDENSED);
1407 }
1408 TableStyle::AsciiBordersOnly => {
1409 table.load_preset(ASCII_BORDERS_ONLY);
1410 }
1411 TableStyle::AsciiHorizontalOnly => {
1412 table.load_preset(ASCII_HORIZONTAL_ONLY);
1413 }
1414 TableStyle::AsciiNoBorders => {
1415 table.load_preset(ASCII_NO_BORDERS);
1416 }
1417 TableStyle::Markdown => {
1418 table.load_preset(ASCII_MARKDOWN);
1419 }
1420 TableStyle::Utf8Full => {
1421 table.load_preset(UTF8_FULL);
1422 }
1423 TableStyle::Utf8Condensed => {
1424 table.load_preset(UTF8_FULL_CONDENSED);
1425 }
1426 TableStyle::Utf8BordersOnly => {
1427 table.load_preset(UTF8_BORDERS_ONLY);
1428 }
1429 TableStyle::Utf8HorizontalOnly => {
1430 table.load_preset(UTF8_HORIZONTAL_ONLY);
1431 }
1432 TableStyle::Utf8NoBorders => {
1433 table.load_preset(UTF8_NO_BORDERS);
1434 }
1435 TableStyle::Plain => {
1436 table.load_preset(NOTHING);
1437 }
1438 }
1439
1440 if max_col_width.is_some() {
1442 table.set_content_arrangement(ContentArrangement::Dynamic);
1443 }
1444
1445 let columns = dataview.column_names();
1447 table.set_header(&columns);
1448
1449 for row_idx in 0..dataview.row_count() {
1451 if let Some(row) = dataview.get_row(row_idx) {
1452 let row_strings: Vec<String> = row
1453 .values
1454 .iter()
1455 .map(|v| {
1456 let s = format_value(v);
1457 if let Some(max_width) = max_col_width {
1459 if s.len() > max_width {
1460 format!("{}...", &s[..max_width.saturating_sub(3)])
1461 } else {
1462 s
1463 }
1464 } else {
1465 s
1466 }
1467 })
1468 .collect();
1469 table.add_row(row_strings);
1470 }
1471 }
1472
1473 writeln!(writer, "{}", table)?;
1475
1476 Ok(())
1477}
1478
1479fn format_value(value: &DataValue) -> String {
1481 match value {
1482 DataValue::Null => String::new(),
1483 DataValue::Integer(i) => i.to_string(),
1484 DataValue::Float(f) => f.to_string(),
1485 DataValue::String(s) => s.clone(),
1486 DataValue::InternedString(s) => s.to_string(),
1487 DataValue::Boolean(b) => b.to_string(),
1488 DataValue::DateTime(dt) => dt.to_string(),
1489 }
1490}
1491
1492fn value_to_json(value: &DataValue) -> serde_json::Value {
1494 match value {
1495 DataValue::Null => serde_json::Value::Null,
1496 DataValue::Integer(i) => serde_json::Value::Number((*i).into()),
1497 DataValue::Float(f) => {
1498 if let Some(n) = serde_json::Number::from_f64(*f) {
1499 serde_json::Value::Number(n)
1500 } else {
1501 serde_json::Value::Null
1502 }
1503 }
1504 DataValue::String(s) => serde_json::Value::String(s.clone()),
1505 DataValue::InternedString(s) => serde_json::Value::String(s.to_string()),
1506 DataValue::Boolean(b) => serde_json::Value::Bool(*b),
1507 DataValue::DateTime(dt) => serde_json::Value::String(dt.to_string()),
1508 }
1509}
1510
1511fn escape_csv_field(field: &str, delimiter: char) -> String {
1513 if field.contains(delimiter)
1514 || field.contains('"')
1515 || field.contains('\n')
1516 || field.contains('\r')
1517 {
1518 format!("\"{}\"", field.replace('"', "\"\""))
1519 } else {
1520 field.to_string()
1521 }
1522}