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