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_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, pub delimiter_override: Option<u8>, }
224
225fn make_transformer_config(config: &NonInteractiveConfig) -> crate::query_plan::TransformerConfig {
227 crate::query_plan::TransformerConfig {
228 enable_pivot_expander: true, enable_expression_lifter: !config.no_expression_lifter,
230 enable_where_expansion: !config.no_where_expansion,
231 enable_group_by_expansion: !config.no_group_by_expansion,
232 enable_having_expansion: !config.no_having_expansion,
233 enable_order_by_expansion: !config.no_order_by_expansion,
234 enable_qualify_to_where: !config.no_qualify_to_where,
235 enable_ilike_to_like: true, enable_cte_hoister: !config.no_cte_hoister,
237 enable_in_lifter: !config.no_in_lifter,
238 }
239}
240
241pub fn execute_non_interactive(config: NonInteractiveConfig) -> Result<()> {
243 let start_time = Instant::now();
244
245 let data_file_to_use = if config.data_file.is_empty() {
250 let lines: Vec<&str> = config.query.lines().take(10).collect();
252 let mut hint_path: Option<String> = None;
253
254 for line in lines {
255 let trimmed = line.trim();
256 if trimmed.starts_with("-- #!") {
257 hint_path = Some(trimmed[5..].trim().to_string());
258 break;
259 }
260 }
261
262 if let Some(path) = hint_path {
263 debug!("Found data file hint: {}", path);
264 let resolved_path = if path.starts_with("../") {
266 std::path::Path::new(&path).to_path_buf()
268 } else if path.starts_with("data/") {
269 std::path::Path::new(&path).to_path_buf()
271 } else {
272 std::path::Path::new(&path).to_path_buf()
273 };
274
275 if resolved_path.exists() {
276 info!("Using data file from hint: {:?}", resolved_path);
277 resolved_path.to_string_lossy().to_string()
278 } else {
279 debug!("Data file hint path does not exist: {:?}", resolved_path);
280 String::new()
281 }
282 } else {
283 String::new()
284 }
285 } else {
286 config.data_file.clone()
287 };
288
289 let (data_table, _is_dual) = if data_file_to_use.is_empty() {
291 info!("No data file provided, using DUAL table");
292 (crate::data::datatable::DataTable::dual(), true)
293 } else {
294 info!("Loading data from: {}", data_file_to_use);
295 let table = load_data_file(&data_file_to_use, config.delimiter_override)?;
296 info!(
297 "Loaded {} rows with {} columns",
298 table.row_count(),
299 table.column_count()
300 );
301 (table, false)
302 };
303 let _table_name = data_table.name.clone();
304
305 use crate::execution::{ExecutionConfig, ExecutionContext, StatementExecutor};
307 let mut context = ExecutionContext::new(std::sync::Arc::new(data_table));
308
309 let dataview = DataView::new(context.source_table.clone());
311
312 info!("Executing query: {}", config.query);
314
315 if config.execution_plan {
317 println!("\n=== EXECUTION PLAN ===");
318 println!("Query: {}", config.query);
319 println!("\nExecution Steps:");
320 println!("1. PARSE - Parse SQL query");
321 println!("2. LOAD_DATA - Load data from {}", &config.data_file);
322 println!(
323 " • Loaded {} rows, {} columns",
324 dataview.row_count(),
325 dataview.column_count()
326 );
327 }
328
329 if config.show_work_units {
331 use crate::query_plan::{ExpressionLifter, QueryAnalyzer};
332 use crate::sql::recursive_parser::Parser;
333
334 let mut parser = Parser::new(&config.query);
335 match parser.parse() {
336 Ok(stmt) => {
337 let mut analyzer = QueryAnalyzer::new();
338 let mut lifter = ExpressionLifter::new();
339
340 let mut stmt_copy = stmt.clone();
342 let lifted = lifter.lift_expressions(&mut stmt_copy);
343
344 match analyzer.analyze(&stmt_copy, config.query.clone()) {
346 Ok(plan) => {
347 println!("\n{}", plan.explain());
348
349 if !lifted.is_empty() {
350 println!("\nLifted CTEs:");
351 for cte in &lifted {
352 println!(" - {}", cte.name);
353 }
354 }
355
356 return Ok(());
357 }
358 Err(e) => {
359 eprintln!("Error analyzing query: {}", e);
360 return Err(anyhow::anyhow!("Query analysis failed: {}", e));
361 }
362 }
363 }
364 Err(e) => {
365 eprintln!("Error parsing query: {}", e);
366 return Err(anyhow::anyhow!("Parse error: {}", e));
367 }
368 }
369 }
370
371 if config.query_plan {
373 use crate::sql::recursive_parser::Parser;
374 let mut parser = Parser::new(&config.query);
375 match parser.parse() {
376 Ok(statement) => {
377 println!("\n=== QUERY PLAN (AST) ===");
378 println!("{statement:#?}");
379 println!("=== END QUERY PLAN ===\n");
380 }
381 Err(e) => {
382 eprintln!("Failed to parse query for plan: {e}");
383 }
384 }
385 }
386
387 if config.rewrite_analysis {
389 use crate::sql::query_rewriter::{QueryRewriter, RewriteAnalysis};
390 use crate::sql::recursive_parser::Parser;
391 use serde_json::json;
392
393 let mut parser = Parser::new(&config.query);
394 match parser.parse() {
395 Ok(statement) => {
396 let mut rewriter = QueryRewriter::new();
397 let suggestions = rewriter.analyze(&statement);
398
399 let analysis = RewriteAnalysis::from_suggestions(suggestions);
400 println!("{}", serde_json::to_string_pretty(&analysis).unwrap());
401 return Ok(());
402 }
403 Err(e) => {
404 let output = json!({
405 "success": false,
406 "error": format!("{}", e),
407 "suggestions": [],
408 "can_auto_rewrite": false,
409 });
410 println!("{}", serde_json::to_string_pretty(&output).unwrap());
411 return Ok(());
412 }
413 }
414 }
415
416 if config.cte_info {
418 use crate::sql::recursive_parser::Parser;
419 use serde_json::json;
420
421 let mut parser = Parser::new(&config.query);
422 match parser.parse() {
423 Ok(statement) => {
424 let mut cte_info = Vec::new();
425
426 for (index, cte) in statement.ctes.iter().enumerate() {
428 let cte_json = json!({
429 "index": index,
430 "name": cte.name,
431 "columns": cte.column_list,
432 "dependencies": extract_cte_dependencies(cte),
435 });
436 cte_info.push(cte_json);
437 }
438
439 let output = json!({
440 "success": true,
441 "ctes": cte_info,
442 "total": statement.ctes.len(),
443 "has_final_select": !statement.columns.is_empty() || !statement.select_items.is_empty(),
444 });
445
446 println!("{}", serde_json::to_string_pretty(&output).unwrap());
447 return Ok(());
448 }
449 Err(e) => {
450 let output = json!({
451 "success": false,
452 "error": format!("{}", e),
453 "ctes": [],
454 "total": 0,
455 });
456 println!("{}", serde_json::to_string_pretty(&output).unwrap());
457 return Ok(());
458 }
459 }
460 }
461
462 let query_start = Instant::now();
463
464 let app_config = Config::load().unwrap_or_else(|e| {
466 debug!("Could not load config file: {}. Using defaults.", e);
467 Config::default()
468 });
469
470 crate::config::global::init_config(app_config.clone());
472
473 let exec_config = ExecutionConfig::from_cli_flags(
475 config.show_preprocessing,
476 config.show_transformations,
477 config.case_insensitive,
478 config.auto_hide_empty,
479 config.no_expression_lifter,
480 config.no_where_expansion,
481 config.no_group_by_expansion,
482 config.no_having_expansion,
483 config.no_order_by_expansion,
484 config.no_qualify_to_where,
485 config.no_cte_hoister,
486 config.no_in_lifter,
487 config.debug_trace,
488 );
489
490 let executor = StatementExecutor::with_config(exec_config);
492
493 let exec_start = Instant::now();
495 let result = {
496 use crate::sql::recursive_parser::Parser;
497
498 use crate::sql::template_expander::TemplateExpander;
500 let expander = TemplateExpander::new(&context.temp_tables);
501
502 let query_to_parse = match expander.parse_templates(&config.query) {
503 Ok(vars) => {
504 if vars.is_empty() {
505 config.query.clone()
506 } else {
507 match expander.expand(&config.query, &vars) {
508 Ok(expanded) => {
509 debug!(
510 "Template expansion in single query mode: {} vars expanded",
511 vars.len()
512 );
513 for var in &vars {
514 debug!(" {} -> resolved", var.placeholder);
515 }
516 expanded
517 }
518 Err(e) => {
519 return Err(anyhow::anyhow!(
520 "Template expansion failed: {}. Available tables: {}",
521 e,
522 context.temp_tables.list_tables().join(", ")
523 ));
524 }
525 }
526 }
527 }
528 Err(e) => {
529 return Err(anyhow::anyhow!("Template parsing failed: {}", e));
530 }
531 };
532
533 let mut parser = Parser::new(&query_to_parse);
534 match parser.parse() {
535 Ok(stmt) => {
536 match executor.execute(stmt, &mut context) {
542 Ok(exec_result) => {
543 Ok(
545 crate::services::query_execution_service::QueryExecutionResult {
546 dataview: exec_result.dataview,
547 stats: crate::services::query_execution_service::QueryStats {
548 row_count: exec_result.stats.row_count,
549 column_count: exec_result.stats.column_count,
550 execution_time: exec_start.elapsed(),
551 query_engine_time: exec_start.elapsed(),
552 },
553 hidden_columns: Vec::new(),
554 query: config.query.clone(),
555 execution_plan: None,
556 debug_trace: None,
557 },
558 )
559 }
560 Err(e) => Err(e),
561 }
562 }
563 Err(e) => {
564 Err(anyhow::anyhow!("Parse error: {}", e))
566 }
567 }
568 }?;
569 let exec_time = exec_start.elapsed();
570
571 let query_time = query_start.elapsed();
572 info!("Query executed in {:?}", query_time);
573 info!(
574 "Result: {} rows, {} columns",
575 result.dataview.row_count(),
576 result.dataview.column_count()
577 );
578
579 if config.execution_plan {
581 use crate::data::query_engine::QueryEngine;
583
584 let query_engine = QueryEngine::new();
585
586 match query_engine.execute_with_plan(
587 std::sync::Arc::new(dataview.source().clone()),
588 &config.query,
589 ) {
590 Ok((_view, plan)) => {
591 print!("{}", plan.format_tree());
593 }
594 Err(e) => {
595 eprintln!("Could not generate detailed execution plan: {}", e);
597 println!(
598 "3. QUERY_EXECUTION [{:.3}ms]",
599 exec_time.as_secs_f64() * 1000.0
600 );
601
602 use crate::sql::recursive_parser::Parser;
604 let mut parser = Parser::new(&config.query);
605 if let Ok(stmt) = parser.parse() {
606 if stmt.where_clause.is_some() {
607 println!(" • WHERE clause filtering applied");
608 println!(" • Rows after filter: {}", result.dataview.row_count());
609 }
610
611 if let Some(ref order_by) = stmt.order_by {
612 println!(" • ORDER BY: {} column(s)", order_by.len());
613 }
614
615 if let Some(ref group_by) = stmt.group_by {
616 println!(" • GROUP BY: {} column(s)", group_by.len());
617 }
618
619 if let Some(limit) = stmt.limit {
620 println!(" • LIMIT: {} rows", limit);
621 }
622
623 if stmt.distinct {
624 println!(" • DISTINCT applied");
625 }
626 }
627 }
628 }
629
630 println!("\nExecution Statistics:");
631 println!(
632 " Preparation: {:.3}ms",
633 (exec_start - start_time).as_secs_f64() * 1000.0
634 );
635 println!(
636 " Query time: {:.3}ms",
637 exec_time.as_secs_f64() * 1000.0
638 );
639 println!(
640 " Total time: {:.3}ms",
641 query_time.as_secs_f64() * 1000.0
642 );
643 println!(" Rows returned: {}", result.dataview.row_count());
644 println!(" Columns: {}", result.dataview.column_count());
645 println!("\n=== END EXECUTION PLAN ===");
646 println!();
647 }
648
649 let final_view = if let Some(limit) = config.limit {
651 let limited_table = limit_results(&result.dataview, limit)?;
652 DataView::new(std::sync::Arc::new(limited_table))
653 } else {
654 result.dataview
655 };
656
657 if let Some(ref trace_output) = result.debug_trace {
659 eprintln!("{}", trace_output);
660 }
661
662 let exec_time_ms = exec_time.as_secs_f64() * 1000.0;
664 let output_result = if let Some(ref path) = config.output_file {
665 let mut file = fs::File::create(path)
666 .with_context(|| format!("Failed to create output file: {path}"))?;
667 output_results(
668 &final_view,
669 config.output_format,
670 &mut file,
671 config.max_col_width,
672 config.col_sample_rows,
673 exec_time_ms,
674 config.table_style,
675 config.styled,
676 config.style_file.as_deref(),
677 )?;
678 info!("Results written to: {}", path);
679 Ok(())
680 } else {
681 output_results(
682 &final_view,
683 config.output_format,
684 &mut io::stdout(),
685 config.max_col_width,
686 config.col_sample_rows,
687 exec_time_ms,
688 config.table_style,
689 config.styled,
690 config.style_file.as_deref(),
691 )?;
692 Ok(())
693 };
694
695 let total_time = start_time.elapsed();
696 debug!("Total execution time: {:?}", total_time);
697
698 if config.output_file.is_none() {
700 eprintln!(
701 "\n# Query completed: {} rows in {:?}",
702 final_view.row_count(),
703 query_time
704 );
705 }
706
707 output_result
708}
709
710pub fn execute_script(config: NonInteractiveConfig) -> Result<()> {
712 let _start_time = Instant::now();
713
714 let parser = ScriptParser::new(&config.query);
716 let script_statements = parser.parse_script_statements();
717
718 if script_statements.is_empty() {
719 anyhow::bail!("No statements found in script");
720 }
721
722 info!("Found {} statements in script", script_statements.len());
723
724 let data_file = if !config.data_file.is_empty() {
726 config.data_file.clone()
728 } else if let Some(hint) = parser.data_file_hint() {
729 info!("Using data file from script hint: {}", hint);
731
732 if let Some(script_path) = config.script_file.as_ref() {
734 let script_dir = std::path::Path::new(script_path)
735 .parent()
736 .unwrap_or(std::path::Path::new("."));
737 let hint_path = std::path::Path::new(hint);
738
739 if hint_path.is_relative() {
740 script_dir.join(hint_path).to_string_lossy().to_string()
741 } else {
742 hint.to_string()
743 }
744 } else {
745 hint.to_string()
746 }
747 } else {
748 String::new()
749 };
750
751 let (data_table, _is_dual) = if data_file.is_empty() {
753 info!("No data file provided, using DUAL table");
755 (DataTable::dual(), true)
756 } else {
757 if !std::path::Path::new(&data_file).exists() {
759 anyhow::bail!(
760 "Data file not found: {}\n\
761 Please check the path is correct",
762 data_file
763 );
764 }
765
766 info!("Loading data from: {}", data_file);
767 let table = load_data_file(&data_file, config.delimiter_override)?;
768 info!(
769 "Loaded {} rows with {} columns",
770 table.row_count(),
771 table.column_count()
772 );
773 (table, false)
774 };
775
776 let mut script_result = ScriptResult::new();
778 let mut output = Vec::new();
779
780 use crate::execution::{ExecutionConfig, ExecutionContext, StatementExecutor};
782 let mut context = ExecutionContext::new(std::sync::Arc::new(data_table));
783
784 let exec_config = ExecutionConfig::from_cli_flags(
786 config.show_preprocessing,
787 config.show_transformations,
788 config.case_insensitive,
789 config.auto_hide_empty,
790 config.no_expression_lifter,
791 config.no_where_expansion,
792 config.no_group_by_expansion,
793 config.no_having_expansion,
794 config.no_order_by_expansion,
795 config.no_qualify_to_where,
796 config.no_cte_hoister,
797 config.no_in_lifter,
798 false, );
800
801 let executor = StatementExecutor::with_config(exec_config);
803
804 for (idx, script_stmt) in script_statements.iter().enumerate() {
806 let statement_num = idx + 1;
807 let stmt_start = Instant::now();
808
809 if script_stmt.is_exit() {
811 let exit_code = script_stmt.get_exit_code().unwrap_or(0);
812 info!("EXIT statement encountered (code: {})", exit_code);
813
814 if matches!(config.output_format, OutputFormat::Table) {
816 if idx > 0 {
817 output.push(String::new());
818 }
819 output.push(format!("-- Statement {} --", statement_num));
820 output.push(format!("Script execution stopped by EXIT {}", exit_code));
821 }
822
823 script_result.add_success(
825 statement_num,
826 format!("EXIT {}", exit_code),
827 0,
828 stmt_start.elapsed().as_secs_f64() * 1000.0,
829 );
830
831 break;
833 }
834
835 if script_stmt.should_skip() {
837 info!(
838 "Skipping statement {} due to [SKIP] directive",
839 statement_num
840 );
841
842 if matches!(config.output_format, OutputFormat::Table) {
844 if idx > 0 {
845 output.push(String::new());
846 }
847 output.push(format!("-- Statement {} [SKIPPED] --", statement_num));
848 }
849
850 script_result.add_success(
852 statement_num,
853 "[SKIPPED]".to_string(),
854 0,
855 stmt_start.elapsed().as_secs_f64() * 1000.0,
856 );
857
858 continue;
859 }
860
861 let statement = match script_stmt.get_query() {
863 Some(sql) => sql,
864 None => continue, };
866
867 if matches!(config.output_format, OutputFormat::Table) {
869 if idx > 0 {
870 output.push(String::new()); }
872 output.push(format!("-- Query {} --", statement_num));
873 }
874
875 use crate::sql::template_expander::TemplateExpander;
878 let expander = TemplateExpander::new(&context.temp_tables);
879
880 let expanded_statement = match expander.parse_templates(statement) {
881 Ok(vars) => {
882 if vars.is_empty() {
883 statement.to_string()
884 } else {
885 match expander.expand(statement, &vars) {
886 Ok(expanded) => {
887 debug!("Expanded templates in SQL: {} vars found", vars.len());
888 for var in &vars {
889 debug!(
890 " {} -> expanding from {}",
891 var.placeholder, var.table_name
892 );
893 }
894 expanded
895 }
896 Err(e) => {
897 script_result.add_failure(
898 statement_num,
899 statement.to_string(),
900 format!("Template expansion error: {}", e),
901 stmt_start.elapsed().as_secs_f64() * 1000.0,
902 );
903 continue; }
905 }
906 }
907 }
908 Err(e) => {
909 script_result.add_failure(
910 statement_num,
911 statement.to_string(),
912 format!("Template parse error: {}", e),
913 stmt_start.elapsed().as_secs_f64() * 1000.0,
914 );
915 continue; }
917 };
918
919 let statement = expanded_statement.as_str();
921
922 let mut parser = Parser::new(statement);
924 let parsed_stmt = match parser.parse() {
925 Ok(stmt) => stmt,
926 Err(e) => {
927 script_result.add_failure(
929 statement_num,
930 statement.to_string(),
931 format!("Parse error: {}", e),
932 stmt_start.elapsed().as_secs_f64() * 1000.0,
933 );
934 break;
935 }
936 };
937
938 if let Some(from_table) = &parsed_stmt.from_table {
941 if from_table.starts_with('#') && !context.has_temp_table(from_table) {
942 script_result.add_failure(
943 statement_num,
944 statement.to_string(),
945 format!("Temporary table {} not found", from_table),
946 stmt_start.elapsed().as_secs_f64() * 1000.0,
947 );
948 break;
949 }
950 }
951
952 let into_table = parsed_stmt.into_table.clone();
955 let stmt_without_into = if into_table.is_some() {
956 use crate::query_plan::IntoClauseRemover;
957 IntoClauseRemover::remove_into_clause(parsed_stmt)
958 } else {
959 parsed_stmt
960 };
961
962 let result = executor.execute(stmt_without_into, &mut context);
968
969 match result {
970 Ok(exec_result) => {
971 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
972 let final_view = exec_result.dataview;
973
974 if let Some(into_table) = &into_table {
976 let result_table = final_view.source_arc();
978 let row_count = result_table.row_count();
979
980 match context.store_temp_table(into_table.name.clone(), result_table) {
982 Ok(_) => {
983 info!(
984 "Stored {} rows in temporary table {}",
985 row_count, into_table.name
986 );
987
988 if matches!(config.output_format, OutputFormat::Table) {
991 let mut statement_output = Vec::new();
992 writeln!(
993 &mut statement_output,
994 "({} rows affected) -> {}",
995 row_count, into_table.name
996 )?;
997 output.extend(
998 String::from_utf8_lossy(&statement_output)
999 .lines()
1000 .map(String::from),
1001 );
1002 }
1003
1004 script_result.add_success(
1005 statement_num,
1006 statement.to_string(),
1007 row_count,
1008 exec_time,
1009 );
1010 continue; }
1012 Err(e) => {
1013 script_result.add_failure(
1014 statement_num,
1015 statement.to_string(),
1016 e.to_string(),
1017 exec_time,
1018 );
1019 break;
1020 }
1021 }
1022 }
1023
1024 let mut statement_output = Vec::new();
1026 match config.output_format {
1027 OutputFormat::Csv => {
1028 output_csv(&final_view, &mut statement_output, ',')?;
1029 }
1030 OutputFormat::Json => {
1031 output_json(&final_view, &mut statement_output)?;
1032 }
1033 OutputFormat::JsonStructured => {
1034 output_json_structured(&final_view, &mut statement_output, exec_time)?;
1035 }
1036 OutputFormat::Table => {
1037 output_table(
1038 &final_view,
1039 &mut statement_output,
1040 config.max_col_width,
1041 config.col_sample_rows,
1042 config.table_style,
1043 config.styled,
1044 config.style_file.as_deref(),
1045 )?;
1046 writeln!(
1047 &mut statement_output,
1048 "Query completed: {} rows in {:.2}ms",
1049 final_view.row_count(),
1050 exec_time
1051 )?;
1052 }
1053 OutputFormat::Tsv => {
1054 output_csv(&final_view, &mut statement_output, '\t')?;
1055 }
1056 }
1057
1058 output.extend(
1060 String::from_utf8_lossy(&statement_output)
1061 .lines()
1062 .map(String::from),
1063 );
1064
1065 script_result.add_success(
1066 statement_num,
1067 statement.to_string(),
1068 final_view.row_count(),
1069 exec_time,
1070 );
1071 }
1072 Err(e) => {
1073 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
1074 let error_msg = format!("Query {} failed: {}", statement_num, e);
1075
1076 if matches!(config.output_format, OutputFormat::Table) {
1077 output.push(error_msg.clone());
1078 }
1079
1080 script_result.add_failure(
1081 statement_num,
1082 statement.to_string(),
1083 e.to_string(),
1084 exec_time,
1085 );
1086
1087 }
1089 }
1090 }
1091
1092 if let Some(ref output_file) = config.output_file {
1094 let mut file = fs::File::create(output_file)?;
1095 for line in &output {
1096 writeln!(file, "{}", line)?;
1097 }
1098 info!("Results written to: {}", output_file);
1099 } else {
1100 for line in &output {
1101 println!("{}", line);
1102 }
1103 }
1104
1105 if matches!(config.output_format, OutputFormat::Table) {
1107 println!("\n=== Script Summary ===");
1108 println!("Total statements: {}", script_result.total_statements);
1109 println!("Successful: {}", script_result.successful_statements);
1110 println!("Failed: {}", script_result.failed_statements);
1111 println!(
1112 "Total execution time: {:.2}ms",
1113 script_result.total_execution_time_ms
1114 );
1115 }
1116
1117 if !script_result.all_successful() {
1118 return Err(anyhow::anyhow!(
1119 "{} of {} statements failed",
1120 script_result.failed_statements,
1121 script_result.total_statements
1122 ));
1123 }
1124
1125 Ok(())
1126}
1127
1128fn load_data_file(path: &str, delimiter_override: Option<u8>) -> Result<DataTable> {
1133 use crate::data::datatable_loaders::load_csv_to_datatable_with_opts;
1134 use crate::data::stream_loader::{resolve_delimiter, CsvReadOptions};
1135
1136 let path = Path::new(path);
1137
1138 if !path.exists() {
1139 return Err(anyhow::anyhow!("File not found: {}", path.display()));
1140 }
1141
1142 let extension = path
1144 .extension()
1145 .and_then(|ext| ext.to_str())
1146 .map(str::to_lowercase)
1147 .unwrap_or_default();
1148
1149 let table_name = path
1150 .file_stem()
1151 .and_then(|stem| stem.to_str())
1152 .unwrap_or("data")
1153 .to_string();
1154
1155 let is_csv_family =
1159 matches!(extension.as_str(), "csv" | "tsv" | "psv") || delimiter_override.is_some();
1160 if is_csv_family {
1161 let path_str = path.display().to_string();
1162 let opts = CsvReadOptions {
1163 delimiter: resolve_delimiter(&path_str, delimiter_override),
1164 has_headers: true,
1165 };
1166 return load_csv_to_datatable_with_opts(path, &table_name, &opts)
1167 .with_context(|| format!("Failed to load CSV-family file: {}", path.display()));
1168 }
1169
1170 match extension.as_str() {
1171 "json" | "jsonl" | "ndjson" => load_json_to_datatable(path, &table_name)
1172 .with_context(|| format!("Failed to load JSON file: {}", path.display())),
1173 _ => Err(anyhow::anyhow!(
1174 "Unsupported file type: {}. Use .csv, .tsv, .psv, .json, .jsonl, or .ndjson \
1175 (or pass --delimiter to force CSV parsing on an unknown extension)",
1176 extension
1177 )),
1178 }
1179}
1180
1181fn limit_results(dataview: &DataView, limit: usize) -> Result<DataTable> {
1183 let source = dataview.source();
1184 let mut limited_table = DataTable::new(&source.name);
1185
1186 for col in &source.columns {
1188 limited_table.add_column(col.clone());
1189 }
1190
1191 let rows_to_copy = dataview.row_count().min(limit);
1193 for i in 0..rows_to_copy {
1194 if let Some(row) = dataview.get_row(i) {
1195 let _ = limited_table.add_row(row.clone());
1196 }
1197 }
1198
1199 Ok(limited_table)
1200}
1201
1202fn output_results<W: Write>(
1204 dataview: &DataView,
1205 format: OutputFormat,
1206 writer: &mut W,
1207 max_col_width: Option<usize>,
1208 col_sample_rows: usize,
1209 exec_time_ms: f64,
1210 table_style: TableStyle,
1211 styled: bool,
1212 style_file: Option<&str>,
1213) -> Result<()> {
1214 match format {
1215 OutputFormat::Csv => output_csv(dataview, writer, ','),
1216 OutputFormat::Tsv => output_csv(dataview, writer, '\t'),
1217 OutputFormat::Json => output_json(dataview, writer),
1218 OutputFormat::JsonStructured => output_json_structured(dataview, writer, exec_time_ms),
1219 OutputFormat::Table => output_table(
1220 dataview,
1221 writer,
1222 max_col_width,
1223 col_sample_rows,
1224 table_style,
1225 styled,
1226 style_file,
1227 ),
1228 }
1229}
1230
1231fn output_csv<W: Write>(dataview: &DataView, writer: &mut W, delimiter: char) -> Result<()> {
1233 let columns = dataview.column_names();
1235 for (i, col) in columns.iter().enumerate() {
1236 if i > 0 {
1237 write!(writer, "{delimiter}")?;
1238 }
1239 write!(writer, "{}", escape_csv_field(col, delimiter))?;
1240 }
1241 writeln!(writer)?;
1242
1243 for row_idx in 0..dataview.row_count() {
1245 if let Some(row) = dataview.get_row(row_idx) {
1246 for (i, value) in row.values.iter().enumerate() {
1247 if i > 0 {
1248 write!(writer, "{delimiter}")?;
1249 }
1250 write!(
1251 writer,
1252 "{}",
1253 escape_csv_field(&format_value(value), delimiter)
1254 )?;
1255 }
1256 writeln!(writer)?;
1257 }
1258 }
1259
1260 Ok(())
1261}
1262
1263fn output_json<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
1265 let columns = dataview.column_names();
1266 let mut rows = Vec::new();
1267
1268 for row_idx in 0..dataview.row_count() {
1269 if let Some(row) = dataview.get_row(row_idx) {
1270 let mut json_row = serde_json::Map::new();
1271 for (col_idx, value) in row.values.iter().enumerate() {
1272 if col_idx < columns.len() {
1273 json_row.insert(columns[col_idx].clone(), value_to_json(value));
1274 }
1275 }
1276 rows.push(serde_json::Value::Object(json_row));
1277 }
1278 }
1279
1280 let json = serde_json::to_string_pretty(&rows)?;
1281 writeln!(writer, "{json}")?;
1282
1283 Ok(())
1284}
1285
1286fn output_json_structured<W: Write>(
1288 dataview: &DataView,
1289 writer: &mut W,
1290 exec_time: f64,
1291) -> Result<()> {
1292 let column_names = dataview.column_names();
1293 let data_table = dataview.source();
1294
1295 let mut columns = Vec::new();
1297 for (idx, name) in column_names.iter().enumerate() {
1298 let col_type = data_table
1299 .columns
1300 .get(idx)
1301 .map(|c| format!("{:?}", c.data_type))
1302 .unwrap_or_else(|| "UNKNOWN".to_string());
1303
1304 let mut max_width = name.len();
1306 for row_idx in 0..dataview.row_count() {
1307 if let Some(row) = dataview.get_row(row_idx) {
1308 if let Some(value) = row.values.get(idx) {
1309 let display_width = match value {
1310 DataValue::Null => 4, DataValue::Integer(i) => i.to_string().len(),
1312 DataValue::Float(f) => format!("{:.2}", f).len(),
1313 DataValue::String(s) => s.len(),
1314 DataValue::InternedString(s) => s.len(),
1315 DataValue::Boolean(b) => {
1316 if *b {
1317 4
1318 } else {
1319 5
1320 }
1321 } DataValue::DateTime(dt) => dt.len(),
1323 DataValue::Vector(v) => {
1324 let components: Vec<String> = v.iter().map(|f| f.to_string()).collect();
1325 format!("[{}]", components.join(",")).len()
1326 }
1327 };
1328 max_width = max_width.max(display_width);
1329 }
1330 }
1331 }
1332
1333 let alignment = match data_table.columns.get(idx).map(|c| &c.data_type) {
1334 Some(crate::data::datatable::DataType::Integer) => "right",
1335 Some(crate::data::datatable::DataType::Float) => "right",
1336 _ => "left",
1337 };
1338
1339 let col_meta = serde_json::json!({
1340 "name": name,
1341 "type": col_type,
1342 "max_width": max_width,
1343 "alignment": alignment
1344 });
1345 columns.push(col_meta);
1346 }
1347
1348 let mut rows = Vec::new();
1350 for row_idx in 0..dataview.row_count() {
1351 if let Some(row) = dataview.get_row(row_idx) {
1352 let row_values: Vec<String> = row
1353 .values
1354 .iter()
1355 .map(|v| match v {
1356 DataValue::Null => String::new(),
1357 DataValue::Integer(i) => i.to_string(),
1358 DataValue::Float(f) => format!("{:.2}", f),
1359 DataValue::String(s) => s.clone(),
1360 DataValue::InternedString(s) => s.to_string(),
1361 DataValue::Boolean(b) => b.to_string(),
1362 DataValue::DateTime(dt) => dt.clone(),
1363 DataValue::Vector(v) => {
1364 let components: Vec<String> = v.iter().map(|f| f.to_string()).collect();
1365 format!("[{}]", components.join(","))
1366 }
1367 })
1368 .collect();
1369 rows.push(serde_json::Value::Array(
1370 row_values
1371 .into_iter()
1372 .map(serde_json::Value::String)
1373 .collect(),
1374 ));
1375 }
1376 }
1377
1378 let output = serde_json::json!({
1380 "columns": columns,
1381 "rows": rows,
1382 "metadata": {
1383 "total_rows": dataview.row_count(),
1384 "query_time_ms": exec_time
1385 }
1386 });
1387
1388 let json = serde_json::to_string_pretty(&output)?;
1389 writeln!(writer, "{json}")?;
1390
1391 Ok(())
1392}
1393
1394fn output_table_old_style<W: Write>(
1396 dataview: &DataView,
1397 writer: &mut W,
1398 max_col_width: Option<usize>,
1399) -> Result<()> {
1400 let columns = dataview.column_names();
1401
1402 let mut widths = vec![0; columns.len()];
1404 for (i, col) in columns.iter().enumerate() {
1405 widths[i] = col.len();
1406 }
1407
1408 for row_idx in 0..dataview.row_count() {
1410 if let Some(row) = dataview.get_row(row_idx) {
1411 for (i, value) in row.values.iter().enumerate() {
1412 if i < widths.len() {
1413 let value_str = format_value(value);
1414 widths[i] = widths[i].max(display_width(&value_str));
1415 }
1416 }
1417 }
1418 }
1419
1420 if let Some(max_width) = max_col_width {
1422 for width in &mut widths {
1423 *width = (*width).min(max_width);
1424 }
1425 }
1426
1427 write!(writer, "+")?;
1429 for width in &widths {
1430 write!(writer, "{}", "-".repeat(*width + 2))?;
1431 write!(writer, "+")?;
1432 }
1433 writeln!(writer)?;
1434
1435 write!(writer, "|")?;
1437 for (i, col) in columns.iter().enumerate() {
1438 write!(writer, " {:^width$} |", col, width = widths[i])?;
1439 }
1440 writeln!(writer)?;
1441
1442 write!(writer, "+")?;
1444 for width in &widths {
1445 write!(writer, "{}", "-".repeat(*width + 2))?;
1446 write!(writer, "+")?;
1447 }
1448 writeln!(writer)?;
1449
1450 for row_idx in 0..dataview.row_count() {
1452 if let Some(row) = dataview.get_row(row_idx) {
1453 write!(writer, "|")?;
1454 for (i, value) in row.values.iter().enumerate() {
1455 if i < widths.len() {
1456 let value_str = format_value(value);
1457 let display_len = display_width(&value_str);
1458
1459 write!(writer, " {}", value_str)?;
1462 let padding_needed = if display_len < widths[i] {
1463 widths[i] - display_len
1464 } else {
1465 0
1466 };
1467 write!(writer, "{} |", " ".repeat(padding_needed))?;
1468 }
1469 }
1470 writeln!(writer)?;
1471 }
1472 }
1473
1474 write!(writer, "+")?;
1476 for width in &widths {
1477 write!(writer, "{}", "-".repeat(*width + 2))?;
1478 write!(writer, "+")?;
1479 }
1480 writeln!(writer)?;
1481
1482 Ok(())
1483}
1484
1485fn output_table<W: Write>(
1487 dataview: &DataView,
1488 writer: &mut W,
1489 max_col_width: Option<usize>,
1490 _col_sample_rows: usize, style: TableStyle,
1492 styled: bool,
1493 style_file: Option<&str>,
1494) -> Result<()> {
1495 let mut table = Table::new();
1496
1497 match style {
1499 TableStyle::Default => {
1500 return output_table_old_style(dataview, writer, max_col_width);
1503 }
1504 TableStyle::AsciiFull => {
1505 table.load_preset(ASCII_FULL);
1506 }
1507 TableStyle::AsciiCondensed => {
1508 table.load_preset(ASCII_FULL_CONDENSED);
1509 }
1510 TableStyle::AsciiBordersOnly => {
1511 table.load_preset(ASCII_BORDERS_ONLY);
1512 }
1513 TableStyle::AsciiHorizontalOnly => {
1514 table.load_preset(ASCII_HORIZONTAL_ONLY);
1515 }
1516 TableStyle::AsciiNoBorders => {
1517 table.load_preset(ASCII_NO_BORDERS);
1518 }
1519 TableStyle::Markdown => {
1520 table.load_preset(ASCII_MARKDOWN);
1521 }
1522 TableStyle::Utf8Full => {
1523 table.load_preset(UTF8_FULL);
1524 }
1525 TableStyle::Utf8Condensed => {
1526 table.load_preset(UTF8_FULL_CONDENSED);
1527 }
1528 TableStyle::Utf8BordersOnly => {
1529 table.load_preset(UTF8_BORDERS_ONLY);
1530 }
1531 TableStyle::Utf8HorizontalOnly => {
1532 table.load_preset(UTF8_HORIZONTAL_ONLY);
1533 }
1534 TableStyle::Utf8NoBorders => {
1535 table.load_preset(UTF8_NO_BORDERS);
1536 }
1537 TableStyle::Plain => {
1538 table.load_preset(NOTHING);
1539 }
1540 }
1541
1542 if max_col_width.is_some() {
1544 table.set_content_arrangement(ContentArrangement::Dynamic);
1545 }
1546
1547 let columns = dataview.column_names();
1549
1550 if styled {
1552 use crate::output::styled_table::{apply_styles_to_table, StyleConfig};
1553 use std::path::PathBuf;
1554
1555 let style_config = if let Some(file_path) = style_file {
1557 let path = PathBuf::from(file_path);
1558 StyleConfig::from_file(&path).ok()
1559 } else {
1560 StyleConfig::load_default()
1561 };
1562
1563 if let Some(config) = style_config {
1564 let rows: Vec<Vec<String>> = (0..dataview.row_count())
1566 .filter_map(|i| {
1567 dataview.get_row(i).map(|row| {
1568 row.values
1569 .iter()
1570 .map(|v| {
1571 let s = format_value(v);
1572 if let Some(max_width) = max_col_width {
1574 if s.len() > max_width {
1575 format!("{}...", &s[..max_width.saturating_sub(3)])
1576 } else {
1577 s
1578 }
1579 } else {
1580 s
1581 }
1582 })
1583 .collect()
1584 })
1585 })
1586 .collect();
1587
1588 if let Err(e) = apply_styles_to_table(&mut table, &columns, &rows, &config) {
1589 eprintln!("Warning: Failed to apply styles: {}", e);
1590 }
1591 }
1592 } else {
1593 table.set_header(&columns);
1595
1596 for row_idx in 0..dataview.row_count() {
1598 if let Some(row) = dataview.get_row(row_idx) {
1599 let row_strings: Vec<String> = row
1600 .values
1601 .iter()
1602 .map(|v| {
1603 let s = format_value(v);
1604 if let Some(max_width) = max_col_width {
1606 if s.len() > max_width {
1607 format!("{}...", &s[..max_width.saturating_sub(3)])
1608 } else {
1609 s
1610 }
1611 } else {
1612 s
1613 }
1614 })
1615 .collect();
1616 table.add_row(row_strings);
1617 }
1618 }
1619 }
1620
1621 writeln!(writer, "{}", table)?;
1623
1624 Ok(())
1625}
1626
1627fn format_value(value: &DataValue) -> String {
1629 match value {
1630 DataValue::Null => String::new(),
1631 DataValue::Integer(i) => i.to_string(),
1632 DataValue::Float(f) => f.to_string(),
1633 DataValue::String(s) => s.clone(),
1634 DataValue::InternedString(s) => s.to_string(),
1635 DataValue::Boolean(b) => b.to_string(),
1636 DataValue::DateTime(dt) => dt.to_string(),
1637 DataValue::Vector(v) => {
1638 let components: Vec<String> = v.iter().map(|f| f.to_string()).collect();
1639 format!("[{}]", components.join(","))
1640 }
1641 }
1642}
1643
1644fn value_to_json(value: &DataValue) -> serde_json::Value {
1646 match value {
1647 DataValue::Null => serde_json::Value::Null,
1648 DataValue::Integer(i) => serde_json::Value::Number((*i).into()),
1649 DataValue::Float(f) => {
1650 if let Some(n) = serde_json::Number::from_f64(*f) {
1651 serde_json::Value::Number(n)
1652 } else {
1653 serde_json::Value::Null
1654 }
1655 }
1656 DataValue::String(s) => serde_json::Value::String(s.clone()),
1657 DataValue::InternedString(s) => serde_json::Value::String(s.to_string()),
1658 DataValue::Boolean(b) => serde_json::Value::Bool(*b),
1659 DataValue::DateTime(dt) => serde_json::Value::String(dt.to_string()),
1660 DataValue::Vector(v) => serde_json::Value::Array(
1661 v.iter()
1662 .map(|f| {
1663 if let Some(n) = serde_json::Number::from_f64(*f) {
1664 serde_json::Value::Number(n)
1665 } else {
1666 serde_json::Value::Null
1667 }
1668 })
1669 .collect(),
1670 ),
1671 }
1672}
1673
1674fn escape_csv_field(field: &str, delimiter: char) -> String {
1676 if field.contains(delimiter)
1677 || field.contains('"')
1678 || field.contains('\n')
1679 || field.contains('\r')
1680 {
1681 format!("\"{}\"", field.replace('"', "\"\""))
1682 } else {
1683 field.to_string()
1684 }
1685}