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::services::query_execution_service::QueryExecutionService;
17use crate::sql::parser::ast::{CTEType, TableSource, CTE};
18use crate::sql::recursive_parser::Parser;
19use crate::sql::script_parser::{ScriptParser, ScriptResult};
20use crate::utils::string_utils::display_width;
21
22fn check_temp_table_usage(query: &str) -> Result<()> {
25 use crate::sql::recursive_parser::Parser;
26
27 let mut parser = Parser::new(query);
28 match parser.parse() {
29 Ok(stmt) => {
30 if let Some(from_table) = &stmt.from_table {
32 if from_table.starts_with('#') {
33 anyhow::bail!(
34 "Temporary table '{}' cannot be used in single-query mode. \
35 Temporary tables are only available in script mode (using -f flag with GO separators).",
36 from_table
37 );
38 }
39 }
40
41 if let Some(into_table) = &stmt.into_table {
43 anyhow::bail!(
44 "INTO clause for temporary table '{}' is only supported in script mode. \
45 Use -f flag with GO separators to create temporary tables.",
46 into_table.name
47 );
48 }
49
50 Ok(())
51 }
52 Err(_) => {
53 Ok(())
55 }
56 }
57}
58
59fn extract_cte_dependencies(cte: &CTE) -> Vec<String> {
61 let mut deps = Vec::new();
64
65 if let CTEType::Standard(query) = &cte.cte_type {
66 if let Some(from_table) = &query.from_table {
67 deps.push(from_table.clone());
68 }
69
70 for join in &query.joins {
72 match &join.table {
73 TableSource::Table(table_name) => {
74 deps.push(table_name.clone());
75 }
76 TableSource::DerivedTable { alias, .. } => {
77 deps.push(alias.clone());
78 }
79 TableSource::Pivot { alias, .. } => {
80 if let Some(pivot_alias) = alias {
82 deps.push(pivot_alias.clone());
83 }
84 }
85 }
86 }
87 }
88
89 deps
90}
91
92#[derive(Debug, Clone)]
94pub enum OutputFormat {
95 Csv,
96 Json,
97 JsonStructured, Table,
99 Tsv,
100}
101
102impl OutputFormat {
103 pub fn from_str(s: &str) -> Result<Self> {
104 match s.to_lowercase().as_str() {
105 "csv" => Ok(OutputFormat::Csv),
106 "json" => Ok(OutputFormat::Json),
107 "json-structured" => Ok(OutputFormat::JsonStructured),
108 "table" => Ok(OutputFormat::Table),
109 "tsv" => Ok(OutputFormat::Tsv),
110 _ => Err(anyhow::anyhow!(
111 "Invalid output format: {}. Use csv, json, json-structured, table, or tsv",
112 s
113 )),
114 }
115 }
116}
117
118#[derive(Debug, Clone, Copy)]
120pub enum TableStyle {
121 Default,
123 AsciiFull,
125 AsciiCondensed,
127 AsciiBordersOnly,
129 AsciiHorizontalOnly,
131 AsciiNoBorders,
133 Markdown,
135 Utf8Full,
137 Utf8Condensed,
139 Utf8BordersOnly,
141 Utf8HorizontalOnly,
143 Utf8NoBorders,
145 Plain,
147}
148
149impl TableStyle {
150 pub fn from_str(s: &str) -> Result<Self> {
151 match s.to_lowercase().as_str() {
152 "default" => Ok(TableStyle::Default),
153 "ascii" | "ascii-full" => Ok(TableStyle::AsciiFull),
154 "ascii-condensed" => Ok(TableStyle::AsciiCondensed),
155 "ascii-borders" | "ascii-borders-only" => Ok(TableStyle::AsciiBordersOnly),
156 "ascii-horizontal" | "ascii-horizontal-only" => Ok(TableStyle::AsciiHorizontalOnly),
157 "ascii-noborders" | "ascii-no-borders" => Ok(TableStyle::AsciiNoBorders),
158 "markdown" | "md" => Ok(TableStyle::Markdown),
159 "utf8" | "utf8-full" => Ok(TableStyle::Utf8Full),
160 "utf8-condensed" => Ok(TableStyle::Utf8Condensed),
161 "utf8-borders" | "utf8-borders-only" => Ok(TableStyle::Utf8BordersOnly),
162 "utf8-horizontal" | "utf8-horizontal-only" => Ok(TableStyle::Utf8HorizontalOnly),
163 "utf8-noborders" | "utf8-no-borders" => Ok(TableStyle::Utf8NoBorders),
164 "plain" | "none" => Ok(TableStyle::Plain),
165 _ => Err(anyhow::anyhow!(
166 "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",
167 s
168 )),
169 }
170 }
171
172 pub fn list_styles() -> &'static str {
173 "Available table styles:
174 default - Current default ASCII style
175 ascii - ASCII with full borders
176 ascii-condensed - ASCII with condensed rows
177 ascii-borders - ASCII with outer borders only
178 ascii-horizontal - ASCII with horizontal lines only
179 ascii-noborders - ASCII with no borders
180 markdown - GitHub-flavored Markdown table
181 utf8 - UTF8 box-drawing characters
182 utf8-condensed - UTF8 with condensed rows
183 utf8-borders - UTF8 with outer borders only
184 utf8-horizontal - UTF8 with horizontal lines only
185 utf8-noborders - UTF8 with no borders
186 plain - No formatting, data only"
187 }
188}
189
190pub struct NonInteractiveConfig {
192 pub data_file: String,
193 pub query: String,
194 pub output_format: OutputFormat,
195 pub output_file: Option<String>,
196 pub case_insensitive: bool,
197 pub auto_hide_empty: bool,
198 pub limit: Option<usize>,
199 pub query_plan: bool,
200 pub show_work_units: bool,
201 pub execution_plan: bool,
202 pub show_preprocessing: bool,
203 pub show_transformations: bool,
204 pub cte_info: bool,
205 pub rewrite_analysis: bool,
206 pub lift_in_expressions: bool,
207 pub script_file: Option<String>, pub debug_trace: bool,
209 pub max_col_width: Option<usize>, pub col_sample_rows: usize, pub table_style: TableStyle, pub styled: bool, pub style_file: Option<String>, pub no_where_expansion: bool, pub no_group_by_expansion: bool, pub no_having_expansion: bool, pub no_order_by_expansion: bool, pub no_qualify_to_where: bool, pub no_expression_lifter: bool, pub no_cte_hoister: bool, pub no_in_lifter: bool, }
223
224fn make_transformer_config(config: &NonInteractiveConfig) -> crate::query_plan::TransformerConfig {
226 crate::query_plan::TransformerConfig {
227 enable_pivot_expander: true, enable_expression_lifter: !config.no_expression_lifter,
229 enable_where_expansion: !config.no_where_expansion,
230 enable_group_by_expansion: !config.no_group_by_expansion,
231 enable_having_expansion: !config.no_having_expansion,
232 enable_order_by_expansion: !config.no_order_by_expansion,
233 enable_qualify_to_where: !config.no_qualify_to_where,
234 enable_ilike_to_like: true, enable_cte_hoister: !config.no_cte_hoister,
236 enable_in_lifter: !config.no_in_lifter,
237 }
238}
239
240pub fn execute_non_interactive(config: NonInteractiveConfig) -> Result<()> {
242 let start_time = Instant::now();
243
244 let data_file_to_use = if config.data_file.is_empty() {
249 let lines: Vec<&str> = config.query.lines().take(10).collect();
251 let mut hint_path: Option<String> = None;
252
253 for line in lines {
254 let trimmed = line.trim();
255 if trimmed.starts_with("-- #!") {
256 hint_path = Some(trimmed[5..].trim().to_string());
257 break;
258 }
259 }
260
261 if let Some(path) = hint_path {
262 debug!("Found data file hint: {}", path);
263 let resolved_path = if path.starts_with("../") {
265 std::path::Path::new(&path).to_path_buf()
267 } else if path.starts_with("data/") {
268 std::path::Path::new(&path).to_path_buf()
270 } else {
271 std::path::Path::new(&path).to_path_buf()
272 };
273
274 if resolved_path.exists() {
275 info!("Using data file from hint: {:?}", resolved_path);
276 resolved_path.to_string_lossy().to_string()
277 } else {
278 debug!("Data file hint path does not exist: {:?}", resolved_path);
279 String::new()
280 }
281 } else {
282 String::new()
283 }
284 } else {
285 config.data_file.clone()
286 };
287
288 let (data_table, _is_dual) = if data_file_to_use.is_empty() {
290 info!("No data file provided, using DUAL table");
291 (crate::data::datatable::DataTable::dual(), true)
292 } else {
293 info!("Loading data from: {}", data_file_to_use);
294 let table = load_data_file(&data_file_to_use)?;
295 info!(
296 "Loaded {} rows with {} columns",
297 table.row_count(),
298 table.column_count()
299 );
300 (table, false)
301 };
302 let _table_name = data_table.name.clone();
303
304 use crate::execution::{ExecutionConfig, ExecutionContext, StatementExecutor};
306 let mut context = ExecutionContext::new(std::sync::Arc::new(data_table));
307
308 let dataview = DataView::new(context.source_table.clone());
310
311 info!("Executing query: {}", config.query);
313
314 if config.execution_plan {
316 println!("\n=== EXECUTION PLAN ===");
317 println!("Query: {}", config.query);
318 println!("\nExecution Steps:");
319 println!("1. PARSE - Parse SQL query");
320 println!("2. LOAD_DATA - Load data from {}", &config.data_file);
321 println!(
322 " • Loaded {} rows, {} columns",
323 dataview.row_count(),
324 dataview.column_count()
325 );
326 }
327
328 if config.show_work_units {
330 use crate::query_plan::{ExpressionLifter, QueryAnalyzer};
331 use crate::sql::recursive_parser::Parser;
332
333 let mut parser = Parser::new(&config.query);
334 match parser.parse() {
335 Ok(stmt) => {
336 let mut analyzer = QueryAnalyzer::new();
337 let mut lifter = ExpressionLifter::new();
338
339 let mut stmt_copy = stmt.clone();
341 let lifted = lifter.lift_expressions(&mut stmt_copy);
342
343 match analyzer.analyze(&stmt_copy, config.query.clone()) {
345 Ok(plan) => {
346 println!("\n{}", plan.explain());
347
348 if !lifted.is_empty() {
349 println!("\nLifted CTEs:");
350 for cte in &lifted {
351 println!(" - {}", cte.name);
352 }
353 }
354
355 return Ok(());
356 }
357 Err(e) => {
358 eprintln!("Error analyzing query: {}", e);
359 return Err(anyhow::anyhow!("Query analysis failed: {}", e));
360 }
361 }
362 }
363 Err(e) => {
364 eprintln!("Error parsing query: {}", e);
365 return Err(anyhow::anyhow!("Parse error: {}", e));
366 }
367 }
368 }
369
370 if config.query_plan {
372 use crate::sql::recursive_parser::Parser;
373 let mut parser = Parser::new(&config.query);
374 match parser.parse() {
375 Ok(statement) => {
376 println!("\n=== QUERY PLAN (AST) ===");
377 println!("{statement:#?}");
378 println!("=== END QUERY PLAN ===\n");
379 }
380 Err(e) => {
381 eprintln!("Failed to parse query for plan: {e}");
382 }
383 }
384 }
385
386 if config.rewrite_analysis {
388 use crate::sql::query_rewriter::{QueryRewriter, RewriteAnalysis};
389 use crate::sql::recursive_parser::Parser;
390 use serde_json::json;
391
392 let mut parser = Parser::new(&config.query);
393 match parser.parse() {
394 Ok(statement) => {
395 let mut rewriter = QueryRewriter::new();
396 let suggestions = rewriter.analyze(&statement);
397
398 let analysis = RewriteAnalysis::from_suggestions(suggestions);
399 println!("{}", serde_json::to_string_pretty(&analysis).unwrap());
400 return Ok(());
401 }
402 Err(e) => {
403 let output = json!({
404 "success": false,
405 "error": format!("{}", e),
406 "suggestions": [],
407 "can_auto_rewrite": false,
408 });
409 println!("{}", serde_json::to_string_pretty(&output).unwrap());
410 return Ok(());
411 }
412 }
413 }
414
415 if config.cte_info {
417 use crate::sql::recursive_parser::Parser;
418 use serde_json::json;
419
420 let mut parser = Parser::new(&config.query);
421 match parser.parse() {
422 Ok(statement) => {
423 let mut cte_info = Vec::new();
424
425 for (index, cte) in statement.ctes.iter().enumerate() {
427 let cte_json = json!({
428 "index": index,
429 "name": cte.name,
430 "columns": cte.column_list,
431 "dependencies": extract_cte_dependencies(cte),
434 });
435 cte_info.push(cte_json);
436 }
437
438 let output = json!({
439 "success": true,
440 "ctes": cte_info,
441 "total": statement.ctes.len(),
442 "has_final_select": !statement.columns.is_empty() || !statement.select_items.is_empty(),
443 });
444
445 println!("{}", serde_json::to_string_pretty(&output).unwrap());
446 return Ok(());
447 }
448 Err(e) => {
449 let output = json!({
450 "success": false,
451 "error": format!("{}", e),
452 "ctes": [],
453 "total": 0,
454 });
455 println!("{}", serde_json::to_string_pretty(&output).unwrap());
456 return Ok(());
457 }
458 }
459 }
460
461 let query_start = Instant::now();
462
463 let app_config = Config::load().unwrap_or_else(|e| {
465 debug!("Could not load config file: {}. Using defaults.", e);
466 Config::default()
467 });
468
469 crate::config::global::init_config(app_config.clone());
471
472 let exec_config = ExecutionConfig::from_cli_flags(
474 config.show_preprocessing,
475 config.show_transformations,
476 config.case_insensitive,
477 config.auto_hide_empty,
478 config.no_expression_lifter,
479 config.no_where_expansion,
480 config.no_group_by_expansion,
481 config.no_having_expansion,
482 config.no_order_by_expansion,
483 config.no_qualify_to_where,
484 config.no_cte_hoister,
485 config.no_in_lifter,
486 config.debug_trace,
487 );
488
489 let executor = StatementExecutor::with_config(exec_config);
491
492 let exec_start = Instant::now();
494 let result = {
495 use crate::sql::recursive_parser::Parser;
496
497 use crate::sql::template_expander::TemplateExpander;
499 let expander = TemplateExpander::new(&context.temp_tables);
500
501 let query_to_parse = match expander.parse_templates(&config.query) {
502 Ok(vars) => {
503 if vars.is_empty() {
504 config.query.clone()
505 } else {
506 match expander.expand(&config.query, &vars) {
507 Ok(expanded) => {
508 debug!(
509 "Template expansion in single query mode: {} vars expanded",
510 vars.len()
511 );
512 for var in &vars {
513 debug!(" {} -> resolved", var.placeholder);
514 }
515 expanded
516 }
517 Err(e) => {
518 return Err(anyhow::anyhow!(
519 "Template expansion failed: {}. Available tables: {}",
520 e,
521 context.temp_tables.list_tables().join(", ")
522 ));
523 }
524 }
525 }
526 }
527 Err(e) => {
528 return Err(anyhow::anyhow!("Template parsing failed: {}", e));
529 }
530 };
531
532 let mut parser = Parser::new(&query_to_parse);
533 match parser.parse() {
534 Ok(stmt) => {
535 match executor.execute(stmt, &mut context) {
541 Ok(exec_result) => {
542 Ok(
544 crate::services::query_execution_service::QueryExecutionResult {
545 dataview: exec_result.dataview,
546 stats: crate::services::query_execution_service::QueryStats {
547 row_count: exec_result.stats.row_count,
548 column_count: exec_result.stats.column_count,
549 execution_time: exec_start.elapsed(),
550 query_engine_time: exec_start.elapsed(),
551 },
552 hidden_columns: Vec::new(),
553 query: config.query.clone(),
554 execution_plan: None,
555 debug_trace: None,
556 },
557 )
558 }
559 Err(e) => Err(e),
560 }
561 }
562 Err(e) => {
563 Err(anyhow::anyhow!("Parse error: {}", e))
565 }
566 }
567 }?;
568 let exec_time = exec_start.elapsed();
569
570 let query_time = query_start.elapsed();
571 info!("Query executed in {:?}", query_time);
572 info!(
573 "Result: {} rows, {} columns",
574 result.dataview.row_count(),
575 result.dataview.column_count()
576 );
577
578 if config.execution_plan {
580 use crate::data::query_engine::QueryEngine;
582
583 let query_engine = QueryEngine::new();
584
585 match query_engine.execute_with_plan(
586 std::sync::Arc::new(dataview.source().clone()),
587 &config.query,
588 ) {
589 Ok((_view, plan)) => {
590 print!("{}", plan.format_tree());
592 }
593 Err(e) => {
594 eprintln!("Could not generate detailed execution plan: {}", e);
596 println!(
597 "3. QUERY_EXECUTION [{:.3}ms]",
598 exec_time.as_secs_f64() * 1000.0
599 );
600
601 use crate::sql::recursive_parser::Parser;
603 let mut parser = Parser::new(&config.query);
604 if let Ok(stmt) = parser.parse() {
605 if stmt.where_clause.is_some() {
606 println!(" • WHERE clause filtering applied");
607 println!(" • Rows after filter: {}", result.dataview.row_count());
608 }
609
610 if let Some(ref order_by) = stmt.order_by {
611 println!(" • ORDER BY: {} column(s)", order_by.len());
612 }
613
614 if let Some(ref group_by) = stmt.group_by {
615 println!(" • GROUP BY: {} column(s)", group_by.len());
616 }
617
618 if let Some(limit) = stmt.limit {
619 println!(" • LIMIT: {} rows", limit);
620 }
621
622 if stmt.distinct {
623 println!(" • DISTINCT applied");
624 }
625 }
626 }
627 }
628
629 println!("\nExecution Statistics:");
630 println!(
631 " Preparation: {:.3}ms",
632 (exec_start - start_time).as_secs_f64() * 1000.0
633 );
634 println!(
635 " Query time: {:.3}ms",
636 exec_time.as_secs_f64() * 1000.0
637 );
638 println!(
639 " Total time: {:.3}ms",
640 query_time.as_secs_f64() * 1000.0
641 );
642 println!(" Rows returned: {}", result.dataview.row_count());
643 println!(" Columns: {}", result.dataview.column_count());
644 println!("\n=== END EXECUTION PLAN ===");
645 println!();
646 }
647
648 let final_view = if let Some(limit) = config.limit {
650 let limited_table = limit_results(&result.dataview, limit)?;
651 DataView::new(std::sync::Arc::new(limited_table))
652 } else {
653 result.dataview
654 };
655
656 if let Some(ref trace_output) = result.debug_trace {
658 eprintln!("{}", trace_output);
659 }
660
661 let exec_time_ms = exec_time.as_secs_f64() * 1000.0;
663 let output_result = if let Some(ref path) = config.output_file {
664 let mut file = fs::File::create(path)
665 .with_context(|| format!("Failed to create output file: {path}"))?;
666 output_results(
667 &final_view,
668 config.output_format,
669 &mut file,
670 config.max_col_width,
671 config.col_sample_rows,
672 exec_time_ms,
673 config.table_style,
674 config.styled,
675 config.style_file.as_deref(),
676 )?;
677 info!("Results written to: {}", path);
678 Ok(())
679 } else {
680 output_results(
681 &final_view,
682 config.output_format,
683 &mut io::stdout(),
684 config.max_col_width,
685 config.col_sample_rows,
686 exec_time_ms,
687 config.table_style,
688 config.styled,
689 config.style_file.as_deref(),
690 )?;
691 Ok(())
692 };
693
694 let total_time = start_time.elapsed();
695 debug!("Total execution time: {:?}", total_time);
696
697 if config.output_file.is_none() {
699 eprintln!(
700 "\n# Query completed: {} rows in {:?}",
701 final_view.row_count(),
702 query_time
703 );
704 }
705
706 output_result
707}
708
709pub fn execute_script(config: NonInteractiveConfig) -> Result<()> {
711 let _start_time = Instant::now();
712
713 let parser = ScriptParser::new(&config.query);
715 let script_statements = parser.parse_script_statements();
716
717 if script_statements.is_empty() {
718 anyhow::bail!("No statements found in script");
719 }
720
721 info!("Found {} statements in script", script_statements.len());
722
723 let data_file = if !config.data_file.is_empty() {
725 config.data_file.clone()
727 } else if let Some(hint) = parser.data_file_hint() {
728 info!("Using data file from script hint: {}", hint);
730
731 if let Some(script_path) = config.script_file.as_ref() {
733 let script_dir = std::path::Path::new(script_path)
734 .parent()
735 .unwrap_or(std::path::Path::new("."));
736 let hint_path = std::path::Path::new(hint);
737
738 if hint_path.is_relative() {
739 script_dir.join(hint_path).to_string_lossy().to_string()
740 } else {
741 hint.to_string()
742 }
743 } else {
744 hint.to_string()
745 }
746 } else {
747 String::new()
748 };
749
750 let (data_table, _is_dual) = if data_file.is_empty() {
752 info!("No data file provided, using DUAL table");
754 (DataTable::dual(), true)
755 } else {
756 if !std::path::Path::new(&data_file).exists() {
758 anyhow::bail!(
759 "Data file not found: {}\n\
760 Please check the path is correct",
761 data_file
762 );
763 }
764
765 info!("Loading data from: {}", data_file);
766 let table = load_data_file(&data_file)?;
767 info!(
768 "Loaded {} rows with {} columns",
769 table.row_count(),
770 table.column_count()
771 );
772 (table, false)
773 };
774
775 let mut script_result = ScriptResult::new();
777 let mut output = Vec::new();
778
779 use crate::execution::{ExecutionConfig, ExecutionContext, StatementExecutor};
781 let mut context = ExecutionContext::new(std::sync::Arc::new(data_table));
782
783 let exec_config = ExecutionConfig::from_cli_flags(
785 config.show_preprocessing,
786 config.show_transformations,
787 config.case_insensitive,
788 config.auto_hide_empty,
789 config.no_expression_lifter,
790 config.no_where_expansion,
791 config.no_group_by_expansion,
792 config.no_having_expansion,
793 config.no_order_by_expansion,
794 config.no_qualify_to_where,
795 config.no_cte_hoister,
796 config.no_in_lifter,
797 false, );
799
800 let executor = StatementExecutor::with_config(exec_config);
802
803 for (idx, script_stmt) in script_statements.iter().enumerate() {
805 let statement_num = idx + 1;
806 let stmt_start = Instant::now();
807
808 if script_stmt.is_exit() {
810 let exit_code = script_stmt.get_exit_code().unwrap_or(0);
811 info!("EXIT statement encountered (code: {})", exit_code);
812
813 if matches!(config.output_format, OutputFormat::Table) {
815 if idx > 0 {
816 output.push(String::new());
817 }
818 output.push(format!("-- Statement {} --", statement_num));
819 output.push(format!("Script execution stopped by EXIT {}", exit_code));
820 }
821
822 script_result.add_success(
824 statement_num,
825 format!("EXIT {}", exit_code),
826 0,
827 stmt_start.elapsed().as_secs_f64() * 1000.0,
828 );
829
830 break;
832 }
833
834 if script_stmt.should_skip() {
836 info!(
837 "Skipping statement {} due to [SKIP] directive",
838 statement_num
839 );
840
841 if matches!(config.output_format, OutputFormat::Table) {
843 if idx > 0 {
844 output.push(String::new());
845 }
846 output.push(format!("-- Statement {} [SKIPPED] --", statement_num));
847 }
848
849 script_result.add_success(
851 statement_num,
852 "[SKIPPED]".to_string(),
853 0,
854 stmt_start.elapsed().as_secs_f64() * 1000.0,
855 );
856
857 continue;
858 }
859
860 let statement = match script_stmt.get_query() {
862 Some(sql) => sql,
863 None => continue, };
865
866 if matches!(config.output_format, OutputFormat::Table) {
868 if idx > 0 {
869 output.push(String::new()); }
871 output.push(format!("-- Query {} --", statement_num));
872 }
873
874 use crate::sql::template_expander::TemplateExpander;
877 let expander = TemplateExpander::new(&context.temp_tables);
878
879 let expanded_statement = match expander.parse_templates(statement) {
880 Ok(vars) => {
881 if vars.is_empty() {
882 statement.to_string()
883 } else {
884 match expander.expand(statement, &vars) {
885 Ok(expanded) => {
886 debug!("Expanded templates in SQL: {} vars found", vars.len());
887 for var in &vars {
888 debug!(
889 " {} -> expanding from {}",
890 var.placeholder, var.table_name
891 );
892 }
893 expanded
894 }
895 Err(e) => {
896 script_result.add_failure(
897 statement_num,
898 statement.to_string(),
899 format!("Template expansion error: {}", e),
900 stmt_start.elapsed().as_secs_f64() * 1000.0,
901 );
902 continue; }
904 }
905 }
906 }
907 Err(e) => {
908 script_result.add_failure(
909 statement_num,
910 statement.to_string(),
911 format!("Template parse error: {}", e),
912 stmt_start.elapsed().as_secs_f64() * 1000.0,
913 );
914 continue; }
916 };
917
918 let statement = expanded_statement.as_str();
920
921 let mut parser = Parser::new(statement);
923 let parsed_stmt = match parser.parse() {
924 Ok(stmt) => stmt,
925 Err(e) => {
926 script_result.add_failure(
928 statement_num,
929 statement.to_string(),
930 format!("Parse error: {}", e),
931 stmt_start.elapsed().as_secs_f64() * 1000.0,
932 );
933 break;
934 }
935 };
936
937 if let Some(from_table) = &parsed_stmt.from_table {
940 if from_table.starts_with('#') && !context.has_temp_table(from_table) {
941 script_result.add_failure(
942 statement_num,
943 statement.to_string(),
944 format!("Temporary table {} not found", from_table),
945 stmt_start.elapsed().as_secs_f64() * 1000.0,
946 );
947 break;
948 }
949 }
950
951 let into_table = parsed_stmt.into_table.clone();
954 let stmt_without_into = if into_table.is_some() {
955 use crate::query_plan::IntoClauseRemover;
956 IntoClauseRemover::remove_into_clause(parsed_stmt)
957 } else {
958 parsed_stmt
959 };
960
961 let result = executor.execute(stmt_without_into, &mut context);
967
968 match result {
969 Ok(exec_result) => {
970 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
971 let final_view = exec_result.dataview;
972
973 if let Some(into_table) = &into_table {
975 let result_table = final_view.source_arc();
977 let row_count = result_table.row_count();
978
979 match context.store_temp_table(into_table.name.clone(), result_table) {
981 Ok(_) => {
982 info!(
983 "Stored {} rows in temporary table {}",
984 row_count, into_table.name
985 );
986
987 if matches!(config.output_format, OutputFormat::Table) {
990 let mut statement_output = Vec::new();
991 writeln!(
992 &mut statement_output,
993 "({} rows affected) -> {}",
994 row_count, into_table.name
995 )?;
996 output.extend(
997 String::from_utf8_lossy(&statement_output)
998 .lines()
999 .map(String::from),
1000 );
1001 }
1002
1003 script_result.add_success(
1004 statement_num,
1005 statement.to_string(),
1006 row_count,
1007 exec_time,
1008 );
1009 continue; }
1011 Err(e) => {
1012 script_result.add_failure(
1013 statement_num,
1014 statement.to_string(),
1015 e.to_string(),
1016 exec_time,
1017 );
1018 break;
1019 }
1020 }
1021 }
1022
1023 let mut statement_output = Vec::new();
1025 match config.output_format {
1026 OutputFormat::Csv => {
1027 output_csv(&final_view, &mut statement_output, ',')?;
1028 }
1029 OutputFormat::Json => {
1030 output_json(&final_view, &mut statement_output)?;
1031 }
1032 OutputFormat::JsonStructured => {
1033 output_json_structured(&final_view, &mut statement_output, exec_time)?;
1034 }
1035 OutputFormat::Table => {
1036 output_table(
1037 &final_view,
1038 &mut statement_output,
1039 config.max_col_width,
1040 config.col_sample_rows,
1041 config.table_style,
1042 config.styled,
1043 config.style_file.as_deref(),
1044 )?;
1045 writeln!(
1046 &mut statement_output,
1047 "Query completed: {} rows in {:.2}ms",
1048 final_view.row_count(),
1049 exec_time
1050 )?;
1051 }
1052 OutputFormat::Tsv => {
1053 output_csv(&final_view, &mut statement_output, '\t')?;
1054 }
1055 }
1056
1057 output.extend(
1059 String::from_utf8_lossy(&statement_output)
1060 .lines()
1061 .map(String::from),
1062 );
1063
1064 script_result.add_success(
1065 statement_num,
1066 statement.to_string(),
1067 final_view.row_count(),
1068 exec_time,
1069 );
1070 }
1071 Err(e) => {
1072 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
1073 let error_msg = format!("Query {} failed: {}", statement_num, e);
1074
1075 if matches!(config.output_format, OutputFormat::Table) {
1076 output.push(error_msg.clone());
1077 }
1078
1079 script_result.add_failure(
1080 statement_num,
1081 statement.to_string(),
1082 e.to_string(),
1083 exec_time,
1084 );
1085
1086 }
1088 }
1089 }
1090
1091 if let Some(ref output_file) = config.output_file {
1093 let mut file = fs::File::create(output_file)?;
1094 for line in &output {
1095 writeln!(file, "{}", line)?;
1096 }
1097 info!("Results written to: {}", output_file);
1098 } else {
1099 for line in &output {
1100 println!("{}", line);
1101 }
1102 }
1103
1104 if matches!(config.output_format, OutputFormat::Table) {
1106 println!("\n=== Script Summary ===");
1107 println!("Total statements: {}", script_result.total_statements);
1108 println!("Successful: {}", script_result.successful_statements);
1109 println!("Failed: {}", script_result.failed_statements);
1110 println!(
1111 "Total execution time: {:.2}ms",
1112 script_result.total_execution_time_ms
1113 );
1114 }
1115
1116 if !script_result.all_successful() {
1117 return Err(anyhow::anyhow!(
1118 "{} of {} statements failed",
1119 script_result.failed_statements,
1120 script_result.total_statements
1121 ));
1122 }
1123
1124 Ok(())
1125}
1126
1127fn load_data_file(path: &str) -> Result<DataTable> {
1129 let path = Path::new(path);
1130
1131 if !path.exists() {
1132 return Err(anyhow::anyhow!("File not found: {}", path.display()));
1133 }
1134
1135 let extension = path
1137 .extension()
1138 .and_then(|ext| ext.to_str())
1139 .map(str::to_lowercase)
1140 .unwrap_or_default();
1141
1142 let table_name = path
1143 .file_stem()
1144 .and_then(|stem| stem.to_str())
1145 .unwrap_or("data")
1146 .to_string();
1147
1148 match extension.as_str() {
1149 "csv" => load_csv_to_datatable(path, &table_name)
1150 .with_context(|| format!("Failed to load CSV file: {}", path.display())),
1151 "json" => load_json_to_datatable(path, &table_name)
1152 .with_context(|| format!("Failed to load JSON file: {}", path.display())),
1153 _ => Err(anyhow::anyhow!(
1154 "Unsupported file type: {}. Use .csv or .json",
1155 extension
1156 )),
1157 }
1158}
1159
1160fn limit_results(dataview: &DataView, limit: usize) -> Result<DataTable> {
1162 let source = dataview.source();
1163 let mut limited_table = DataTable::new(&source.name);
1164
1165 for col in &source.columns {
1167 limited_table.add_column(col.clone());
1168 }
1169
1170 let rows_to_copy = dataview.row_count().min(limit);
1172 for i in 0..rows_to_copy {
1173 if let Some(row) = dataview.get_row(i) {
1174 let _ = limited_table.add_row(row.clone());
1175 }
1176 }
1177
1178 Ok(limited_table)
1179}
1180
1181fn output_results<W: Write>(
1183 dataview: &DataView,
1184 format: OutputFormat,
1185 writer: &mut W,
1186 max_col_width: Option<usize>,
1187 col_sample_rows: usize,
1188 exec_time_ms: f64,
1189 table_style: TableStyle,
1190 styled: bool,
1191 style_file: Option<&str>,
1192) -> Result<()> {
1193 match format {
1194 OutputFormat::Csv => output_csv(dataview, writer, ','),
1195 OutputFormat::Tsv => output_csv(dataview, writer, '\t'),
1196 OutputFormat::Json => output_json(dataview, writer),
1197 OutputFormat::JsonStructured => output_json_structured(dataview, writer, exec_time_ms),
1198 OutputFormat::Table => output_table(
1199 dataview,
1200 writer,
1201 max_col_width,
1202 col_sample_rows,
1203 table_style,
1204 styled,
1205 style_file,
1206 ),
1207 }
1208}
1209
1210fn output_csv<W: Write>(dataview: &DataView, writer: &mut W, delimiter: char) -> Result<()> {
1212 let columns = dataview.column_names();
1214 for (i, col) in columns.iter().enumerate() {
1215 if i > 0 {
1216 write!(writer, "{delimiter}")?;
1217 }
1218 write!(writer, "{}", escape_csv_field(col, delimiter))?;
1219 }
1220 writeln!(writer)?;
1221
1222 for row_idx in 0..dataview.row_count() {
1224 if let Some(row) = dataview.get_row(row_idx) {
1225 for (i, value) in row.values.iter().enumerate() {
1226 if i > 0 {
1227 write!(writer, "{delimiter}")?;
1228 }
1229 write!(
1230 writer,
1231 "{}",
1232 escape_csv_field(&format_value(value), delimiter)
1233 )?;
1234 }
1235 writeln!(writer)?;
1236 }
1237 }
1238
1239 Ok(())
1240}
1241
1242fn output_json<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
1244 let columns = dataview.column_names();
1245 let mut rows = Vec::new();
1246
1247 for row_idx in 0..dataview.row_count() {
1248 if let Some(row) = dataview.get_row(row_idx) {
1249 let mut json_row = serde_json::Map::new();
1250 for (col_idx, value) in row.values.iter().enumerate() {
1251 if col_idx < columns.len() {
1252 json_row.insert(columns[col_idx].clone(), value_to_json(value));
1253 }
1254 }
1255 rows.push(serde_json::Value::Object(json_row));
1256 }
1257 }
1258
1259 let json = serde_json::to_string_pretty(&rows)?;
1260 writeln!(writer, "{json}")?;
1261
1262 Ok(())
1263}
1264
1265fn output_json_structured<W: Write>(
1267 dataview: &DataView,
1268 writer: &mut W,
1269 exec_time: f64,
1270) -> Result<()> {
1271 let column_names = dataview.column_names();
1272 let data_table = dataview.source();
1273
1274 let mut columns = Vec::new();
1276 for (idx, name) in column_names.iter().enumerate() {
1277 let col_type = data_table
1278 .columns
1279 .get(idx)
1280 .map(|c| format!("{:?}", c.data_type))
1281 .unwrap_or_else(|| "UNKNOWN".to_string());
1282
1283 let mut max_width = name.len();
1285 for row_idx in 0..dataview.row_count() {
1286 if let Some(row) = dataview.get_row(row_idx) {
1287 if let Some(value) = row.values.get(idx) {
1288 let display_width = match value {
1289 DataValue::Null => 4, DataValue::Integer(i) => i.to_string().len(),
1291 DataValue::Float(f) => format!("{:.2}", f).len(),
1292 DataValue::String(s) => s.len(),
1293 DataValue::InternedString(s) => s.len(),
1294 DataValue::Boolean(b) => {
1295 if *b {
1296 4
1297 } else {
1298 5
1299 }
1300 } DataValue::DateTime(dt) => dt.len(),
1302 DataValue::Vector(v) => {
1303 let components: Vec<String> = v.iter().map(|f| f.to_string()).collect();
1304 format!("[{}]", components.join(",")).len()
1305 }
1306 };
1307 max_width = max_width.max(display_width);
1308 }
1309 }
1310 }
1311
1312 let alignment = match data_table.columns.get(idx).map(|c| &c.data_type) {
1313 Some(crate::data::datatable::DataType::Integer) => "right",
1314 Some(crate::data::datatable::DataType::Float) => "right",
1315 _ => "left",
1316 };
1317
1318 let col_meta = serde_json::json!({
1319 "name": name,
1320 "type": col_type,
1321 "max_width": max_width,
1322 "alignment": alignment
1323 });
1324 columns.push(col_meta);
1325 }
1326
1327 let mut rows = Vec::new();
1329 for row_idx in 0..dataview.row_count() {
1330 if let Some(row) = dataview.get_row(row_idx) {
1331 let row_values: Vec<String> = row
1332 .values
1333 .iter()
1334 .map(|v| match v {
1335 DataValue::Null => String::new(),
1336 DataValue::Integer(i) => i.to_string(),
1337 DataValue::Float(f) => format!("{:.2}", f),
1338 DataValue::String(s) => s.clone(),
1339 DataValue::InternedString(s) => s.to_string(),
1340 DataValue::Boolean(b) => b.to_string(),
1341 DataValue::DateTime(dt) => dt.clone(),
1342 DataValue::Vector(v) => {
1343 let components: Vec<String> = v.iter().map(|f| f.to_string()).collect();
1344 format!("[{}]", components.join(","))
1345 }
1346 })
1347 .collect();
1348 rows.push(serde_json::Value::Array(
1349 row_values
1350 .into_iter()
1351 .map(serde_json::Value::String)
1352 .collect(),
1353 ));
1354 }
1355 }
1356
1357 let output = serde_json::json!({
1359 "columns": columns,
1360 "rows": rows,
1361 "metadata": {
1362 "total_rows": dataview.row_count(),
1363 "query_time_ms": exec_time
1364 }
1365 });
1366
1367 let json = serde_json::to_string_pretty(&output)?;
1368 writeln!(writer, "{json}")?;
1369
1370 Ok(())
1371}
1372
1373fn output_table_old_style<W: Write>(
1375 dataview: &DataView,
1376 writer: &mut W,
1377 max_col_width: Option<usize>,
1378) -> Result<()> {
1379 let columns = dataview.column_names();
1380
1381 let mut widths = vec![0; columns.len()];
1383 for (i, col) in columns.iter().enumerate() {
1384 widths[i] = col.len();
1385 }
1386
1387 for row_idx in 0..dataview.row_count() {
1389 if let Some(row) = dataview.get_row(row_idx) {
1390 for (i, value) in row.values.iter().enumerate() {
1391 if i < widths.len() {
1392 let value_str = format_value(value);
1393 widths[i] = widths[i].max(display_width(&value_str));
1394 }
1395 }
1396 }
1397 }
1398
1399 if let Some(max_width) = max_col_width {
1401 for width in &mut widths {
1402 *width = (*width).min(max_width);
1403 }
1404 }
1405
1406 write!(writer, "+")?;
1408 for width in &widths {
1409 write!(writer, "{}", "-".repeat(*width + 2))?;
1410 write!(writer, "+")?;
1411 }
1412 writeln!(writer)?;
1413
1414 write!(writer, "|")?;
1416 for (i, col) in columns.iter().enumerate() {
1417 write!(writer, " {:^width$} |", col, width = widths[i])?;
1418 }
1419 writeln!(writer)?;
1420
1421 write!(writer, "+")?;
1423 for width in &widths {
1424 write!(writer, "{}", "-".repeat(*width + 2))?;
1425 write!(writer, "+")?;
1426 }
1427 writeln!(writer)?;
1428
1429 for row_idx in 0..dataview.row_count() {
1431 if let Some(row) = dataview.get_row(row_idx) {
1432 write!(writer, "|")?;
1433 for (i, value) in row.values.iter().enumerate() {
1434 if i < widths.len() {
1435 let value_str = format_value(value);
1436 let display_len = display_width(&value_str);
1437
1438 write!(writer, " {}", value_str)?;
1441 let padding_needed = if display_len < widths[i] {
1442 widths[i] - display_len
1443 } else {
1444 0
1445 };
1446 write!(writer, "{} |", " ".repeat(padding_needed))?;
1447 }
1448 }
1449 writeln!(writer)?;
1450 }
1451 }
1452
1453 write!(writer, "+")?;
1455 for width in &widths {
1456 write!(writer, "{}", "-".repeat(*width + 2))?;
1457 write!(writer, "+")?;
1458 }
1459 writeln!(writer)?;
1460
1461 Ok(())
1462}
1463
1464fn output_table<W: Write>(
1466 dataview: &DataView,
1467 writer: &mut W,
1468 max_col_width: Option<usize>,
1469 _col_sample_rows: usize, style: TableStyle,
1471 styled: bool,
1472 style_file: Option<&str>,
1473) -> Result<()> {
1474 let mut table = Table::new();
1475
1476 match style {
1478 TableStyle::Default => {
1479 return output_table_old_style(dataview, writer, max_col_width);
1482 }
1483 TableStyle::AsciiFull => {
1484 table.load_preset(ASCII_FULL);
1485 }
1486 TableStyle::AsciiCondensed => {
1487 table.load_preset(ASCII_FULL_CONDENSED);
1488 }
1489 TableStyle::AsciiBordersOnly => {
1490 table.load_preset(ASCII_BORDERS_ONLY);
1491 }
1492 TableStyle::AsciiHorizontalOnly => {
1493 table.load_preset(ASCII_HORIZONTAL_ONLY);
1494 }
1495 TableStyle::AsciiNoBorders => {
1496 table.load_preset(ASCII_NO_BORDERS);
1497 }
1498 TableStyle::Markdown => {
1499 table.load_preset(ASCII_MARKDOWN);
1500 }
1501 TableStyle::Utf8Full => {
1502 table.load_preset(UTF8_FULL);
1503 }
1504 TableStyle::Utf8Condensed => {
1505 table.load_preset(UTF8_FULL_CONDENSED);
1506 }
1507 TableStyle::Utf8BordersOnly => {
1508 table.load_preset(UTF8_BORDERS_ONLY);
1509 }
1510 TableStyle::Utf8HorizontalOnly => {
1511 table.load_preset(UTF8_HORIZONTAL_ONLY);
1512 }
1513 TableStyle::Utf8NoBorders => {
1514 table.load_preset(UTF8_NO_BORDERS);
1515 }
1516 TableStyle::Plain => {
1517 table.load_preset(NOTHING);
1518 }
1519 }
1520
1521 if max_col_width.is_some() {
1523 table.set_content_arrangement(ContentArrangement::Dynamic);
1524 }
1525
1526 let columns = dataview.column_names();
1528
1529 if styled {
1531 use crate::output::styled_table::{apply_styles_to_table, StyleConfig};
1532 use std::path::PathBuf;
1533
1534 let style_config = if let Some(file_path) = style_file {
1536 let path = PathBuf::from(file_path);
1537 StyleConfig::from_file(&path).ok()
1538 } else {
1539 StyleConfig::load_default()
1540 };
1541
1542 if let Some(config) = style_config {
1543 let rows: Vec<Vec<String>> = (0..dataview.row_count())
1545 .filter_map(|i| {
1546 dataview.get_row(i).map(|row| {
1547 row.values
1548 .iter()
1549 .map(|v| {
1550 let s = format_value(v);
1551 if let Some(max_width) = max_col_width {
1553 if s.len() > max_width {
1554 format!("{}...", &s[..max_width.saturating_sub(3)])
1555 } else {
1556 s
1557 }
1558 } else {
1559 s
1560 }
1561 })
1562 .collect()
1563 })
1564 })
1565 .collect();
1566
1567 if let Err(e) = apply_styles_to_table(&mut table, &columns, &rows, &config) {
1568 eprintln!("Warning: Failed to apply styles: {}", e);
1569 }
1570 }
1571 } else {
1572 table.set_header(&columns);
1574
1575 for row_idx in 0..dataview.row_count() {
1577 if let Some(row) = dataview.get_row(row_idx) {
1578 let row_strings: Vec<String> = row
1579 .values
1580 .iter()
1581 .map(|v| {
1582 let s = format_value(v);
1583 if let Some(max_width) = max_col_width {
1585 if s.len() > max_width {
1586 format!("{}...", &s[..max_width.saturating_sub(3)])
1587 } else {
1588 s
1589 }
1590 } else {
1591 s
1592 }
1593 })
1594 .collect();
1595 table.add_row(row_strings);
1596 }
1597 }
1598 }
1599
1600 writeln!(writer, "{}", table)?;
1602
1603 Ok(())
1604}
1605
1606fn format_value(value: &DataValue) -> String {
1608 match value {
1609 DataValue::Null => String::new(),
1610 DataValue::Integer(i) => i.to_string(),
1611 DataValue::Float(f) => f.to_string(),
1612 DataValue::String(s) => s.clone(),
1613 DataValue::InternedString(s) => s.to_string(),
1614 DataValue::Boolean(b) => b.to_string(),
1615 DataValue::DateTime(dt) => dt.to_string(),
1616 DataValue::Vector(v) => {
1617 let components: Vec<String> = v.iter().map(|f| f.to_string()).collect();
1618 format!("[{}]", components.join(","))
1619 }
1620 }
1621}
1622
1623fn value_to_json(value: &DataValue) -> serde_json::Value {
1625 match value {
1626 DataValue::Null => serde_json::Value::Null,
1627 DataValue::Integer(i) => serde_json::Value::Number((*i).into()),
1628 DataValue::Float(f) => {
1629 if let Some(n) = serde_json::Number::from_f64(*f) {
1630 serde_json::Value::Number(n)
1631 } else {
1632 serde_json::Value::Null
1633 }
1634 }
1635 DataValue::String(s) => serde_json::Value::String(s.clone()),
1636 DataValue::InternedString(s) => serde_json::Value::String(s.to_string()),
1637 DataValue::Boolean(b) => serde_json::Value::Bool(*b),
1638 DataValue::DateTime(dt) => serde_json::Value::String(dt.to_string()),
1639 DataValue::Vector(v) => serde_json::Value::Array(
1640 v.iter()
1641 .map(|f| {
1642 if let Some(n) = serde_json::Number::from_f64(*f) {
1643 serde_json::Value::Number(n)
1644 } else {
1645 serde_json::Value::Null
1646 }
1647 })
1648 .collect(),
1649 ),
1650 }
1651}
1652
1653fn escape_csv_field(field: &str, delimiter: char) -> String {
1655 if field.contains(delimiter)
1656 || field.contains('"')
1657 || field.contains('\n')
1658 || field.contains('\r')
1659 {
1660 format!("\"{}\"", field.replace('"', "\"\""))
1661 } else {
1662 field.to_string()
1663 }
1664}