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