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 let into_pattern = regex::Regex::new(r"(?i)\s+INTO\s+#\w+").unwrap();
898 into_pattern.replace(statement, "").to_string()
899 } else {
900 statement.to_string()
901 };
902
903 let service = QueryExecutionService::new(config.case_insensitive, config.auto_hide_empty);
905 match service.execute_with_temp_tables(
906 &executable_sql,
907 Some(&dataview),
908 None,
909 Some(&temp_tables),
910 ) {
911 Ok(result) => {
912 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
913 let final_view = result.dataview;
914
915 if let Some(into_table) = &parsed_stmt.into_table {
917 let result_table = final_view.source_arc();
919 let row_count = result_table.row_count();
920
921 match temp_tables.insert(into_table.name.clone(), result_table) {
922 Ok(_) => {
923 info!(
924 "Stored {} rows in temporary table {}",
925 row_count, into_table.name
926 );
927
928 let mut statement_output = Vec::new();
930 writeln!(
931 &mut statement_output,
932 "({} rows affected) -> {}",
933 row_count, into_table.name
934 )?;
935 output.extend(
936 String::from_utf8_lossy(&statement_output)
937 .lines()
938 .map(String::from),
939 );
940
941 script_result.add_success(
942 statement_num,
943 statement.to_string(),
944 row_count,
945 exec_time,
946 );
947 continue; }
949 Err(e) => {
950 script_result.add_failure(
951 statement_num,
952 statement.to_string(),
953 e.to_string(),
954 exec_time,
955 );
956 break;
957 }
958 }
959 }
960
961 let mut statement_output = Vec::new();
963 match config.output_format {
964 OutputFormat::Csv => {
965 output_csv(&final_view, &mut statement_output, ',')?;
966 }
967 OutputFormat::Json => {
968 output_json(&final_view, &mut statement_output)?;
969 }
970 OutputFormat::JsonStructured => {
971 output_json_structured(&final_view, &mut statement_output, exec_time)?;
972 }
973 OutputFormat::Table => {
974 output_table(
975 &final_view,
976 &mut statement_output,
977 config.max_col_width,
978 config.col_sample_rows,
979 config.table_style,
980 )?;
981 writeln!(
982 &mut statement_output,
983 "Query completed: {} rows in {:.2}ms",
984 final_view.row_count(),
985 exec_time
986 )?;
987 }
988 OutputFormat::Tsv => {
989 output_csv(&final_view, &mut statement_output, '\t')?;
990 }
991 }
992
993 output.extend(
995 String::from_utf8_lossy(&statement_output)
996 .lines()
997 .map(String::from),
998 );
999
1000 script_result.add_success(
1001 statement_num,
1002 statement.to_string(),
1003 final_view.row_count(),
1004 exec_time,
1005 );
1006 }
1007 Err(e) => {
1008 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
1009 let error_msg = format!("Query {} failed: {}", statement_num, e);
1010
1011 if matches!(config.output_format, OutputFormat::Table) {
1012 output.push(error_msg.clone());
1013 }
1014
1015 script_result.add_failure(
1016 statement_num,
1017 statement.to_string(),
1018 e.to_string(),
1019 exec_time,
1020 );
1021
1022 }
1024 }
1025 }
1026
1027 if let Some(ref output_file) = config.output_file {
1029 let mut file = fs::File::create(output_file)?;
1030 for line in &output {
1031 writeln!(file, "{}", line)?;
1032 }
1033 info!("Results written to: {}", output_file);
1034 } else {
1035 for line in &output {
1036 println!("{}", line);
1037 }
1038 }
1039
1040 if matches!(config.output_format, OutputFormat::Table) {
1042 println!("\n=== Script Summary ===");
1043 println!("Total statements: {}", script_result.total_statements);
1044 println!("Successful: {}", script_result.successful_statements);
1045 println!("Failed: {}", script_result.failed_statements);
1046 println!(
1047 "Total execution time: {:.2}ms",
1048 script_result.total_execution_time_ms
1049 );
1050 }
1051
1052 if !script_result.all_successful() {
1053 return Err(anyhow::anyhow!(
1054 "{} of {} statements failed",
1055 script_result.failed_statements,
1056 script_result.total_statements
1057 ));
1058 }
1059
1060 Ok(())
1061}
1062
1063fn load_data_file(path: &str) -> Result<DataTable> {
1065 let path = Path::new(path);
1066
1067 if !path.exists() {
1068 return Err(anyhow::anyhow!("File not found: {}", path.display()));
1069 }
1070
1071 let extension = path
1073 .extension()
1074 .and_then(|ext| ext.to_str())
1075 .map(str::to_lowercase)
1076 .unwrap_or_default();
1077
1078 let table_name = path
1079 .file_stem()
1080 .and_then(|stem| stem.to_str())
1081 .unwrap_or("data")
1082 .to_string();
1083
1084 match extension.as_str() {
1085 "csv" => load_csv_to_datatable(path, &table_name)
1086 .with_context(|| format!("Failed to load CSV file: {}", path.display())),
1087 "json" => load_json_to_datatable(path, &table_name)
1088 .with_context(|| format!("Failed to load JSON file: {}", path.display())),
1089 _ => Err(anyhow::anyhow!(
1090 "Unsupported file type: {}. Use .csv or .json",
1091 extension
1092 )),
1093 }
1094}
1095
1096fn limit_results(dataview: &DataView, limit: usize) -> Result<DataTable> {
1098 let source = dataview.source();
1099 let mut limited_table = DataTable::new(&source.name);
1100
1101 for col in &source.columns {
1103 limited_table.add_column(col.clone());
1104 }
1105
1106 let rows_to_copy = dataview.row_count().min(limit);
1108 for i in 0..rows_to_copy {
1109 if let Some(row) = dataview.get_row(i) {
1110 let _ = limited_table.add_row(row.clone());
1111 }
1112 }
1113
1114 Ok(limited_table)
1115}
1116
1117fn output_results<W: Write>(
1119 dataview: &DataView,
1120 format: OutputFormat,
1121 writer: &mut W,
1122 max_col_width: Option<usize>,
1123 col_sample_rows: usize,
1124 exec_time_ms: f64,
1125 table_style: TableStyle,
1126) -> Result<()> {
1127 match format {
1128 OutputFormat::Csv => output_csv(dataview, writer, ','),
1129 OutputFormat::Tsv => output_csv(dataview, writer, '\t'),
1130 OutputFormat::Json => output_json(dataview, writer),
1131 OutputFormat::JsonStructured => output_json_structured(dataview, writer, exec_time_ms),
1132 OutputFormat::Table => output_table(
1133 dataview,
1134 writer,
1135 max_col_width,
1136 col_sample_rows,
1137 table_style,
1138 ),
1139 }
1140}
1141
1142fn output_csv<W: Write>(dataview: &DataView, writer: &mut W, delimiter: char) -> Result<()> {
1144 let columns = dataview.column_names();
1146 for (i, col) in columns.iter().enumerate() {
1147 if i > 0 {
1148 write!(writer, "{delimiter}")?;
1149 }
1150 write!(writer, "{}", escape_csv_field(col, delimiter))?;
1151 }
1152 writeln!(writer)?;
1153
1154 for row_idx in 0..dataview.row_count() {
1156 if let Some(row) = dataview.get_row(row_idx) {
1157 for (i, value) in row.values.iter().enumerate() {
1158 if i > 0 {
1159 write!(writer, "{delimiter}")?;
1160 }
1161 write!(
1162 writer,
1163 "{}",
1164 escape_csv_field(&format_value(value), delimiter)
1165 )?;
1166 }
1167 writeln!(writer)?;
1168 }
1169 }
1170
1171 Ok(())
1172}
1173
1174fn output_json<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
1176 let columns = dataview.column_names();
1177 let mut rows = Vec::new();
1178
1179 for row_idx in 0..dataview.row_count() {
1180 if let Some(row) = dataview.get_row(row_idx) {
1181 let mut json_row = serde_json::Map::new();
1182 for (col_idx, value) in row.values.iter().enumerate() {
1183 if col_idx < columns.len() {
1184 json_row.insert(columns[col_idx].clone(), value_to_json(value));
1185 }
1186 }
1187 rows.push(serde_json::Value::Object(json_row));
1188 }
1189 }
1190
1191 let json = serde_json::to_string_pretty(&rows)?;
1192 writeln!(writer, "{json}")?;
1193
1194 Ok(())
1195}
1196
1197fn output_json_structured<W: Write>(
1199 dataview: &DataView,
1200 writer: &mut W,
1201 exec_time: f64,
1202) -> Result<()> {
1203 let column_names = dataview.column_names();
1204 let data_table = dataview.source();
1205
1206 let mut columns = Vec::new();
1208 for (idx, name) in column_names.iter().enumerate() {
1209 let col_type = data_table
1210 .columns
1211 .get(idx)
1212 .map(|c| format!("{:?}", c.data_type))
1213 .unwrap_or_else(|| "UNKNOWN".to_string());
1214
1215 let mut max_width = name.len();
1217 for row_idx in 0..dataview.row_count() {
1218 if let Some(row) = dataview.get_row(row_idx) {
1219 if let Some(value) = row.values.get(idx) {
1220 let display_width = match value {
1221 DataValue::Null => 4, DataValue::Integer(i) => i.to_string().len(),
1223 DataValue::Float(f) => format!("{:.2}", f).len(),
1224 DataValue::String(s) => s.len(),
1225 DataValue::InternedString(s) => s.len(),
1226 DataValue::Boolean(b) => {
1227 if *b {
1228 4
1229 } else {
1230 5
1231 }
1232 } DataValue::DateTime(dt) => dt.len(),
1234 };
1235 max_width = max_width.max(display_width);
1236 }
1237 }
1238 }
1239
1240 let alignment = match data_table.columns.get(idx).map(|c| &c.data_type) {
1241 Some(crate::data::datatable::DataType::Integer) => "right",
1242 Some(crate::data::datatable::DataType::Float) => "right",
1243 _ => "left",
1244 };
1245
1246 let col_meta = serde_json::json!({
1247 "name": name,
1248 "type": col_type,
1249 "max_width": max_width,
1250 "alignment": alignment
1251 });
1252 columns.push(col_meta);
1253 }
1254
1255 let mut rows = Vec::new();
1257 for row_idx in 0..dataview.row_count() {
1258 if let Some(row) = dataview.get_row(row_idx) {
1259 let row_values: Vec<String> = row
1260 .values
1261 .iter()
1262 .map(|v| match v {
1263 DataValue::Null => String::new(),
1264 DataValue::Integer(i) => i.to_string(),
1265 DataValue::Float(f) => format!("{:.2}", f),
1266 DataValue::String(s) => s.clone(),
1267 DataValue::InternedString(s) => s.to_string(),
1268 DataValue::Boolean(b) => b.to_string(),
1269 DataValue::DateTime(dt) => dt.clone(),
1270 })
1271 .collect();
1272 rows.push(serde_json::Value::Array(
1273 row_values
1274 .into_iter()
1275 .map(serde_json::Value::String)
1276 .collect(),
1277 ));
1278 }
1279 }
1280
1281 let output = serde_json::json!({
1283 "columns": columns,
1284 "rows": rows,
1285 "metadata": {
1286 "total_rows": dataview.row_count(),
1287 "query_time_ms": exec_time
1288 }
1289 });
1290
1291 let json = serde_json::to_string_pretty(&output)?;
1292 writeln!(writer, "{json}")?;
1293
1294 Ok(())
1295}
1296
1297fn output_table_old_style<W: Write>(
1299 dataview: &DataView,
1300 writer: &mut W,
1301 max_col_width: Option<usize>,
1302) -> Result<()> {
1303 let columns = dataview.column_names();
1304
1305 let mut widths = vec![0; columns.len()];
1307 for (i, col) in columns.iter().enumerate() {
1308 widths[i] = col.len();
1309 }
1310
1311 for row_idx in 0..dataview.row_count() {
1313 if let Some(row) = dataview.get_row(row_idx) {
1314 for (i, value) in row.values.iter().enumerate() {
1315 if i < widths.len() {
1316 let value_str = format_value(value);
1317 widths[i] = widths[i].max(value_str.len());
1318 }
1319 }
1320 }
1321 }
1322
1323 if let Some(max_width) = max_col_width {
1325 for width in &mut widths {
1326 *width = (*width).min(max_width);
1327 }
1328 }
1329
1330 write!(writer, "+")?;
1332 for width in &widths {
1333 write!(writer, "{}", "-".repeat(*width + 2))?;
1334 write!(writer, "+")?;
1335 }
1336 writeln!(writer)?;
1337
1338 write!(writer, "|")?;
1340 for (i, col) in columns.iter().enumerate() {
1341 write!(writer, " {:^width$} |", col, width = widths[i])?;
1342 }
1343 writeln!(writer)?;
1344
1345 write!(writer, "+")?;
1347 for width in &widths {
1348 write!(writer, "{}", "-".repeat(*width + 2))?;
1349 write!(writer, "+")?;
1350 }
1351 writeln!(writer)?;
1352
1353 for row_idx in 0..dataview.row_count() {
1355 if let Some(row) = dataview.get_row(row_idx) {
1356 write!(writer, "|")?;
1357 for (i, value) in row.values.iter().enumerate() {
1358 if i < widths.len() {
1359 let value_str = format_value(value);
1360 let truncated = if value_str.len() > widths[i] {
1361 format!("{}...", &value_str[..widths[i].saturating_sub(3)])
1362 } else {
1363 value_str
1364 };
1365 write!(writer, " {:<width$} |", truncated, width = widths[i])?;
1366 }
1367 }
1368 writeln!(writer)?;
1369 }
1370 }
1371
1372 write!(writer, "+")?;
1374 for width in &widths {
1375 write!(writer, "{}", "-".repeat(*width + 2))?;
1376 write!(writer, "+")?;
1377 }
1378 writeln!(writer)?;
1379
1380 Ok(())
1381}
1382
1383fn output_table<W: Write>(
1385 dataview: &DataView,
1386 writer: &mut W,
1387 max_col_width: Option<usize>,
1388 _col_sample_rows: usize, style: TableStyle,
1390) -> Result<()> {
1391 let mut table = Table::new();
1392
1393 match style {
1395 TableStyle::Default => {
1396 return output_table_old_style(dataview, writer, max_col_width);
1399 }
1400 TableStyle::AsciiFull => {
1401 table.load_preset(ASCII_FULL);
1402 }
1403 TableStyle::AsciiCondensed => {
1404 table.load_preset(ASCII_FULL_CONDENSED);
1405 }
1406 TableStyle::AsciiBordersOnly => {
1407 table.load_preset(ASCII_BORDERS_ONLY);
1408 }
1409 TableStyle::AsciiHorizontalOnly => {
1410 table.load_preset(ASCII_HORIZONTAL_ONLY);
1411 }
1412 TableStyle::AsciiNoBorders => {
1413 table.load_preset(ASCII_NO_BORDERS);
1414 }
1415 TableStyle::Markdown => {
1416 table.load_preset(ASCII_MARKDOWN);
1417 }
1418 TableStyle::Utf8Full => {
1419 table.load_preset(UTF8_FULL);
1420 }
1421 TableStyle::Utf8Condensed => {
1422 table.load_preset(UTF8_FULL_CONDENSED);
1423 }
1424 TableStyle::Utf8BordersOnly => {
1425 table.load_preset(UTF8_BORDERS_ONLY);
1426 }
1427 TableStyle::Utf8HorizontalOnly => {
1428 table.load_preset(UTF8_HORIZONTAL_ONLY);
1429 }
1430 TableStyle::Utf8NoBorders => {
1431 table.load_preset(UTF8_NO_BORDERS);
1432 }
1433 TableStyle::Plain => {
1434 table.load_preset(NOTHING);
1435 }
1436 }
1437
1438 if max_col_width.is_some() {
1440 table.set_content_arrangement(ContentArrangement::Dynamic);
1441 }
1442
1443 let columns = dataview.column_names();
1445 table.set_header(&columns);
1446
1447 for row_idx in 0..dataview.row_count() {
1449 if let Some(row) = dataview.get_row(row_idx) {
1450 let row_strings: Vec<String> = row
1451 .values
1452 .iter()
1453 .map(|v| {
1454 let s = format_value(v);
1455 if let Some(max_width) = max_col_width {
1457 if s.len() > max_width {
1458 format!("{}...", &s[..max_width.saturating_sub(3)])
1459 } else {
1460 s
1461 }
1462 } else {
1463 s
1464 }
1465 })
1466 .collect();
1467 table.add_row(row_strings);
1468 }
1469 }
1470
1471 writeln!(writer, "{}", table)?;
1473
1474 Ok(())
1475}
1476
1477fn format_value(value: &DataValue) -> String {
1479 match value {
1480 DataValue::Null => String::new(),
1481 DataValue::Integer(i) => i.to_string(),
1482 DataValue::Float(f) => f.to_string(),
1483 DataValue::String(s) => s.clone(),
1484 DataValue::InternedString(s) => s.to_string(),
1485 DataValue::Boolean(b) => b.to_string(),
1486 DataValue::DateTime(dt) => dt.to_string(),
1487 }
1488}
1489
1490fn value_to_json(value: &DataValue) -> serde_json::Value {
1492 match value {
1493 DataValue::Null => serde_json::Value::Null,
1494 DataValue::Integer(i) => serde_json::Value::Number((*i).into()),
1495 DataValue::Float(f) => {
1496 if let Some(n) = serde_json::Number::from_f64(*f) {
1497 serde_json::Value::Number(n)
1498 } else {
1499 serde_json::Value::Null
1500 }
1501 }
1502 DataValue::String(s) => serde_json::Value::String(s.clone()),
1503 DataValue::InternedString(s) => serde_json::Value::String(s.to_string()),
1504 DataValue::Boolean(b) => serde_json::Value::Bool(*b),
1505 DataValue::DateTime(dt) => serde_json::Value::String(dt.to_string()),
1506 }
1507}
1508
1509fn escape_csv_field(field: &str, delimiter: char) -> String {
1511 if field.contains(delimiter)
1512 || field.contains('"')
1513 || field.contains('\n')
1514 || field.contains('\r')
1515 {
1516 format!("\"{}\"", field.replace('"', "\"\""))
1517 } else {
1518 field.to_string()
1519 }
1520}