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 let msg =
898 format!("Query {} template expansion error: {}", statement_num, e);
899 if matches!(config.output_format, OutputFormat::Table) {
900 output.push(msg.clone());
901 } else {
902 eprintln!("{}", msg);
903 }
904 script_result.add_failure(
905 statement_num,
906 statement.to_string(),
907 msg,
908 stmt_start.elapsed().as_secs_f64() * 1000.0,
909 );
910 continue; }
912 }
913 }
914 }
915 Err(e) => {
916 let msg = format!("Query {} template parse error: {}", statement_num, e);
917 if matches!(config.output_format, OutputFormat::Table) {
918 output.push(msg.clone());
919 } else {
920 eprintln!("{}", msg);
921 }
922 script_result.add_failure(
923 statement_num,
924 statement.to_string(),
925 msg,
926 stmt_start.elapsed().as_secs_f64() * 1000.0,
927 );
928 continue; }
930 };
931
932 let statement = expanded_statement.as_str();
934
935 let mut parser = Parser::new(statement);
937 let parsed_stmt = match parser.parse() {
938 Ok(stmt) => stmt,
939 Err(e) => {
940 let msg = format!("Query {} parse error: {}", statement_num, e);
942 if matches!(config.output_format, OutputFormat::Table) {
943 output.push(msg.clone());
944 } else {
945 eprintln!("{}", msg);
946 }
947 script_result.add_failure(
948 statement_num,
949 statement.to_string(),
950 msg,
951 stmt_start.elapsed().as_secs_f64() * 1000.0,
952 );
953 break;
954 }
955 };
956
957 if let Some(from_table) = &parsed_stmt.from_table {
960 if from_table.starts_with('#') && !context.has_temp_table(from_table) {
961 let msg = format!(
962 "Query {} failed: Temporary table {} not found",
963 statement_num, from_table
964 );
965 if matches!(config.output_format, OutputFormat::Table) {
966 output.push(msg.clone());
967 } else {
968 eprintln!("{}", msg);
969 }
970 script_result.add_failure(
971 statement_num,
972 statement.to_string(),
973 msg,
974 stmt_start.elapsed().as_secs_f64() * 1000.0,
975 );
976 break;
977 }
978 }
979
980 let into_table = parsed_stmt.into_table.clone();
983 let stmt_without_into = if into_table.is_some() {
984 use crate::query_plan::IntoClauseRemover;
985 IntoClauseRemover::remove_into_clause(parsed_stmt)
986 } else {
987 parsed_stmt
988 };
989
990 let result = executor.execute(stmt_without_into, &mut context);
996
997 match result {
998 Ok(exec_result) => {
999 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
1000 let final_view = exec_result.dataview;
1001
1002 if let Some(into_table) = &into_table {
1004 let result_table = final_view.source_arc();
1006 let row_count = result_table.row_count();
1007
1008 match context.store_temp_table(into_table.name.clone(), result_table) {
1010 Ok(_) => {
1011 info!(
1012 "Stored {} rows in temporary table {}",
1013 row_count, into_table.name
1014 );
1015
1016 if matches!(config.output_format, OutputFormat::Table) {
1019 let mut statement_output = Vec::new();
1020 writeln!(
1021 &mut statement_output,
1022 "({} rows affected) -> {}",
1023 row_count, into_table.name
1024 )?;
1025 output.extend(
1026 String::from_utf8_lossy(&statement_output)
1027 .lines()
1028 .map(String::from),
1029 );
1030 }
1031
1032 script_result.add_success(
1033 statement_num,
1034 statement.to_string(),
1035 row_count,
1036 exec_time,
1037 );
1038 continue; }
1040 Err(e) => {
1041 script_result.add_failure(
1042 statement_num,
1043 statement.to_string(),
1044 e.to_string(),
1045 exec_time,
1046 );
1047 break;
1048 }
1049 }
1050 }
1051
1052 let mut statement_output = Vec::new();
1054 match config.output_format {
1055 OutputFormat::Csv => {
1056 output_csv(&final_view, &mut statement_output, ',')?;
1057 }
1058 OutputFormat::Json => {
1059 output_json(&final_view, &mut statement_output)?;
1060 }
1061 OutputFormat::JsonStructured => {
1062 output_json_structured(&final_view, &mut statement_output, exec_time)?;
1063 }
1064 OutputFormat::Table => {
1065 output_table(
1066 &final_view,
1067 &mut statement_output,
1068 config.max_col_width,
1069 config.col_sample_rows,
1070 config.table_style,
1071 config.styled,
1072 config.style_file.as_deref(),
1073 )?;
1074 writeln!(
1075 &mut statement_output,
1076 "Query completed: {} rows in {:.2}ms",
1077 final_view.row_count(),
1078 exec_time
1079 )?;
1080 }
1081 OutputFormat::Tsv => {
1082 output_csv(&final_view, &mut statement_output, '\t')?;
1083 }
1084 }
1085
1086 output.extend(
1088 String::from_utf8_lossy(&statement_output)
1089 .lines()
1090 .map(String::from),
1091 );
1092
1093 script_result.add_success(
1094 statement_num,
1095 statement.to_string(),
1096 final_view.row_count(),
1097 exec_time,
1098 );
1099 }
1100 Err(e) => {
1101 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
1102 let error_msg = format!("Query {} failed: {:#}", statement_num, e);
1103
1104 if matches!(config.output_format, OutputFormat::Table) {
1108 output.push(error_msg.clone());
1109 } else {
1110 eprintln!("{}", error_msg);
1111 }
1112
1113 script_result.add_failure(
1114 statement_num,
1115 statement.to_string(),
1116 e.to_string(),
1117 exec_time,
1118 );
1119
1120 }
1122 }
1123 }
1124
1125 if let Some(ref output_file) = config.output_file {
1127 let mut file = fs::File::create(output_file)?;
1128 for line in &output {
1129 writeln!(file, "{}", line)?;
1130 }
1131 info!("Results written to: {}", output_file);
1132 } else {
1133 for line in &output {
1134 println!("{}", line);
1135 }
1136 }
1137
1138 if matches!(config.output_format, OutputFormat::Table) {
1140 println!("\n=== Script Summary ===");
1141 println!("Total statements: {}", script_result.total_statements);
1142 println!("Successful: {}", script_result.successful_statements);
1143 println!("Failed: {}", script_result.failed_statements);
1144 println!(
1145 "Total execution time: {:.2}ms",
1146 script_result.total_execution_time_ms
1147 );
1148 }
1149
1150 if !script_result.all_successful() {
1151 return Err(anyhow::anyhow!(
1152 "{} of {} statements failed",
1153 script_result.failed_statements,
1154 script_result.total_statements
1155 ));
1156 }
1157
1158 Ok(())
1159}
1160
1161fn load_data_file(path: &str, delimiter_override: Option<u8>) -> Result<DataTable> {
1166 use crate::data::datatable_loaders::load_csv_to_datatable_with_opts;
1167 use crate::data::stream_loader::{resolve_delimiter, CsvReadOptions};
1168
1169 let path = Path::new(path);
1170
1171 if !path.exists() {
1172 return Err(anyhow::anyhow!("File not found: {}", path.display()));
1173 }
1174
1175 let extension = path
1177 .extension()
1178 .and_then(|ext| ext.to_str())
1179 .map(str::to_lowercase)
1180 .unwrap_or_default();
1181
1182 let table_name = path
1183 .file_stem()
1184 .and_then(|stem| stem.to_str())
1185 .unwrap_or("data")
1186 .to_string();
1187
1188 let is_csv_family =
1192 matches!(extension.as_str(), "csv" | "tsv" | "psv") || delimiter_override.is_some();
1193 if is_csv_family {
1194 let path_str = path.display().to_string();
1195 let opts = CsvReadOptions {
1196 delimiter: resolve_delimiter(&path_str, delimiter_override),
1197 has_headers: true,
1198 };
1199 return load_csv_to_datatable_with_opts(path, &table_name, &opts)
1200 .with_context(|| format!("Failed to load CSV-family file: {}", path.display()));
1201 }
1202
1203 match extension.as_str() {
1204 "json" | "jsonl" | "ndjson" => load_json_to_datatable(path, &table_name)
1205 .with_context(|| format!("Failed to load JSON file: {}", path.display())),
1206 _ => Err(anyhow::anyhow!(
1207 "Unsupported file type: {}. Use .csv, .tsv, .psv, .json, .jsonl, or .ndjson \
1208 (or pass --delimiter to force CSV parsing on an unknown extension)",
1209 extension
1210 )),
1211 }
1212}
1213
1214fn limit_results(dataview: &DataView, limit: usize) -> Result<DataTable> {
1216 let source = dataview.source();
1217 let mut limited_table = DataTable::new(&source.name);
1218
1219 for col in &source.columns {
1221 limited_table.add_column(col.clone());
1222 }
1223
1224 let rows_to_copy = dataview.row_count().min(limit);
1226 for i in 0..rows_to_copy {
1227 if let Some(row) = dataview.get_row(i) {
1228 let _ = limited_table.add_row(row.clone());
1229 }
1230 }
1231
1232 Ok(limited_table)
1233}
1234
1235fn output_results<W: Write>(
1237 dataview: &DataView,
1238 format: OutputFormat,
1239 writer: &mut W,
1240 max_col_width: Option<usize>,
1241 col_sample_rows: usize,
1242 exec_time_ms: f64,
1243 table_style: TableStyle,
1244 styled: bool,
1245 style_file: Option<&str>,
1246) -> Result<()> {
1247 match format {
1248 OutputFormat::Csv => output_csv(dataview, writer, ','),
1249 OutputFormat::Tsv => output_csv(dataview, writer, '\t'),
1250 OutputFormat::Json => output_json(dataview, writer),
1251 OutputFormat::JsonStructured => output_json_structured(dataview, writer, exec_time_ms),
1252 OutputFormat::Table => output_table(
1253 dataview,
1254 writer,
1255 max_col_width,
1256 col_sample_rows,
1257 table_style,
1258 styled,
1259 style_file,
1260 ),
1261 }
1262}
1263
1264fn output_csv<W: Write>(dataview: &DataView, writer: &mut W, delimiter: char) -> Result<()> {
1266 let columns = dataview.column_names();
1268 for (i, col) in columns.iter().enumerate() {
1269 if i > 0 {
1270 write!(writer, "{delimiter}")?;
1271 }
1272 write!(writer, "{}", escape_csv_field(col, delimiter))?;
1273 }
1274 writeln!(writer)?;
1275
1276 for row_idx in 0..dataview.row_count() {
1278 if let Some(row) = dataview.get_row(row_idx) {
1279 for (i, value) in row.values.iter().enumerate() {
1280 if i > 0 {
1281 write!(writer, "{delimiter}")?;
1282 }
1283 write!(
1284 writer,
1285 "{}",
1286 escape_csv_field(&format_value(value), delimiter)
1287 )?;
1288 }
1289 writeln!(writer)?;
1290 }
1291 }
1292
1293 Ok(())
1294}
1295
1296fn output_json<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
1298 let columns = dataview.column_names();
1299 let mut rows = Vec::new();
1300
1301 for row_idx in 0..dataview.row_count() {
1302 if let Some(row) = dataview.get_row(row_idx) {
1303 let mut json_row = serde_json::Map::new();
1304 for (col_idx, value) in row.values.iter().enumerate() {
1305 if col_idx < columns.len() {
1306 json_row.insert(columns[col_idx].clone(), value_to_json(value));
1307 }
1308 }
1309 rows.push(serde_json::Value::Object(json_row));
1310 }
1311 }
1312
1313 let json = serde_json::to_string_pretty(&rows)?;
1314 writeln!(writer, "{json}")?;
1315
1316 Ok(())
1317}
1318
1319fn output_json_structured<W: Write>(
1321 dataview: &DataView,
1322 writer: &mut W,
1323 exec_time: f64,
1324) -> Result<()> {
1325 let column_names = dataview.column_names();
1326 let data_table = dataview.source();
1327
1328 let mut columns = Vec::new();
1330 for (idx, name) in column_names.iter().enumerate() {
1331 let col_type = data_table
1332 .columns
1333 .get(idx)
1334 .map(|c| format!("{:?}", c.data_type))
1335 .unwrap_or_else(|| "UNKNOWN".to_string());
1336
1337 let mut max_width = name.len();
1339 for row_idx in 0..dataview.row_count() {
1340 if let Some(row) = dataview.get_row(row_idx) {
1341 if let Some(value) = row.values.get(idx) {
1342 let display_width = match value {
1343 DataValue::Null => 4, DataValue::Integer(i) => i.to_string().len(),
1345 DataValue::Float(f) => format!("{:.2}", f).len(),
1346 DataValue::String(s) => s.len(),
1347 DataValue::InternedString(s) => s.len(),
1348 DataValue::Boolean(b) => {
1349 if *b {
1350 4
1351 } else {
1352 5
1353 }
1354 } DataValue::DateTime(dt) => dt.len(),
1356 DataValue::Vector(v) => {
1357 let components: Vec<String> = v.iter().map(|f| f.to_string()).collect();
1358 format!("[{}]", components.join(",")).len()
1359 }
1360 };
1361 max_width = max_width.max(display_width);
1362 }
1363 }
1364 }
1365
1366 let alignment = match data_table.columns.get(idx).map(|c| &c.data_type) {
1367 Some(crate::data::datatable::DataType::Integer) => "right",
1368 Some(crate::data::datatable::DataType::Float) => "right",
1369 _ => "left",
1370 };
1371
1372 let col_meta = serde_json::json!({
1373 "name": name,
1374 "type": col_type,
1375 "max_width": max_width,
1376 "alignment": alignment
1377 });
1378 columns.push(col_meta);
1379 }
1380
1381 let mut rows = Vec::new();
1383 for row_idx in 0..dataview.row_count() {
1384 if let Some(row) = dataview.get_row(row_idx) {
1385 let row_values: Vec<String> = row
1386 .values
1387 .iter()
1388 .map(|v| match v {
1389 DataValue::Null => String::new(),
1390 DataValue::Integer(i) => i.to_string(),
1391 DataValue::Float(f) => format!("{:.2}", f),
1392 DataValue::String(s) => s.clone(),
1393 DataValue::InternedString(s) => s.to_string(),
1394 DataValue::Boolean(b) => b.to_string(),
1395 DataValue::DateTime(dt) => dt.clone(),
1396 DataValue::Vector(v) => {
1397 let components: Vec<String> = v.iter().map(|f| f.to_string()).collect();
1398 format!("[{}]", components.join(","))
1399 }
1400 })
1401 .collect();
1402 rows.push(serde_json::Value::Array(
1403 row_values
1404 .into_iter()
1405 .map(serde_json::Value::String)
1406 .collect(),
1407 ));
1408 }
1409 }
1410
1411 let output = serde_json::json!({
1413 "columns": columns,
1414 "rows": rows,
1415 "metadata": {
1416 "total_rows": dataview.row_count(),
1417 "query_time_ms": exec_time
1418 }
1419 });
1420
1421 let json = serde_json::to_string_pretty(&output)?;
1422 writeln!(writer, "{json}")?;
1423
1424 Ok(())
1425}
1426
1427fn output_table_old_style<W: Write>(
1429 dataview: &DataView,
1430 writer: &mut W,
1431 max_col_width: Option<usize>,
1432) -> Result<()> {
1433 let columns = dataview.column_names();
1434
1435 let mut widths = vec![0; columns.len()];
1437 for (i, col) in columns.iter().enumerate() {
1438 widths[i] = col.len();
1439 }
1440
1441 for row_idx in 0..dataview.row_count() {
1443 if let Some(row) = dataview.get_row(row_idx) {
1444 for (i, value) in row.values.iter().enumerate() {
1445 if i < widths.len() {
1446 let value_str = format_value(value);
1447 widths[i] = widths[i].max(display_width(&value_str));
1448 }
1449 }
1450 }
1451 }
1452
1453 if let Some(max_width) = max_col_width {
1455 for width in &mut widths {
1456 *width = (*width).min(max_width);
1457 }
1458 }
1459
1460 write!(writer, "+")?;
1462 for width in &widths {
1463 write!(writer, "{}", "-".repeat(*width + 2))?;
1464 write!(writer, "+")?;
1465 }
1466 writeln!(writer)?;
1467
1468 write!(writer, "|")?;
1470 for (i, col) in columns.iter().enumerate() {
1471 write!(writer, " {:^width$} |", col, width = widths[i])?;
1472 }
1473 writeln!(writer)?;
1474
1475 write!(writer, "+")?;
1477 for width in &widths {
1478 write!(writer, "{}", "-".repeat(*width + 2))?;
1479 write!(writer, "+")?;
1480 }
1481 writeln!(writer)?;
1482
1483 for row_idx in 0..dataview.row_count() {
1485 if let Some(row) = dataview.get_row(row_idx) {
1486 write!(writer, "|")?;
1487 for (i, value) in row.values.iter().enumerate() {
1488 if i < widths.len() {
1489 let value_str = format_value(value);
1490 let display_len = display_width(&value_str);
1491
1492 write!(writer, " {}", value_str)?;
1495 let padding_needed = if display_len < widths[i] {
1496 widths[i] - display_len
1497 } else {
1498 0
1499 };
1500 write!(writer, "{} |", " ".repeat(padding_needed))?;
1501 }
1502 }
1503 writeln!(writer)?;
1504 }
1505 }
1506
1507 write!(writer, "+")?;
1509 for width in &widths {
1510 write!(writer, "{}", "-".repeat(*width + 2))?;
1511 write!(writer, "+")?;
1512 }
1513 writeln!(writer)?;
1514
1515 Ok(())
1516}
1517
1518fn output_table<W: Write>(
1520 dataview: &DataView,
1521 writer: &mut W,
1522 max_col_width: Option<usize>,
1523 _col_sample_rows: usize, style: TableStyle,
1525 styled: bool,
1526 style_file: Option<&str>,
1527) -> Result<()> {
1528 let mut table = Table::new();
1529
1530 match style {
1532 TableStyle::Default => {
1533 return output_table_old_style(dataview, writer, max_col_width);
1536 }
1537 TableStyle::AsciiFull => {
1538 table.load_preset(ASCII_FULL);
1539 }
1540 TableStyle::AsciiCondensed => {
1541 table.load_preset(ASCII_FULL_CONDENSED);
1542 }
1543 TableStyle::AsciiBordersOnly => {
1544 table.load_preset(ASCII_BORDERS_ONLY);
1545 }
1546 TableStyle::AsciiHorizontalOnly => {
1547 table.load_preset(ASCII_HORIZONTAL_ONLY);
1548 }
1549 TableStyle::AsciiNoBorders => {
1550 table.load_preset(ASCII_NO_BORDERS);
1551 }
1552 TableStyle::Markdown => {
1553 table.load_preset(ASCII_MARKDOWN);
1554 }
1555 TableStyle::Utf8Full => {
1556 table.load_preset(UTF8_FULL);
1557 }
1558 TableStyle::Utf8Condensed => {
1559 table.load_preset(UTF8_FULL_CONDENSED);
1560 }
1561 TableStyle::Utf8BordersOnly => {
1562 table.load_preset(UTF8_BORDERS_ONLY);
1563 }
1564 TableStyle::Utf8HorizontalOnly => {
1565 table.load_preset(UTF8_HORIZONTAL_ONLY);
1566 }
1567 TableStyle::Utf8NoBorders => {
1568 table.load_preset(UTF8_NO_BORDERS);
1569 }
1570 TableStyle::Plain => {
1571 table.load_preset(NOTHING);
1572 }
1573 }
1574
1575 if max_col_width.is_some() {
1577 table.set_content_arrangement(ContentArrangement::Dynamic);
1578 }
1579
1580 let columns = dataview.column_names();
1582
1583 if styled {
1585 use crate::output::styled_table::{apply_styles_to_table, StyleConfig};
1586 use std::path::PathBuf;
1587
1588 let style_config = if let Some(file_path) = style_file {
1590 let path = PathBuf::from(file_path);
1591 StyleConfig::from_file(&path).ok()
1592 } else {
1593 StyleConfig::load_default()
1594 };
1595
1596 if let Some(config) = style_config {
1597 let rows: Vec<Vec<String>> = (0..dataview.row_count())
1599 .filter_map(|i| {
1600 dataview.get_row(i).map(|row| {
1601 row.values
1602 .iter()
1603 .map(|v| {
1604 let s = format_value(v);
1605 if let Some(max_width) = max_col_width {
1607 if s.len() > max_width {
1608 format!("{}...", &s[..max_width.saturating_sub(3)])
1609 } else {
1610 s
1611 }
1612 } else {
1613 s
1614 }
1615 })
1616 .collect()
1617 })
1618 })
1619 .collect();
1620
1621 if let Err(e) = apply_styles_to_table(&mut table, &columns, &rows, &config) {
1622 eprintln!("Warning: Failed to apply styles: {}", e);
1623 }
1624 }
1625 } else {
1626 table.set_header(&columns);
1628
1629 for row_idx in 0..dataview.row_count() {
1631 if let Some(row) = dataview.get_row(row_idx) {
1632 let row_strings: Vec<String> = row
1633 .values
1634 .iter()
1635 .map(|v| {
1636 let s = format_value(v);
1637 if let Some(max_width) = max_col_width {
1639 if s.len() > max_width {
1640 format!("{}...", &s[..max_width.saturating_sub(3)])
1641 } else {
1642 s
1643 }
1644 } else {
1645 s
1646 }
1647 })
1648 .collect();
1649 table.add_row(row_strings);
1650 }
1651 }
1652 }
1653
1654 writeln!(writer, "{}", table)?;
1656
1657 Ok(())
1658}
1659
1660fn format_value(value: &DataValue) -> String {
1662 match value {
1663 DataValue::Null => String::new(),
1664 DataValue::Integer(i) => i.to_string(),
1665 DataValue::Float(f) => f.to_string(),
1666 DataValue::String(s) => s.clone(),
1667 DataValue::InternedString(s) => s.to_string(),
1668 DataValue::Boolean(b) => b.to_string(),
1669 DataValue::DateTime(dt) => dt.to_string(),
1670 DataValue::Vector(v) => {
1671 let components: Vec<String> = v.iter().map(|f| f.to_string()).collect();
1672 format!("[{}]", components.join(","))
1673 }
1674 }
1675}
1676
1677fn value_to_json(value: &DataValue) -> serde_json::Value {
1679 match value {
1680 DataValue::Null => serde_json::Value::Null,
1681 DataValue::Integer(i) => serde_json::Value::Number((*i).into()),
1682 DataValue::Float(f) => {
1683 if let Some(n) = serde_json::Number::from_f64(*f) {
1684 serde_json::Value::Number(n)
1685 } else {
1686 serde_json::Value::Null
1687 }
1688 }
1689 DataValue::String(s) => serde_json::Value::String(s.clone()),
1690 DataValue::InternedString(s) => serde_json::Value::String(s.to_string()),
1691 DataValue::Boolean(b) => serde_json::Value::Bool(*b),
1692 DataValue::DateTime(dt) => serde_json::Value::String(dt.to_string()),
1693 DataValue::Vector(v) => serde_json::Value::Array(
1694 v.iter()
1695 .map(|f| {
1696 if let Some(n) = serde_json::Number::from_f64(*f) {
1697 serde_json::Value::Number(n)
1698 } else {
1699 serde_json::Value::Null
1700 }
1701 })
1702 .collect(),
1703 ),
1704 }
1705}
1706
1707fn escape_csv_field(field: &str, delimiter: char) -> String {
1709 if field.contains(delimiter)
1710 || field.contains('"')
1711 || field.contains('\n')
1712 || field.contains('\r')
1713 {
1714 format!("\"{}\"", field.replace('"', "\"\""))
1715 } else {
1716 field.to_string()
1717 }
1718}