1use anyhow::{Context, Result};
2use comfy_table::presets::*;
3use comfy_table::{Cell, ContentArrangement, Table};
4use std::fs;
5use std::io::{self, Write};
6use std::path::Path;
7use std::time::Instant;
8use tracing::{debug, info};
9
10use crate::config::config::Config;
11use crate::data::data_view::DataView;
12use crate::data::datatable::{DataTable, DataValue};
13use crate::data::datatable_loaders::{load_csv_to_datatable, load_json_to_datatable};
14use crate::services::query_execution_service::QueryExecutionService;
15use crate::sql::parser::ast::{CTEType, TableSource, CTE};
16use crate::sql::script_parser::{ScriptParser, ScriptResult};
17
18fn extract_cte_dependencies(cte: &CTE) -> Vec<String> {
20 let mut deps = Vec::new();
23
24 if let CTEType::Standard(query) = &cte.cte_type {
25 if let Some(from_table) = &query.from_table {
26 deps.push(from_table.clone());
27 }
28
29 for join in &query.joins {
31 match &join.table {
32 TableSource::Table(table_name) => {
33 deps.push(table_name.clone());
34 }
35 TableSource::DerivedTable { alias, .. } => {
36 deps.push(alias.clone());
37 }
38 }
39 }
40 }
41
42 deps
43}
44
45#[derive(Debug, Clone)]
47pub enum OutputFormat {
48 Csv,
49 Json,
50 JsonStructured, Table,
52 Tsv,
53}
54
55impl OutputFormat {
56 pub fn from_str(s: &str) -> Result<Self> {
57 match s.to_lowercase().as_str() {
58 "csv" => Ok(OutputFormat::Csv),
59 "json" => Ok(OutputFormat::Json),
60 "json-structured" => Ok(OutputFormat::JsonStructured),
61 "table" => Ok(OutputFormat::Table),
62 "tsv" => Ok(OutputFormat::Tsv),
63 _ => Err(anyhow::anyhow!(
64 "Invalid output format: {}. Use csv, json, json-structured, table, or tsv",
65 s
66 )),
67 }
68 }
69}
70
71#[derive(Debug, Clone, Copy)]
73pub enum TableStyle {
74 Default,
76 AsciiFull,
78 AsciiCondensed,
80 AsciiBordersOnly,
82 AsciiHorizontalOnly,
84 AsciiNoBorders,
86 Markdown,
88 Utf8Full,
90 Utf8Condensed,
92 Utf8BordersOnly,
94 Utf8HorizontalOnly,
96 Utf8NoBorders,
98 Plain,
100}
101
102impl TableStyle {
103 pub fn from_str(s: &str) -> Result<Self> {
104 match s.to_lowercase().as_str() {
105 "default" => Ok(TableStyle::Default),
106 "ascii" | "ascii-full" => Ok(TableStyle::AsciiFull),
107 "ascii-condensed" => Ok(TableStyle::AsciiCondensed),
108 "ascii-borders" | "ascii-borders-only" => Ok(TableStyle::AsciiBordersOnly),
109 "ascii-horizontal" | "ascii-horizontal-only" => Ok(TableStyle::AsciiHorizontalOnly),
110 "ascii-noborders" | "ascii-no-borders" => Ok(TableStyle::AsciiNoBorders),
111 "markdown" | "md" => Ok(TableStyle::Markdown),
112 "utf8" | "utf8-full" => Ok(TableStyle::Utf8Full),
113 "utf8-condensed" => Ok(TableStyle::Utf8Condensed),
114 "utf8-borders" | "utf8-borders-only" => Ok(TableStyle::Utf8BordersOnly),
115 "utf8-horizontal" | "utf8-horizontal-only" => Ok(TableStyle::Utf8HorizontalOnly),
116 "utf8-noborders" | "utf8-no-borders" => Ok(TableStyle::Utf8NoBorders),
117 "plain" | "none" => Ok(TableStyle::Plain),
118 _ => Err(anyhow::anyhow!(
119 "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",
120 s
121 )),
122 }
123 }
124
125 pub fn list_styles() -> &'static str {
126 "Available table styles:
127 default - Current default ASCII style
128 ascii - ASCII with full borders
129 ascii-condensed - ASCII with condensed rows
130 ascii-borders - ASCII with outer borders only
131 ascii-horizontal - ASCII with horizontal lines only
132 ascii-noborders - ASCII with no borders
133 markdown - GitHub-flavored Markdown table
134 utf8 - UTF8 box-drawing characters
135 utf8-condensed - UTF8 with condensed rows
136 utf8-borders - UTF8 with outer borders only
137 utf8-horizontal - UTF8 with horizontal lines only
138 utf8-noborders - UTF8 with no borders
139 plain - No formatting, data only"
140 }
141}
142
143pub struct NonInteractiveConfig {
145 pub data_file: String,
146 pub query: String,
147 pub output_format: OutputFormat,
148 pub output_file: Option<String>,
149 pub case_insensitive: bool,
150 pub auto_hide_empty: bool,
151 pub limit: Option<usize>,
152 pub query_plan: bool,
153 pub show_work_units: bool,
154 pub execution_plan: bool,
155 pub cte_info: bool,
156 pub rewrite_analysis: bool,
157 pub lift_in_expressions: bool,
158 pub script_file: Option<String>, pub debug_trace: bool,
160 pub max_col_width: Option<usize>, pub col_sample_rows: usize, pub table_style: TableStyle, }
164
165pub fn execute_non_interactive(config: NonInteractiveConfig) -> Result<()> {
167 let start_time = Instant::now();
168
169 let (data_table, _is_dual) = if config.data_file.is_empty() {
171 info!("No data file provided, using DUAL table");
172 (crate::data::datatable::DataTable::dual(), true)
173 } else {
174 info!("Loading data from: {}", config.data_file);
175 let table = load_data_file(&config.data_file)?;
176 info!(
177 "Loaded {} rows with {} columns",
178 table.row_count(),
179 table.column_count()
180 );
181 (table, false)
182 };
183 let _table_name = data_table.name.clone();
184
185 let dataview = DataView::new(std::sync::Arc::new(data_table));
187
188 info!("Executing query: {}", config.query);
190
191 if config.execution_plan {
193 println!("\n=== EXECUTION PLAN ===");
194 println!("Query: {}", config.query);
195 println!("\nExecution Steps:");
196 println!("1. PARSE - Parse SQL query");
197 println!("2. LOAD_DATA - Load data from {}", &config.data_file);
198 println!(
199 " • Loaded {} rows, {} columns",
200 dataview.row_count(),
201 dataview.column_count()
202 );
203 }
204
205 if config.show_work_units {
207 use crate::query_plan::{ExpressionLifter, QueryAnalyzer};
208 use crate::sql::recursive_parser::Parser;
209
210 let mut parser = Parser::new(&config.query);
211 match parser.parse() {
212 Ok(stmt) => {
213 let mut analyzer = QueryAnalyzer::new();
214 let mut lifter = ExpressionLifter::new();
215
216 let mut stmt_copy = stmt.clone();
218 let lifted = lifter.lift_expressions(&mut stmt_copy);
219
220 match analyzer.analyze(&stmt_copy, config.query.clone()) {
222 Ok(plan) => {
223 println!("\n{}", plan.explain());
224
225 if !lifted.is_empty() {
226 println!("\nLifted CTEs:");
227 for cte in &lifted {
228 println!(" - {}", cte.name);
229 }
230 }
231
232 return Ok(());
233 }
234 Err(e) => {
235 eprintln!("Error analyzing query: {}", e);
236 return Err(anyhow::anyhow!("Query analysis failed: {}", e));
237 }
238 }
239 }
240 Err(e) => {
241 eprintln!("Error parsing query: {}", e);
242 return Err(anyhow::anyhow!("Parse error: {}", e));
243 }
244 }
245 }
246
247 if config.query_plan {
249 use crate::sql::recursive_parser::Parser;
250 let mut parser = Parser::new(&config.query);
251 match parser.parse() {
252 Ok(statement) => {
253 println!("\n=== QUERY PLAN (AST) ===");
254 println!("{statement:#?}");
255 println!("=== END QUERY PLAN ===\n");
256 }
257 Err(e) => {
258 eprintln!("Failed to parse query for plan: {e}");
259 }
260 }
261 }
262
263 if config.rewrite_analysis {
265 use crate::sql::query_rewriter::{QueryRewriter, RewriteAnalysis};
266 use crate::sql::recursive_parser::Parser;
267 use serde_json::json;
268
269 let mut parser = Parser::new(&config.query);
270 match parser.parse() {
271 Ok(statement) => {
272 let mut rewriter = QueryRewriter::new();
273 let suggestions = rewriter.analyze(&statement);
274
275 let analysis = RewriteAnalysis::from_suggestions(suggestions);
276 println!("{}", serde_json::to_string_pretty(&analysis).unwrap());
277 return Ok(());
278 }
279 Err(e) => {
280 let output = json!({
281 "success": false,
282 "error": format!("{}", e),
283 "suggestions": [],
284 "can_auto_rewrite": false,
285 });
286 println!("{}", serde_json::to_string_pretty(&output).unwrap());
287 return Ok(());
288 }
289 }
290 }
291
292 if config.cte_info {
294 use crate::sql::recursive_parser::Parser;
295 use serde_json::json;
296
297 let mut parser = Parser::new(&config.query);
298 match parser.parse() {
299 Ok(statement) => {
300 let mut cte_info = Vec::new();
301
302 for (index, cte) in statement.ctes.iter().enumerate() {
304 let cte_json = json!({
305 "index": index,
306 "name": cte.name,
307 "columns": cte.column_list,
308 "dependencies": extract_cte_dependencies(cte),
311 });
312 cte_info.push(cte_json);
313 }
314
315 let output = json!({
316 "success": true,
317 "ctes": cte_info,
318 "total": statement.ctes.len(),
319 "has_final_select": !statement.columns.is_empty() || !statement.select_items.is_empty(),
320 });
321
322 println!("{}", serde_json::to_string_pretty(&output).unwrap());
323 return Ok(());
324 }
325 Err(e) => {
326 let output = json!({
327 "success": false,
328 "error": format!("{}", e),
329 "ctes": [],
330 "total": 0,
331 });
332 println!("{}", serde_json::to_string_pretty(&output).unwrap());
333 return Ok(());
334 }
335 }
336 }
337
338 let query_start = Instant::now();
339
340 let app_config = Config::load().unwrap_or_else(|e| {
342 debug!("Could not load config file: {}. Using defaults.", e);
343 Config::default()
344 });
345
346 crate::config::global::init_config(app_config.clone());
348
349 let mut behavior_config = app_config.behavior.clone();
351 debug!(
352 "Using date notation: {}",
353 behavior_config.default_date_notation
354 );
355 if config.case_insensitive {
357 behavior_config.case_insensitive_default = true;
358 }
359 if config.auto_hide_empty {
360 behavior_config.hide_empty_columns = true;
361 }
362
363 let exec_start = Instant::now();
365 let result = if config.lift_in_expressions {
366 use crate::data::query_engine::QueryEngine;
367 use crate::query_plan::{CTEHoister, InOperatorLifter};
368 use crate::sql::recursive_parser::Parser;
369
370 let mut parser = Parser::new(&config.query);
371 match parser.parse() {
372 Ok(mut stmt) => {
373 use crate::query_plan::ExpressionLifter;
375 let mut expr_lifter = ExpressionLifter::new();
376 let lifted = expr_lifter.lift_expressions(&mut stmt);
377 if !lifted.is_empty() {
378 info!(
379 "Applied expression lifting - {} CTEs generated",
380 lifted.len()
381 );
382 }
383
384 stmt = CTEHoister::hoist_ctes(stmt);
386
387 let mut lifter = InOperatorLifter::new();
389 if lifter.rewrite_query(&mut stmt) {
390 info!("Applied IN expression lifting - query rewritten with CTEs");
391
392 let engine = QueryEngine::with_case_insensitive(config.case_insensitive);
394
395 match engine.execute_statement(dataview.source_arc(), stmt) {
396 Ok(result_view) => {
397 Ok(
399 crate::services::query_execution_service::QueryExecutionResult {
400 dataview: result_view,
401 stats: crate::services::query_execution_service::QueryStats {
402 row_count: 0, column_count: 0,
404 execution_time: exec_start.elapsed(),
405 query_engine_time: exec_start.elapsed(),
406 },
407 hidden_columns: Vec::new(),
408 query: config.query.clone(),
409 execution_plan: None,
410 debug_trace: None,
411 },
412 )
413 }
414 Err(e) => Err(e),
415 }
416 } else {
417 let query_service =
419 QueryExecutionService::with_behavior_config(behavior_config);
420 if config.debug_trace {
421 let debug_ctx = crate::debug_trace::DebugContext::new(
422 crate::debug_trace::DebugLevel::Debug,
423 );
424 query_service.execute_with_debug(
425 &config.query,
426 Some(&dataview),
427 Some(dataview.source()),
428 Some(debug_ctx),
429 )
430 } else {
431 query_service.execute(
432 &config.query,
433 Some(&dataview),
434 Some(dataview.source()),
435 )
436 }
437 }
438 }
439 Err(_) => {
440 let query_service = QueryExecutionService::with_behavior_config(behavior_config);
442 if config.debug_trace {
443 let debug_ctx = crate::debug_trace::DebugContext::new(
444 crate::debug_trace::DebugLevel::Debug,
445 );
446 query_service.execute_with_debug(
447 &config.query,
448 Some(&dataview),
449 Some(dataview.source()),
450 Some(debug_ctx),
451 )
452 } else {
453 query_service.execute(&config.query, Some(&dataview), Some(dataview.source()))
454 }
455 }
456 }
457 } else {
458 let query_service = QueryExecutionService::with_behavior_config(behavior_config);
460
461 if config.debug_trace {
463 let debug_ctx =
464 crate::debug_trace::DebugContext::new(crate::debug_trace::DebugLevel::Debug);
465 query_service.execute_with_debug(
466 &config.query,
467 Some(&dataview),
468 Some(dataview.source()),
469 Some(debug_ctx),
470 )
471 } else {
472 query_service.execute(&config.query, Some(&dataview), Some(dataview.source()))
473 }
474 }?;
475 let exec_time = exec_start.elapsed();
476
477 let query_time = query_start.elapsed();
478 info!("Query executed in {:?}", query_time);
479 info!(
480 "Result: {} rows, {} columns",
481 result.dataview.row_count(),
482 result.dataview.column_count()
483 );
484
485 if config.execution_plan {
487 use crate::data::query_engine::QueryEngine;
489
490 let query_engine = QueryEngine::new();
491
492 match query_engine.execute_with_plan(
493 std::sync::Arc::new(dataview.source().clone()),
494 &config.query,
495 ) {
496 Ok((_view, plan)) => {
497 print!("{}", plan.format_tree());
499 }
500 Err(e) => {
501 eprintln!("Could not generate detailed execution plan: {}", e);
503 println!(
504 "3. QUERY_EXECUTION [{:.3}ms]",
505 exec_time.as_secs_f64() * 1000.0
506 );
507
508 use crate::sql::recursive_parser::Parser;
510 let mut parser = Parser::new(&config.query);
511 if let Ok(stmt) = parser.parse() {
512 if stmt.where_clause.is_some() {
513 println!(" • WHERE clause filtering applied");
514 println!(" • Rows after filter: {}", result.dataview.row_count());
515 }
516
517 if let Some(ref order_by) = stmt.order_by {
518 println!(" • ORDER BY: {} column(s)", order_by.len());
519 }
520
521 if let Some(ref group_by) = stmt.group_by {
522 println!(" • GROUP BY: {} column(s)", group_by.len());
523 }
524
525 if let Some(limit) = stmt.limit {
526 println!(" • LIMIT: {} rows", limit);
527 }
528
529 if stmt.distinct {
530 println!(" • DISTINCT applied");
531 }
532 }
533 }
534 }
535
536 println!("\nExecution Statistics:");
537 println!(
538 " Preparation: {:.3}ms",
539 (exec_start - start_time).as_secs_f64() * 1000.0
540 );
541 println!(
542 " Query time: {:.3}ms",
543 exec_time.as_secs_f64() * 1000.0
544 );
545 println!(
546 " Total time: {:.3}ms",
547 query_time.as_secs_f64() * 1000.0
548 );
549 println!(" Rows returned: {}", result.dataview.row_count());
550 println!(" Columns: {}", result.dataview.column_count());
551 println!("\n=== END EXECUTION PLAN ===");
552 println!();
553 }
554
555 let final_view = if let Some(limit) = config.limit {
557 let limited_table = limit_results(&result.dataview, limit)?;
558 DataView::new(std::sync::Arc::new(limited_table))
559 } else {
560 result.dataview
561 };
562
563 if let Some(ref trace_output) = result.debug_trace {
565 eprintln!("{}", trace_output);
566 }
567
568 let exec_time_ms = exec_time.as_secs_f64() * 1000.0;
570 let output_result = if let Some(ref path) = config.output_file {
571 let mut file = fs::File::create(path)
572 .with_context(|| format!("Failed to create output file: {path}"))?;
573 output_results(
574 &final_view,
575 config.output_format,
576 &mut file,
577 config.max_col_width,
578 config.col_sample_rows,
579 exec_time_ms,
580 config.table_style,
581 )?;
582 info!("Results written to: {}", path);
583 Ok(())
584 } else {
585 output_results(
586 &final_view,
587 config.output_format,
588 &mut io::stdout(),
589 config.max_col_width,
590 config.col_sample_rows,
591 exec_time_ms,
592 config.table_style,
593 )?;
594 Ok(())
595 };
596
597 let total_time = start_time.elapsed();
598 debug!("Total execution time: {:?}", total_time);
599
600 if config.output_file.is_none() {
602 eprintln!(
603 "\n# Query completed: {} rows in {:?}",
604 final_view.row_count(),
605 query_time
606 );
607 }
608
609 output_result
610}
611
612pub fn execute_script(config: NonInteractiveConfig) -> Result<()> {
614 let _start_time = Instant::now();
615
616 let parser = ScriptParser::new(&config.query);
618 let statements = parser.parse_and_validate()?;
619
620 info!("Found {} statements in script", statements.len());
621
622 let data_file = if !config.data_file.is_empty() {
624 config.data_file.clone()
626 } else if let Some(hint) = parser.data_file_hint() {
627 info!("Using data file from script hint: {}", hint);
629
630 if let Some(script_path) = config.script_file.as_ref() {
632 let script_dir = std::path::Path::new(script_path)
633 .parent()
634 .unwrap_or(std::path::Path::new("."));
635 let hint_path = std::path::Path::new(hint);
636
637 if hint_path.is_relative() {
638 script_dir.join(hint_path).to_string_lossy().to_string()
639 } else {
640 hint.to_string()
641 }
642 } else {
643 hint.to_string()
644 }
645 } else {
646 String::new()
647 };
648
649 let (data_table, _is_dual) = if data_file.is_empty() {
651 info!("No data file provided, using DUAL table");
653 (DataTable::dual(), true)
654 } else {
655 if !std::path::Path::new(&data_file).exists() {
657 anyhow::bail!(
658 "Data file not found: {}\n\
659 Please check the path is correct",
660 data_file
661 );
662 }
663
664 info!("Loading data from: {}", data_file);
665 let table = load_data_file(&data_file)?;
666 info!(
667 "Loaded {} rows with {} columns",
668 table.row_count(),
669 table.column_count()
670 );
671 (table, false)
672 };
673
674 let mut script_result = ScriptResult::new();
676 let mut output = Vec::new();
677
678 let arc_data_table = std::sync::Arc::new(data_table);
680
681 for (idx, statement) in statements.iter().enumerate() {
683 let statement_num = idx + 1;
684 let stmt_start = Instant::now();
685
686 if matches!(config.output_format, OutputFormat::Table) {
688 if idx > 0 {
689 output.push(String::new()); }
691 output.push(format!("-- Query {} --", statement_num));
692 }
693
694 let dataview = DataView::new(arc_data_table.clone());
696
697 let service = QueryExecutionService::new(config.case_insensitive, config.auto_hide_empty);
699 match service.execute(statement, Some(&dataview), None) {
700 Ok(result) => {
701 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
702 let final_view = result.dataview;
703
704 let mut statement_output = Vec::new();
706 match config.output_format {
707 OutputFormat::Csv => {
708 output_csv(&final_view, &mut statement_output, ',')?;
709 }
710 OutputFormat::Json => {
711 output_json(&final_view, &mut statement_output)?;
712 }
713 OutputFormat::JsonStructured => {
714 output_json_structured(&final_view, &mut statement_output, exec_time)?;
715 }
716 OutputFormat::Table => {
717 output_table(
718 &final_view,
719 &mut statement_output,
720 config.max_col_width,
721 config.col_sample_rows,
722 config.table_style,
723 )?;
724 writeln!(
725 &mut statement_output,
726 "Query completed: {} rows in {:.2}ms",
727 final_view.row_count(),
728 exec_time
729 )?;
730 }
731 OutputFormat::Tsv => {
732 output_csv(&final_view, &mut statement_output, '\t')?;
733 }
734 }
735
736 output.extend(
738 String::from_utf8_lossy(&statement_output)
739 .lines()
740 .map(String::from),
741 );
742
743 script_result.add_success(
744 statement_num,
745 statement.clone(),
746 final_view.row_count(),
747 exec_time,
748 );
749 }
750 Err(e) => {
751 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
752 let error_msg = format!("Query {} failed: {}", statement_num, e);
753
754 if matches!(config.output_format, OutputFormat::Table) {
755 output.push(error_msg.clone());
756 }
757
758 script_result.add_failure(
759 statement_num,
760 statement.clone(),
761 e.to_string(),
762 exec_time,
763 );
764
765 }
767 }
768 }
769
770 if let Some(ref output_file) = config.output_file {
772 let mut file = fs::File::create(output_file)?;
773 for line in &output {
774 writeln!(file, "{}", line)?;
775 }
776 info!("Results written to: {}", output_file);
777 } else {
778 for line in &output {
779 println!("{}", line);
780 }
781 }
782
783 if matches!(config.output_format, OutputFormat::Table) {
785 println!("\n=== Script Summary ===");
786 println!("Total statements: {}", script_result.total_statements);
787 println!("Successful: {}", script_result.successful_statements);
788 println!("Failed: {}", script_result.failed_statements);
789 println!(
790 "Total execution time: {:.2}ms",
791 script_result.total_execution_time_ms
792 );
793 }
794
795 if !script_result.all_successful() {
796 return Err(anyhow::anyhow!(
797 "{} of {} statements failed",
798 script_result.failed_statements,
799 script_result.total_statements
800 ));
801 }
802
803 Ok(())
804}
805
806fn load_data_file(path: &str) -> Result<DataTable> {
808 let path = Path::new(path);
809
810 if !path.exists() {
811 return Err(anyhow::anyhow!("File not found: {}", path.display()));
812 }
813
814 let extension = path
816 .extension()
817 .and_then(|ext| ext.to_str())
818 .map(str::to_lowercase)
819 .unwrap_or_default();
820
821 let table_name = path
822 .file_stem()
823 .and_then(|stem| stem.to_str())
824 .unwrap_or("data")
825 .to_string();
826
827 match extension.as_str() {
828 "csv" => load_csv_to_datatable(path, &table_name)
829 .with_context(|| format!("Failed to load CSV file: {}", path.display())),
830 "json" => load_json_to_datatable(path, &table_name)
831 .with_context(|| format!("Failed to load JSON file: {}", path.display())),
832 _ => Err(anyhow::anyhow!(
833 "Unsupported file type: {}. Use .csv or .json",
834 extension
835 )),
836 }
837}
838
839fn limit_results(dataview: &DataView, limit: usize) -> Result<DataTable> {
841 let source = dataview.source();
842 let mut limited_table = DataTable::new(&source.name);
843
844 for col in &source.columns {
846 limited_table.add_column(col.clone());
847 }
848
849 let rows_to_copy = dataview.row_count().min(limit);
851 for i in 0..rows_to_copy {
852 if let Some(row) = dataview.get_row(i) {
853 limited_table.add_row(row.clone());
854 }
855 }
856
857 Ok(limited_table)
858}
859
860fn output_results<W: Write>(
862 dataview: &DataView,
863 format: OutputFormat,
864 writer: &mut W,
865 max_col_width: Option<usize>,
866 col_sample_rows: usize,
867 exec_time_ms: f64,
868 table_style: TableStyle,
869) -> Result<()> {
870 match format {
871 OutputFormat::Csv => output_csv(dataview, writer, ','),
872 OutputFormat::Tsv => output_csv(dataview, writer, '\t'),
873 OutputFormat::Json => output_json(dataview, writer),
874 OutputFormat::JsonStructured => output_json_structured(dataview, writer, exec_time_ms),
875 OutputFormat::Table => output_table(
876 dataview,
877 writer,
878 max_col_width,
879 col_sample_rows,
880 table_style,
881 ),
882 }
883}
884
885fn output_csv<W: Write>(dataview: &DataView, writer: &mut W, delimiter: char) -> Result<()> {
887 let columns = dataview.column_names();
889 for (i, col) in columns.iter().enumerate() {
890 if i > 0 {
891 write!(writer, "{delimiter}")?;
892 }
893 write!(writer, "{}", escape_csv_field(col, delimiter))?;
894 }
895 writeln!(writer)?;
896
897 for row_idx in 0..dataview.row_count() {
899 if let Some(row) = dataview.get_row(row_idx) {
900 for (i, value) in row.values.iter().enumerate() {
901 if i > 0 {
902 write!(writer, "{delimiter}")?;
903 }
904 write!(
905 writer,
906 "{}",
907 escape_csv_field(&format_value(value), delimiter)
908 )?;
909 }
910 writeln!(writer)?;
911 }
912 }
913
914 Ok(())
915}
916
917fn output_json<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
919 let columns = dataview.column_names();
920 let mut rows = Vec::new();
921
922 for row_idx in 0..dataview.row_count() {
923 if let Some(row) = dataview.get_row(row_idx) {
924 let mut json_row = serde_json::Map::new();
925 for (col_idx, value) in row.values.iter().enumerate() {
926 if col_idx < columns.len() {
927 json_row.insert(columns[col_idx].clone(), value_to_json(value));
928 }
929 }
930 rows.push(serde_json::Value::Object(json_row));
931 }
932 }
933
934 let json = serde_json::to_string_pretty(&rows)?;
935 writeln!(writer, "{json}")?;
936
937 Ok(())
938}
939
940fn output_json_structured<W: Write>(
942 dataview: &DataView,
943 writer: &mut W,
944 exec_time: f64,
945) -> Result<()> {
946 let column_names = dataview.column_names();
947 let data_table = dataview.source();
948
949 let mut columns = Vec::new();
951 for (idx, name) in column_names.iter().enumerate() {
952 let col_type = data_table
953 .columns
954 .get(idx)
955 .map(|c| format!("{:?}", c.data_type))
956 .unwrap_or_else(|| "UNKNOWN".to_string());
957
958 let mut max_width = name.len();
960 for row_idx in 0..dataview.row_count() {
961 if let Some(row) = dataview.get_row(row_idx) {
962 if let Some(value) = row.values.get(idx) {
963 let display_width = match value {
964 DataValue::Null => 4, DataValue::Integer(i) => i.to_string().len(),
966 DataValue::Float(f) => format!("{:.2}", f).len(),
967 DataValue::String(s) => s.len(),
968 DataValue::InternedString(s) => s.len(),
969 DataValue::Boolean(b) => {
970 if *b {
971 4
972 } else {
973 5
974 }
975 } DataValue::DateTime(dt) => dt.len(),
977 };
978 max_width = max_width.max(display_width);
979 }
980 }
981 }
982
983 let alignment = match data_table.columns.get(idx).map(|c| &c.data_type) {
984 Some(crate::data::datatable::DataType::Integer) => "right",
985 Some(crate::data::datatable::DataType::Float) => "right",
986 _ => "left",
987 };
988
989 let col_meta = serde_json::json!({
990 "name": name,
991 "type": col_type,
992 "max_width": max_width,
993 "alignment": alignment
994 });
995 columns.push(col_meta);
996 }
997
998 let mut rows = Vec::new();
1000 for row_idx in 0..dataview.row_count() {
1001 if let Some(row) = dataview.get_row(row_idx) {
1002 let row_values: Vec<String> = row
1003 .values
1004 .iter()
1005 .map(|v| match v {
1006 DataValue::Null => String::new(),
1007 DataValue::Integer(i) => i.to_string(),
1008 DataValue::Float(f) => format!("{:.2}", f),
1009 DataValue::String(s) => s.clone(),
1010 DataValue::InternedString(s) => s.to_string(),
1011 DataValue::Boolean(b) => b.to_string(),
1012 DataValue::DateTime(dt) => dt.clone(),
1013 })
1014 .collect();
1015 rows.push(serde_json::Value::Array(
1016 row_values
1017 .into_iter()
1018 .map(serde_json::Value::String)
1019 .collect(),
1020 ));
1021 }
1022 }
1023
1024 let output = serde_json::json!({
1026 "columns": columns,
1027 "rows": rows,
1028 "metadata": {
1029 "total_rows": dataview.row_count(),
1030 "query_time_ms": exec_time
1031 }
1032 });
1033
1034 let json = serde_json::to_string_pretty(&output)?;
1035 writeln!(writer, "{json}")?;
1036
1037 Ok(())
1038}
1039
1040fn output_table_old_style<W: Write>(
1042 dataview: &DataView,
1043 writer: &mut W,
1044 max_col_width: Option<usize>,
1045) -> Result<()> {
1046 let columns = dataview.column_names();
1047
1048 let mut widths = vec![0; columns.len()];
1050 for (i, col) in columns.iter().enumerate() {
1051 widths[i] = col.len();
1052 }
1053
1054 for row_idx in 0..dataview.row_count() {
1056 if let Some(row) = dataview.get_row(row_idx) {
1057 for (i, value) in row.values.iter().enumerate() {
1058 if i < widths.len() {
1059 let value_str = format_value(value);
1060 widths[i] = widths[i].max(value_str.len());
1061 }
1062 }
1063 }
1064 }
1065
1066 if let Some(max_width) = max_col_width {
1068 for width in &mut widths {
1069 *width = (*width).min(max_width);
1070 }
1071 }
1072
1073 write!(writer, "+")?;
1075 for width in &widths {
1076 write!(writer, "{}", "-".repeat(*width + 2))?;
1077 write!(writer, "+")?;
1078 }
1079 writeln!(writer)?;
1080
1081 write!(writer, "|")?;
1083 for (i, col) in columns.iter().enumerate() {
1084 write!(writer, " {:^width$} |", col, width = widths[i])?;
1085 }
1086 writeln!(writer)?;
1087
1088 write!(writer, "+")?;
1090 for width in &widths {
1091 write!(writer, "{}", "-".repeat(*width + 2))?;
1092 write!(writer, "+")?;
1093 }
1094 writeln!(writer)?;
1095
1096 for row_idx in 0..dataview.row_count() {
1098 if let Some(row) = dataview.get_row(row_idx) {
1099 write!(writer, "|")?;
1100 for (i, value) in row.values.iter().enumerate() {
1101 if i < widths.len() {
1102 let value_str = format_value(value);
1103 let truncated = if value_str.len() > widths[i] {
1104 format!("{}...", &value_str[..widths[i].saturating_sub(3)])
1105 } else {
1106 value_str
1107 };
1108 write!(writer, " {:<width$} |", truncated, width = widths[i])?;
1109 }
1110 }
1111 writeln!(writer)?;
1112 }
1113 }
1114
1115 write!(writer, "+")?;
1117 for width in &widths {
1118 write!(writer, "{}", "-".repeat(*width + 2))?;
1119 write!(writer, "+")?;
1120 }
1121 writeln!(writer)?;
1122
1123 Ok(())
1124}
1125
1126fn output_table<W: Write>(
1128 dataview: &DataView,
1129 writer: &mut W,
1130 max_col_width: Option<usize>,
1131 _col_sample_rows: usize, style: TableStyle,
1133) -> Result<()> {
1134 let mut table = Table::new();
1135
1136 match style {
1138 TableStyle::Default => {
1139 return output_table_old_style(dataview, writer, max_col_width);
1142 }
1143 TableStyle::AsciiFull => {
1144 table.load_preset(ASCII_FULL);
1145 }
1146 TableStyle::AsciiCondensed => {
1147 table.load_preset(ASCII_FULL_CONDENSED);
1148 }
1149 TableStyle::AsciiBordersOnly => {
1150 table.load_preset(ASCII_BORDERS_ONLY);
1151 }
1152 TableStyle::AsciiHorizontalOnly => {
1153 table.load_preset(ASCII_HORIZONTAL_ONLY);
1154 }
1155 TableStyle::AsciiNoBorders => {
1156 table.load_preset(ASCII_NO_BORDERS);
1157 }
1158 TableStyle::Markdown => {
1159 table.load_preset(ASCII_MARKDOWN);
1160 }
1161 TableStyle::Utf8Full => {
1162 table.load_preset(UTF8_FULL);
1163 }
1164 TableStyle::Utf8Condensed => {
1165 table.load_preset(UTF8_FULL_CONDENSED);
1166 }
1167 TableStyle::Utf8BordersOnly => {
1168 table.load_preset(UTF8_BORDERS_ONLY);
1169 }
1170 TableStyle::Utf8HorizontalOnly => {
1171 table.load_preset(UTF8_HORIZONTAL_ONLY);
1172 }
1173 TableStyle::Utf8NoBorders => {
1174 table.load_preset(UTF8_NO_BORDERS);
1175 }
1176 TableStyle::Plain => {
1177 table.load_preset(NOTHING);
1178 }
1179 }
1180
1181 if max_col_width.is_some() {
1183 table.set_content_arrangement(ContentArrangement::Dynamic);
1184 }
1185
1186 let columns = dataview.column_names();
1188 table.set_header(&columns);
1189
1190 for row_idx in 0..dataview.row_count() {
1192 if let Some(row) = dataview.get_row(row_idx) {
1193 let row_strings: Vec<String> = row
1194 .values
1195 .iter()
1196 .map(|v| {
1197 let s = format_value(v);
1198 if let Some(max_width) = max_col_width {
1200 if s.len() > max_width {
1201 format!("{}...", &s[..max_width.saturating_sub(3)])
1202 } else {
1203 s
1204 }
1205 } else {
1206 s
1207 }
1208 })
1209 .collect();
1210 table.add_row(row_strings);
1211 }
1212 }
1213
1214 writeln!(writer, "{}", table)?;
1216
1217 Ok(())
1218}
1219
1220fn format_value(value: &DataValue) -> String {
1222 match value {
1223 DataValue::Null => String::new(),
1224 DataValue::Integer(i) => i.to_string(),
1225 DataValue::Float(f) => f.to_string(),
1226 DataValue::String(s) => s.clone(),
1227 DataValue::InternedString(s) => s.to_string(),
1228 DataValue::Boolean(b) => b.to_string(),
1229 DataValue::DateTime(dt) => dt.to_string(),
1230 }
1231}
1232
1233fn value_to_json(value: &DataValue) -> serde_json::Value {
1235 match value {
1236 DataValue::Null => serde_json::Value::Null,
1237 DataValue::Integer(i) => serde_json::Value::Number((*i).into()),
1238 DataValue::Float(f) => {
1239 if let Some(n) = serde_json::Number::from_f64(*f) {
1240 serde_json::Value::Number(n)
1241 } else {
1242 serde_json::Value::Null
1243 }
1244 }
1245 DataValue::String(s) => serde_json::Value::String(s.clone()),
1246 DataValue::InternedString(s) => serde_json::Value::String(s.to_string()),
1247 DataValue::Boolean(b) => serde_json::Value::Bool(*b),
1248 DataValue::DateTime(dt) => serde_json::Value::String(dt.to_string()),
1249 }
1250}
1251
1252fn escape_csv_field(field: &str, delimiter: char) -> String {
1254 if field.contains(delimiter)
1255 || field.contains('"')
1256 || field.contains('\n')
1257 || field.contains('\r')
1258 {
1259 format!("\"{}\"", field.replace('"', "\"\""))
1260 } else {
1261 field.to_string()
1262 }
1263}