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, pub styled: bool, pub style_file: Option<String>, }
205
206pub fn execute_non_interactive(config: NonInteractiveConfig) -> Result<()> {
208 let start_time = Instant::now();
209
210 check_temp_table_usage(&config.query)?;
212
213 let (data_table, _is_dual) = if config.data_file.is_empty() {
215 info!("No data file provided, using DUAL table");
216 (crate::data::datatable::DataTable::dual(), true)
217 } else {
218 info!("Loading data from: {}", config.data_file);
219 let table = load_data_file(&config.data_file)?;
220 info!(
221 "Loaded {} rows with {} columns",
222 table.row_count(),
223 table.column_count()
224 );
225 (table, false)
226 };
227 let _table_name = data_table.name.clone();
228
229 let dataview = DataView::new(std::sync::Arc::new(data_table));
231
232 info!("Executing query: {}", config.query);
234
235 if config.execution_plan {
237 println!("\n=== EXECUTION PLAN ===");
238 println!("Query: {}", config.query);
239 println!("\nExecution Steps:");
240 println!("1. PARSE - Parse SQL query");
241 println!("2. LOAD_DATA - Load data from {}", &config.data_file);
242 println!(
243 " • Loaded {} rows, {} columns",
244 dataview.row_count(),
245 dataview.column_count()
246 );
247 }
248
249 if config.show_work_units {
251 use crate::query_plan::{ExpressionLifter, QueryAnalyzer};
252 use crate::sql::recursive_parser::Parser;
253
254 let mut parser = Parser::new(&config.query);
255 match parser.parse() {
256 Ok(stmt) => {
257 let mut analyzer = QueryAnalyzer::new();
258 let mut lifter = ExpressionLifter::new();
259
260 let mut stmt_copy = stmt.clone();
262 let lifted = lifter.lift_expressions(&mut stmt_copy);
263
264 match analyzer.analyze(&stmt_copy, config.query.clone()) {
266 Ok(plan) => {
267 println!("\n{}", plan.explain());
268
269 if !lifted.is_empty() {
270 println!("\nLifted CTEs:");
271 for cte in &lifted {
272 println!(" - {}", cte.name);
273 }
274 }
275
276 return Ok(());
277 }
278 Err(e) => {
279 eprintln!("Error analyzing query: {}", e);
280 return Err(anyhow::anyhow!("Query analysis failed: {}", e));
281 }
282 }
283 }
284 Err(e) => {
285 eprintln!("Error parsing query: {}", e);
286 return Err(anyhow::anyhow!("Parse error: {}", e));
287 }
288 }
289 }
290
291 if config.query_plan {
293 use crate::sql::recursive_parser::Parser;
294 let mut parser = Parser::new(&config.query);
295 match parser.parse() {
296 Ok(statement) => {
297 println!("\n=== QUERY PLAN (AST) ===");
298 println!("{statement:#?}");
299 println!("=== END QUERY PLAN ===\n");
300 }
301 Err(e) => {
302 eprintln!("Failed to parse query for plan: {e}");
303 }
304 }
305 }
306
307 if config.rewrite_analysis {
309 use crate::sql::query_rewriter::{QueryRewriter, RewriteAnalysis};
310 use crate::sql::recursive_parser::Parser;
311 use serde_json::json;
312
313 let mut parser = Parser::new(&config.query);
314 match parser.parse() {
315 Ok(statement) => {
316 let mut rewriter = QueryRewriter::new();
317 let suggestions = rewriter.analyze(&statement);
318
319 let analysis = RewriteAnalysis::from_suggestions(suggestions);
320 println!("{}", serde_json::to_string_pretty(&analysis).unwrap());
321 return Ok(());
322 }
323 Err(e) => {
324 let output = json!({
325 "success": false,
326 "error": format!("{}", e),
327 "suggestions": [],
328 "can_auto_rewrite": false,
329 });
330 println!("{}", serde_json::to_string_pretty(&output).unwrap());
331 return Ok(());
332 }
333 }
334 }
335
336 if config.cte_info {
338 use crate::sql::recursive_parser::Parser;
339 use serde_json::json;
340
341 let mut parser = Parser::new(&config.query);
342 match parser.parse() {
343 Ok(statement) => {
344 let mut cte_info = Vec::new();
345
346 for (index, cte) in statement.ctes.iter().enumerate() {
348 let cte_json = json!({
349 "index": index,
350 "name": cte.name,
351 "columns": cte.column_list,
352 "dependencies": extract_cte_dependencies(cte),
355 });
356 cte_info.push(cte_json);
357 }
358
359 let output = json!({
360 "success": true,
361 "ctes": cte_info,
362 "total": statement.ctes.len(),
363 "has_final_select": !statement.columns.is_empty() || !statement.select_items.is_empty(),
364 });
365
366 println!("{}", serde_json::to_string_pretty(&output).unwrap());
367 return Ok(());
368 }
369 Err(e) => {
370 let output = json!({
371 "success": false,
372 "error": format!("{}", e),
373 "ctes": [],
374 "total": 0,
375 });
376 println!("{}", serde_json::to_string_pretty(&output).unwrap());
377 return Ok(());
378 }
379 }
380 }
381
382 let query_start = Instant::now();
383
384 let app_config = Config::load().unwrap_or_else(|e| {
386 debug!("Could not load config file: {}. Using defaults.", e);
387 Config::default()
388 });
389
390 crate::config::global::init_config(app_config.clone());
392
393 let mut behavior_config = app_config.behavior.clone();
395 debug!(
396 "Using date notation: {}",
397 behavior_config.default_date_notation
398 );
399 if config.case_insensitive {
401 behavior_config.case_insensitive_default = true;
402 }
403 if config.auto_hide_empty {
404 behavior_config.hide_empty_columns = true;
405 }
406
407 let exec_start = Instant::now();
409 let result = if config.lift_in_expressions {
410 use crate::data::query_engine::QueryEngine;
411 use crate::query_plan::{CTEHoister, InOperatorLifter};
412 use crate::sql::recursive_parser::Parser;
413
414 let mut parser = Parser::new(&config.query);
415 match parser.parse() {
416 Ok(mut stmt) => {
417 use crate::query_plan::ExpressionLifter;
419 let mut expr_lifter = ExpressionLifter::new();
420 let lifted = expr_lifter.lift_expressions(&mut stmt);
421 if !lifted.is_empty() {
422 info!(
423 "Applied expression lifting - {} CTEs generated",
424 lifted.len()
425 );
426 }
427
428 stmt = CTEHoister::hoist_ctes(stmt);
430
431 let mut lifter = InOperatorLifter::new();
433 if lifter.rewrite_query(&mut stmt) {
434 info!("Applied IN expression lifting - query rewritten with CTEs");
435
436 let engine = QueryEngine::with_case_insensitive(config.case_insensitive);
438
439 match engine.execute_statement(dataview.source_arc(), stmt) {
440 Ok(result_view) => {
441 Ok(
443 crate::services::query_execution_service::QueryExecutionResult {
444 dataview: result_view,
445 stats: crate::services::query_execution_service::QueryStats {
446 row_count: 0, column_count: 0,
448 execution_time: exec_start.elapsed(),
449 query_engine_time: exec_start.elapsed(),
450 },
451 hidden_columns: Vec::new(),
452 query: config.query.clone(),
453 execution_plan: None,
454 debug_trace: None,
455 },
456 )
457 }
458 Err(e) => Err(e),
459 }
460 } else {
461 let query_service =
463 QueryExecutionService::with_behavior_config(behavior_config);
464 if config.debug_trace {
465 let debug_ctx = crate::debug_trace::DebugContext::new(
466 crate::debug_trace::DebugLevel::Debug,
467 );
468 query_service.execute_with_debug(
469 &config.query,
470 Some(&dataview),
471 Some(dataview.source()),
472 Some(debug_ctx),
473 )
474 } else {
475 query_service.execute(
476 &config.query,
477 Some(&dataview),
478 Some(dataview.source()),
479 )
480 }
481 }
482 }
483 Err(_) => {
484 let query_service = QueryExecutionService::with_behavior_config(behavior_config);
486 if config.debug_trace {
487 let debug_ctx = crate::debug_trace::DebugContext::new(
488 crate::debug_trace::DebugLevel::Debug,
489 );
490 query_service.execute_with_debug(
491 &config.query,
492 Some(&dataview),
493 Some(dataview.source()),
494 Some(debug_ctx),
495 )
496 } else {
497 query_service.execute(&config.query, Some(&dataview), Some(dataview.source()))
498 }
499 }
500 }
501 } else {
502 let query_service = QueryExecutionService::with_behavior_config(behavior_config);
504
505 if config.debug_trace {
507 let debug_ctx =
508 crate::debug_trace::DebugContext::new(crate::debug_trace::DebugLevel::Debug);
509 query_service.execute_with_debug(
510 &config.query,
511 Some(&dataview),
512 Some(dataview.source()),
513 Some(debug_ctx),
514 )
515 } else {
516 query_service.execute(&config.query, Some(&dataview), Some(dataview.source()))
517 }
518 }?;
519 let exec_time = exec_start.elapsed();
520
521 let query_time = query_start.elapsed();
522 info!("Query executed in {:?}", query_time);
523 info!(
524 "Result: {} rows, {} columns",
525 result.dataview.row_count(),
526 result.dataview.column_count()
527 );
528
529 if config.execution_plan {
531 use crate::data::query_engine::QueryEngine;
533
534 let query_engine = QueryEngine::new();
535
536 match query_engine.execute_with_plan(
537 std::sync::Arc::new(dataview.source().clone()),
538 &config.query,
539 ) {
540 Ok((_view, plan)) => {
541 print!("{}", plan.format_tree());
543 }
544 Err(e) => {
545 eprintln!("Could not generate detailed execution plan: {}", e);
547 println!(
548 "3. QUERY_EXECUTION [{:.3}ms]",
549 exec_time.as_secs_f64() * 1000.0
550 );
551
552 use crate::sql::recursive_parser::Parser;
554 let mut parser = Parser::new(&config.query);
555 if let Ok(stmt) = parser.parse() {
556 if stmt.where_clause.is_some() {
557 println!(" • WHERE clause filtering applied");
558 println!(" • Rows after filter: {}", result.dataview.row_count());
559 }
560
561 if let Some(ref order_by) = stmt.order_by {
562 println!(" • ORDER BY: {} column(s)", order_by.len());
563 }
564
565 if let Some(ref group_by) = stmt.group_by {
566 println!(" • GROUP BY: {} column(s)", group_by.len());
567 }
568
569 if let Some(limit) = stmt.limit {
570 println!(" • LIMIT: {} rows", limit);
571 }
572
573 if stmt.distinct {
574 println!(" • DISTINCT applied");
575 }
576 }
577 }
578 }
579
580 println!("\nExecution Statistics:");
581 println!(
582 " Preparation: {:.3}ms",
583 (exec_start - start_time).as_secs_f64() * 1000.0
584 );
585 println!(
586 " Query time: {:.3}ms",
587 exec_time.as_secs_f64() * 1000.0
588 );
589 println!(
590 " Total time: {:.3}ms",
591 query_time.as_secs_f64() * 1000.0
592 );
593 println!(" Rows returned: {}", result.dataview.row_count());
594 println!(" Columns: {}", result.dataview.column_count());
595 println!("\n=== END EXECUTION PLAN ===");
596 println!();
597 }
598
599 let final_view = if let Some(limit) = config.limit {
601 let limited_table = limit_results(&result.dataview, limit)?;
602 DataView::new(std::sync::Arc::new(limited_table))
603 } else {
604 result.dataview
605 };
606
607 if let Some(ref trace_output) = result.debug_trace {
609 eprintln!("{}", trace_output);
610 }
611
612 let exec_time_ms = exec_time.as_secs_f64() * 1000.0;
614 let output_result = if let Some(ref path) = config.output_file {
615 let mut file = fs::File::create(path)
616 .with_context(|| format!("Failed to create output file: {path}"))?;
617 output_results(
618 &final_view,
619 config.output_format,
620 &mut file,
621 config.max_col_width,
622 config.col_sample_rows,
623 exec_time_ms,
624 config.table_style,
625 config.styled,
626 config.style_file.as_deref(),
627 )?;
628 info!("Results written to: {}", path);
629 Ok(())
630 } else {
631 output_results(
632 &final_view,
633 config.output_format,
634 &mut io::stdout(),
635 config.max_col_width,
636 config.col_sample_rows,
637 exec_time_ms,
638 config.table_style,
639 config.styled,
640 config.style_file.as_deref(),
641 )?;
642 Ok(())
643 };
644
645 let total_time = start_time.elapsed();
646 debug!("Total execution time: {:?}", total_time);
647
648 if config.output_file.is_none() {
650 eprintln!(
651 "\n# Query completed: {} rows in {:?}",
652 final_view.row_count(),
653 query_time
654 );
655 }
656
657 output_result
658}
659
660pub fn execute_script(config: NonInteractiveConfig) -> Result<()> {
662 let _start_time = Instant::now();
663
664 let parser = ScriptParser::new(&config.query);
666 let script_statements = parser.parse_script_statements();
667
668 if script_statements.is_empty() {
669 anyhow::bail!("No statements found in script");
670 }
671
672 info!("Found {} statements in script", script_statements.len());
673
674 let data_file = if !config.data_file.is_empty() {
676 config.data_file.clone()
678 } else if let Some(hint) = parser.data_file_hint() {
679 info!("Using data file from script hint: {}", hint);
681
682 if let Some(script_path) = config.script_file.as_ref() {
684 let script_dir = std::path::Path::new(script_path)
685 .parent()
686 .unwrap_or(std::path::Path::new("."));
687 let hint_path = std::path::Path::new(hint);
688
689 if hint_path.is_relative() {
690 script_dir.join(hint_path).to_string_lossy().to_string()
691 } else {
692 hint.to_string()
693 }
694 } else {
695 hint.to_string()
696 }
697 } else {
698 String::new()
699 };
700
701 let (data_table, _is_dual) = if data_file.is_empty() {
703 info!("No data file provided, using DUAL table");
705 (DataTable::dual(), true)
706 } else {
707 if !std::path::Path::new(&data_file).exists() {
709 anyhow::bail!(
710 "Data file not found: {}\n\
711 Please check the path is correct",
712 data_file
713 );
714 }
715
716 info!("Loading data from: {}", data_file);
717 let table = load_data_file(&data_file)?;
718 info!(
719 "Loaded {} rows with {} columns",
720 table.row_count(),
721 table.column_count()
722 );
723 (table, false)
724 };
725
726 let mut script_result = ScriptResult::new();
728 let mut output = Vec::new();
729
730 let arc_data_table = std::sync::Arc::new(data_table);
732
733 let mut temp_tables = TempTableRegistry::new();
735
736 for (idx, script_stmt) in script_statements.iter().enumerate() {
738 let statement_num = idx + 1;
739 let stmt_start = Instant::now();
740
741 if script_stmt.is_exit() {
743 let exit_code = script_stmt.get_exit_code().unwrap_or(0);
744 info!("EXIT statement encountered (code: {})", exit_code);
745
746 if matches!(config.output_format, OutputFormat::Table) {
748 if idx > 0 {
749 output.push(String::new());
750 }
751 output.push(format!("-- Statement {} --", statement_num));
752 output.push(format!("Script execution stopped by EXIT {}", exit_code));
753 }
754
755 script_result.add_success(
757 statement_num,
758 format!("EXIT {}", exit_code),
759 0,
760 stmt_start.elapsed().as_secs_f64() * 1000.0,
761 );
762
763 break;
765 }
766
767 if script_stmt.should_skip() {
769 info!(
770 "Skipping statement {} due to [SKIP] directive",
771 statement_num
772 );
773
774 if matches!(config.output_format, OutputFormat::Table) {
776 if idx > 0 {
777 output.push(String::new());
778 }
779 output.push(format!("-- Statement {} [SKIPPED] --", statement_num));
780 }
781
782 script_result.add_success(
784 statement_num,
785 "[SKIPPED]".to_string(),
786 0,
787 stmt_start.elapsed().as_secs_f64() * 1000.0,
788 );
789
790 continue;
791 }
792
793 let statement = match script_stmt.get_query() {
795 Some(sql) => sql,
796 None => continue, };
798
799 if matches!(config.output_format, OutputFormat::Table) {
801 if idx > 0 {
802 output.push(String::new()); }
804 output.push(format!("-- Query {} --", statement_num));
805 }
806
807 use crate::sql::template_expander::TemplateExpander;
810 let expander = TemplateExpander::new(&temp_tables);
811
812 let expanded_statement = match expander.parse_templates(statement) {
813 Ok(vars) => {
814 if vars.is_empty() {
815 statement.to_string()
816 } else {
817 match expander.expand(statement, &vars) {
818 Ok(expanded) => {
819 debug!("Expanded templates in SQL: {} vars found", vars.len());
820 for var in &vars {
821 debug!(
822 " {} -> expanding from {}",
823 var.placeholder, var.table_name
824 );
825 }
826 expanded
827 }
828 Err(e) => {
829 script_result.add_failure(
830 statement_num,
831 statement.to_string(),
832 format!("Template expansion error: {}", e),
833 stmt_start.elapsed().as_secs_f64() * 1000.0,
834 );
835 continue; }
837 }
838 }
839 }
840 Err(e) => {
841 script_result.add_failure(
842 statement_num,
843 statement.to_string(),
844 format!("Template parse error: {}", e),
845 stmt_start.elapsed().as_secs_f64() * 1000.0,
846 );
847 continue; }
849 };
850
851 let statement = expanded_statement.as_str();
853
854 let mut parser = Parser::new(statement);
856 let parsed_stmt = match parser.parse() {
857 Ok(stmt) => stmt,
858 Err(e) => {
859 script_result.add_failure(
861 statement_num,
862 statement.to_string(),
863 format!("Parse error: {}", e),
864 stmt_start.elapsed().as_secs_f64() * 1000.0,
865 );
866 break;
867 }
868 };
869
870 let source_table = if let Some(from_table) = &parsed_stmt.from_table {
872 if from_table.starts_with('#') {
873 match temp_tables.get(from_table) {
875 Some(temp_table) => temp_table,
876 None => {
877 script_result.add_failure(
878 statement_num,
879 statement.to_string(),
880 format!("Temporary table {} not found", from_table),
881 stmt_start.elapsed().as_secs_f64() * 1000.0,
882 );
883 break;
884 }
885 }
886 } else {
887 arc_data_table.clone()
889 }
890 } else {
891 arc_data_table.clone()
893 };
894
895 let dataview = DataView::new(source_table);
897
898 let executable_sql = if parsed_stmt.into_table.is_some() {
901 use crate::query_plan::IntoClauseRemover;
903 use crate::sql::parser::ast_formatter;
904
905 let cleaned_stmt = IntoClauseRemover::remove_into_clause(parsed_stmt.clone());
906 ast_formatter::format_select_statement(&cleaned_stmt)
907 } else {
908 statement.to_string()
909 };
910
911 let service = QueryExecutionService::new(config.case_insensitive, config.auto_hide_empty);
913 match service.execute_with_temp_tables(
914 &executable_sql,
915 Some(&dataview),
916 None,
917 Some(&temp_tables),
918 ) {
919 Ok(result) => {
920 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
921 let final_view = result.dataview;
922
923 if let Some(into_table) = &parsed_stmt.into_table {
925 let result_table = final_view.source_arc();
927 let row_count = result_table.row_count();
928
929 match temp_tables.insert(into_table.name.clone(), result_table) {
930 Ok(_) => {
931 info!(
932 "Stored {} rows in temporary table {}",
933 row_count, into_table.name
934 );
935
936 if matches!(config.output_format, OutputFormat::Table) {
939 let mut statement_output = Vec::new();
940 writeln!(
941 &mut statement_output,
942 "({} rows affected) -> {}",
943 row_count, into_table.name
944 )?;
945 output.extend(
946 String::from_utf8_lossy(&statement_output)
947 .lines()
948 .map(String::from),
949 );
950 }
951
952 script_result.add_success(
953 statement_num,
954 statement.to_string(),
955 row_count,
956 exec_time,
957 );
958 continue; }
960 Err(e) => {
961 script_result.add_failure(
962 statement_num,
963 statement.to_string(),
964 e.to_string(),
965 exec_time,
966 );
967 break;
968 }
969 }
970 }
971
972 let mut statement_output = Vec::new();
974 match config.output_format {
975 OutputFormat::Csv => {
976 output_csv(&final_view, &mut statement_output, ',')?;
977 }
978 OutputFormat::Json => {
979 output_json(&final_view, &mut statement_output)?;
980 }
981 OutputFormat::JsonStructured => {
982 output_json_structured(&final_view, &mut statement_output, exec_time)?;
983 }
984 OutputFormat::Table => {
985 output_table(
986 &final_view,
987 &mut statement_output,
988 config.max_col_width,
989 config.col_sample_rows,
990 config.table_style,
991 config.styled,
992 config.style_file.as_deref(),
993 )?;
994 writeln!(
995 &mut statement_output,
996 "Query completed: {} rows in {:.2}ms",
997 final_view.row_count(),
998 exec_time
999 )?;
1000 }
1001 OutputFormat::Tsv => {
1002 output_csv(&final_view, &mut statement_output, '\t')?;
1003 }
1004 }
1005
1006 output.extend(
1008 String::from_utf8_lossy(&statement_output)
1009 .lines()
1010 .map(String::from),
1011 );
1012
1013 script_result.add_success(
1014 statement_num,
1015 statement.to_string(),
1016 final_view.row_count(),
1017 exec_time,
1018 );
1019 }
1020 Err(e) => {
1021 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
1022 let error_msg = format!("Query {} failed: {}", statement_num, e);
1023
1024 if matches!(config.output_format, OutputFormat::Table) {
1025 output.push(error_msg.clone());
1026 }
1027
1028 script_result.add_failure(
1029 statement_num,
1030 statement.to_string(),
1031 e.to_string(),
1032 exec_time,
1033 );
1034
1035 }
1037 }
1038 }
1039
1040 if let Some(ref output_file) = config.output_file {
1042 let mut file = fs::File::create(output_file)?;
1043 for line in &output {
1044 writeln!(file, "{}", line)?;
1045 }
1046 info!("Results written to: {}", output_file);
1047 } else {
1048 for line in &output {
1049 println!("{}", line);
1050 }
1051 }
1052
1053 if matches!(config.output_format, OutputFormat::Table) {
1055 println!("\n=== Script Summary ===");
1056 println!("Total statements: {}", script_result.total_statements);
1057 println!("Successful: {}", script_result.successful_statements);
1058 println!("Failed: {}", script_result.failed_statements);
1059 println!(
1060 "Total execution time: {:.2}ms",
1061 script_result.total_execution_time_ms
1062 );
1063 }
1064
1065 if !script_result.all_successful() {
1066 return Err(anyhow::anyhow!(
1067 "{} of {} statements failed",
1068 script_result.failed_statements,
1069 script_result.total_statements
1070 ));
1071 }
1072
1073 Ok(())
1074}
1075
1076fn load_data_file(path: &str) -> Result<DataTable> {
1078 let path = Path::new(path);
1079
1080 if !path.exists() {
1081 return Err(anyhow::anyhow!("File not found: {}", path.display()));
1082 }
1083
1084 let extension = path
1086 .extension()
1087 .and_then(|ext| ext.to_str())
1088 .map(str::to_lowercase)
1089 .unwrap_or_default();
1090
1091 let table_name = path
1092 .file_stem()
1093 .and_then(|stem| stem.to_str())
1094 .unwrap_or("data")
1095 .to_string();
1096
1097 match extension.as_str() {
1098 "csv" => load_csv_to_datatable(path, &table_name)
1099 .with_context(|| format!("Failed to load CSV file: {}", path.display())),
1100 "json" => load_json_to_datatable(path, &table_name)
1101 .with_context(|| format!("Failed to load JSON file: {}", path.display())),
1102 _ => Err(anyhow::anyhow!(
1103 "Unsupported file type: {}. Use .csv or .json",
1104 extension
1105 )),
1106 }
1107}
1108
1109fn limit_results(dataview: &DataView, limit: usize) -> Result<DataTable> {
1111 let source = dataview.source();
1112 let mut limited_table = DataTable::new(&source.name);
1113
1114 for col in &source.columns {
1116 limited_table.add_column(col.clone());
1117 }
1118
1119 let rows_to_copy = dataview.row_count().min(limit);
1121 for i in 0..rows_to_copy {
1122 if let Some(row) = dataview.get_row(i) {
1123 let _ = limited_table.add_row(row.clone());
1124 }
1125 }
1126
1127 Ok(limited_table)
1128}
1129
1130fn output_results<W: Write>(
1132 dataview: &DataView,
1133 format: OutputFormat,
1134 writer: &mut W,
1135 max_col_width: Option<usize>,
1136 col_sample_rows: usize,
1137 exec_time_ms: f64,
1138 table_style: TableStyle,
1139 styled: bool,
1140 style_file: Option<&str>,
1141) -> Result<()> {
1142 match format {
1143 OutputFormat::Csv => output_csv(dataview, writer, ','),
1144 OutputFormat::Tsv => output_csv(dataview, writer, '\t'),
1145 OutputFormat::Json => output_json(dataview, writer),
1146 OutputFormat::JsonStructured => output_json_structured(dataview, writer, exec_time_ms),
1147 OutputFormat::Table => output_table(
1148 dataview,
1149 writer,
1150 max_col_width,
1151 col_sample_rows,
1152 table_style,
1153 styled,
1154 style_file,
1155 ),
1156 }
1157}
1158
1159fn output_csv<W: Write>(dataview: &DataView, writer: &mut W, delimiter: char) -> Result<()> {
1161 let columns = dataview.column_names();
1163 for (i, col) in columns.iter().enumerate() {
1164 if i > 0 {
1165 write!(writer, "{delimiter}")?;
1166 }
1167 write!(writer, "{}", escape_csv_field(col, delimiter))?;
1168 }
1169 writeln!(writer)?;
1170
1171 for row_idx in 0..dataview.row_count() {
1173 if let Some(row) = dataview.get_row(row_idx) {
1174 for (i, value) in row.values.iter().enumerate() {
1175 if i > 0 {
1176 write!(writer, "{delimiter}")?;
1177 }
1178 write!(
1179 writer,
1180 "{}",
1181 escape_csv_field(&format_value(value), delimiter)
1182 )?;
1183 }
1184 writeln!(writer)?;
1185 }
1186 }
1187
1188 Ok(())
1189}
1190
1191fn output_json<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
1193 let columns = dataview.column_names();
1194 let mut rows = Vec::new();
1195
1196 for row_idx in 0..dataview.row_count() {
1197 if let Some(row) = dataview.get_row(row_idx) {
1198 let mut json_row = serde_json::Map::new();
1199 for (col_idx, value) in row.values.iter().enumerate() {
1200 if col_idx < columns.len() {
1201 json_row.insert(columns[col_idx].clone(), value_to_json(value));
1202 }
1203 }
1204 rows.push(serde_json::Value::Object(json_row));
1205 }
1206 }
1207
1208 let json = serde_json::to_string_pretty(&rows)?;
1209 writeln!(writer, "{json}")?;
1210
1211 Ok(())
1212}
1213
1214fn output_json_structured<W: Write>(
1216 dataview: &DataView,
1217 writer: &mut W,
1218 exec_time: f64,
1219) -> Result<()> {
1220 let column_names = dataview.column_names();
1221 let data_table = dataview.source();
1222
1223 let mut columns = Vec::new();
1225 for (idx, name) in column_names.iter().enumerate() {
1226 let col_type = data_table
1227 .columns
1228 .get(idx)
1229 .map(|c| format!("{:?}", c.data_type))
1230 .unwrap_or_else(|| "UNKNOWN".to_string());
1231
1232 let mut max_width = name.len();
1234 for row_idx in 0..dataview.row_count() {
1235 if let Some(row) = dataview.get_row(row_idx) {
1236 if let Some(value) = row.values.get(idx) {
1237 let display_width = match value {
1238 DataValue::Null => 4, DataValue::Integer(i) => i.to_string().len(),
1240 DataValue::Float(f) => format!("{:.2}", f).len(),
1241 DataValue::String(s) => s.len(),
1242 DataValue::InternedString(s) => s.len(),
1243 DataValue::Boolean(b) => {
1244 if *b {
1245 4
1246 } else {
1247 5
1248 }
1249 } DataValue::DateTime(dt) => dt.len(),
1251 };
1252 max_width = max_width.max(display_width);
1253 }
1254 }
1255 }
1256
1257 let alignment = match data_table.columns.get(idx).map(|c| &c.data_type) {
1258 Some(crate::data::datatable::DataType::Integer) => "right",
1259 Some(crate::data::datatable::DataType::Float) => "right",
1260 _ => "left",
1261 };
1262
1263 let col_meta = serde_json::json!({
1264 "name": name,
1265 "type": col_type,
1266 "max_width": max_width,
1267 "alignment": alignment
1268 });
1269 columns.push(col_meta);
1270 }
1271
1272 let mut rows = Vec::new();
1274 for row_idx in 0..dataview.row_count() {
1275 if let Some(row) = dataview.get_row(row_idx) {
1276 let row_values: Vec<String> = row
1277 .values
1278 .iter()
1279 .map(|v| match v {
1280 DataValue::Null => String::new(),
1281 DataValue::Integer(i) => i.to_string(),
1282 DataValue::Float(f) => format!("{:.2}", f),
1283 DataValue::String(s) => s.clone(),
1284 DataValue::InternedString(s) => s.to_string(),
1285 DataValue::Boolean(b) => b.to_string(),
1286 DataValue::DateTime(dt) => dt.clone(),
1287 })
1288 .collect();
1289 rows.push(serde_json::Value::Array(
1290 row_values
1291 .into_iter()
1292 .map(serde_json::Value::String)
1293 .collect(),
1294 ));
1295 }
1296 }
1297
1298 let output = serde_json::json!({
1300 "columns": columns,
1301 "rows": rows,
1302 "metadata": {
1303 "total_rows": dataview.row_count(),
1304 "query_time_ms": exec_time
1305 }
1306 });
1307
1308 let json = serde_json::to_string_pretty(&output)?;
1309 writeln!(writer, "{json}")?;
1310
1311 Ok(())
1312}
1313
1314fn output_table_old_style<W: Write>(
1316 dataview: &DataView,
1317 writer: &mut W,
1318 max_col_width: Option<usize>,
1319) -> Result<()> {
1320 let columns = dataview.column_names();
1321
1322 let mut widths = vec![0; columns.len()];
1324 for (i, col) in columns.iter().enumerate() {
1325 widths[i] = col.len();
1326 }
1327
1328 for row_idx in 0..dataview.row_count() {
1330 if let Some(row) = dataview.get_row(row_idx) {
1331 for (i, value) in row.values.iter().enumerate() {
1332 if i < widths.len() {
1333 let value_str = format_value(value);
1334 widths[i] = widths[i].max(value_str.len());
1335 }
1336 }
1337 }
1338 }
1339
1340 if let Some(max_width) = max_col_width {
1342 for width in &mut widths {
1343 *width = (*width).min(max_width);
1344 }
1345 }
1346
1347 write!(writer, "+")?;
1349 for width in &widths {
1350 write!(writer, "{}", "-".repeat(*width + 2))?;
1351 write!(writer, "+")?;
1352 }
1353 writeln!(writer)?;
1354
1355 write!(writer, "|")?;
1357 for (i, col) in columns.iter().enumerate() {
1358 write!(writer, " {:^width$} |", col, width = widths[i])?;
1359 }
1360 writeln!(writer)?;
1361
1362 write!(writer, "+")?;
1364 for width in &widths {
1365 write!(writer, "{}", "-".repeat(*width + 2))?;
1366 write!(writer, "+")?;
1367 }
1368 writeln!(writer)?;
1369
1370 for row_idx in 0..dataview.row_count() {
1372 if let Some(row) = dataview.get_row(row_idx) {
1373 write!(writer, "|")?;
1374 for (i, value) in row.values.iter().enumerate() {
1375 if i < widths.len() {
1376 let value_str = format_value(value);
1377 let truncated = if value_str.len() > widths[i] {
1378 format!("{}...", &value_str[..widths[i].saturating_sub(3)])
1379 } else {
1380 value_str
1381 };
1382 write!(writer, " {:<width$} |", truncated, width = widths[i])?;
1383 }
1384 }
1385 writeln!(writer)?;
1386 }
1387 }
1388
1389 write!(writer, "+")?;
1391 for width in &widths {
1392 write!(writer, "{}", "-".repeat(*width + 2))?;
1393 write!(writer, "+")?;
1394 }
1395 writeln!(writer)?;
1396
1397 Ok(())
1398}
1399
1400fn output_table<W: Write>(
1402 dataview: &DataView,
1403 writer: &mut W,
1404 max_col_width: Option<usize>,
1405 _col_sample_rows: usize, style: TableStyle,
1407 styled: bool,
1408 style_file: Option<&str>,
1409) -> Result<()> {
1410 let mut table = Table::new();
1411
1412 match style {
1414 TableStyle::Default => {
1415 return output_table_old_style(dataview, writer, max_col_width);
1418 }
1419 TableStyle::AsciiFull => {
1420 table.load_preset(ASCII_FULL);
1421 }
1422 TableStyle::AsciiCondensed => {
1423 table.load_preset(ASCII_FULL_CONDENSED);
1424 }
1425 TableStyle::AsciiBordersOnly => {
1426 table.load_preset(ASCII_BORDERS_ONLY);
1427 }
1428 TableStyle::AsciiHorizontalOnly => {
1429 table.load_preset(ASCII_HORIZONTAL_ONLY);
1430 }
1431 TableStyle::AsciiNoBorders => {
1432 table.load_preset(ASCII_NO_BORDERS);
1433 }
1434 TableStyle::Markdown => {
1435 table.load_preset(ASCII_MARKDOWN);
1436 }
1437 TableStyle::Utf8Full => {
1438 table.load_preset(UTF8_FULL);
1439 }
1440 TableStyle::Utf8Condensed => {
1441 table.load_preset(UTF8_FULL_CONDENSED);
1442 }
1443 TableStyle::Utf8BordersOnly => {
1444 table.load_preset(UTF8_BORDERS_ONLY);
1445 }
1446 TableStyle::Utf8HorizontalOnly => {
1447 table.load_preset(UTF8_HORIZONTAL_ONLY);
1448 }
1449 TableStyle::Utf8NoBorders => {
1450 table.load_preset(UTF8_NO_BORDERS);
1451 }
1452 TableStyle::Plain => {
1453 table.load_preset(NOTHING);
1454 }
1455 }
1456
1457 if max_col_width.is_some() {
1459 table.set_content_arrangement(ContentArrangement::Dynamic);
1460 }
1461
1462 let columns = dataview.column_names();
1464
1465 if styled {
1467 use crate::output::styled_table::{apply_styles_to_table, StyleConfig};
1468 use std::path::PathBuf;
1469
1470 let style_config = if let Some(file_path) = style_file {
1472 let path = PathBuf::from(file_path);
1473 StyleConfig::from_file(&path).ok()
1474 } else {
1475 StyleConfig::load_default()
1476 };
1477
1478 if let Some(config) = style_config {
1479 let rows: Vec<Vec<String>> = (0..dataview.row_count())
1481 .filter_map(|i| {
1482 dataview.get_row(i).map(|row| {
1483 row.values
1484 .iter()
1485 .map(|v| {
1486 let s = format_value(v);
1487 if let Some(max_width) = max_col_width {
1489 if s.len() > max_width {
1490 format!("{}...", &s[..max_width.saturating_sub(3)])
1491 } else {
1492 s
1493 }
1494 } else {
1495 s
1496 }
1497 })
1498 .collect()
1499 })
1500 })
1501 .collect();
1502
1503 if let Err(e) = apply_styles_to_table(&mut table, &columns, &rows, &config) {
1504 eprintln!("Warning: Failed to apply styles: {}", e);
1505 }
1506 }
1507 } else {
1508 table.set_header(&columns);
1510
1511 for row_idx in 0..dataview.row_count() {
1513 if let Some(row) = dataview.get_row(row_idx) {
1514 let row_strings: Vec<String> = row
1515 .values
1516 .iter()
1517 .map(|v| {
1518 let s = format_value(v);
1519 if let Some(max_width) = max_col_width {
1521 if s.len() > max_width {
1522 format!("{}...", &s[..max_width.saturating_sub(3)])
1523 } else {
1524 s
1525 }
1526 } else {
1527 s
1528 }
1529 })
1530 .collect();
1531 table.add_row(row_strings);
1532 }
1533 }
1534 }
1535
1536 writeln!(writer, "{}", table)?;
1538
1539 Ok(())
1540}
1541
1542fn format_value(value: &DataValue) -> String {
1544 match value {
1545 DataValue::Null => String::new(),
1546 DataValue::Integer(i) => i.to_string(),
1547 DataValue::Float(f) => f.to_string(),
1548 DataValue::String(s) => s.clone(),
1549 DataValue::InternedString(s) => s.to_string(),
1550 DataValue::Boolean(b) => b.to_string(),
1551 DataValue::DateTime(dt) => dt.to_string(),
1552 }
1553}
1554
1555fn value_to_json(value: &DataValue) -> serde_json::Value {
1557 match value {
1558 DataValue::Null => serde_json::Value::Null,
1559 DataValue::Integer(i) => serde_json::Value::Number((*i).into()),
1560 DataValue::Float(f) => {
1561 if let Some(n) = serde_json::Number::from_f64(*f) {
1562 serde_json::Value::Number(n)
1563 } else {
1564 serde_json::Value::Null
1565 }
1566 }
1567 DataValue::String(s) => serde_json::Value::String(s.clone()),
1568 DataValue::InternedString(s) => serde_json::Value::String(s.to_string()),
1569 DataValue::Boolean(b) => serde_json::Value::Bool(*b),
1570 DataValue::DateTime(dt) => serde_json::Value::String(dt.to_string()),
1571 }
1572}
1573
1574fn escape_csv_field(field: &str, delimiter: char) -> String {
1576 if field.contains(delimiter)
1577 || field.contains('"')
1578 || field.contains('\n')
1579 || field.contains('\r')
1580 {
1581 format!("\"{}\"", field.replace('"', "\"\""))
1582 } else {
1583 field.to_string()
1584 }
1585}