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