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