sql_cli/
non_interactive.rs

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};
14// Phase 1: TempTableRegistry no longer used directly - it's in ExecutionContext
15// use crate::data::temp_table_registry::TempTableRegistry;
16use crate::services::query_execution_service::QueryExecutionService;
17use crate::sql::parser::ast::{CTEType, TableSource, CTE};
18use crate::sql::recursive_parser::Parser;
19use crate::sql::script_parser::{ScriptParser, ScriptResult};
20use crate::utils::string_utils::display_width;
21
22/// Check if a query references temporary tables (starting with #)
23/// Temporary tables are only valid in script mode
24fn check_temp_table_usage(query: &str) -> Result<()> {
25    use crate::sql::recursive_parser::Parser;
26
27    let mut parser = Parser::new(query);
28    match parser.parse() {
29        Ok(stmt) => {
30            // Check FROM clause
31            if let Some(from_table) = &stmt.from_table {
32                if from_table.starts_with('#') {
33                    anyhow::bail!(
34                        "Temporary table '{}' cannot be used in single-query mode. \
35                        Temporary tables are only available in script mode (using -f flag with GO separators).",
36                        from_table
37                    );
38                }
39            }
40
41            // Check INTO clause
42            if let Some(into_table) = &stmt.into_table {
43                anyhow::bail!(
44                    "INTO clause for temporary table '{}' is only supported in script mode. \
45                    Use -f flag with GO separators to create temporary tables.",
46                    into_table.name
47                );
48            }
49
50            Ok(())
51        }
52        Err(_) => {
53            // If parse fails, let it fail later in the actual execution
54            Ok(())
55        }
56    }
57}
58
59/// Extract dependencies from a CTE by analyzing what tables it references
60fn extract_cte_dependencies(cte: &CTE) -> Vec<String> {
61    // For now, we'll just return the from_table if it exists
62    // This could be enhanced to do deeper analysis of the query AST
63    let mut deps = Vec::new();
64
65    if let CTEType::Standard(query) = &cte.cte_type {
66        if let Some(from_table) = &query.from_table {
67            deps.push(from_table.clone());
68        }
69
70        // Could also check joins, subqueries, etc.
71        for join in &query.joins {
72            match &join.table {
73                TableSource::Table(table_name) => {
74                    deps.push(table_name.clone());
75                }
76                TableSource::DerivedTable { alias, .. } => {
77                    deps.push(alias.clone());
78                }
79            }
80        }
81    }
82
83    deps
84}
85
86/// Output format for query results
87#[derive(Debug, Clone)]
88pub enum OutputFormat {
89    Csv,
90    Json,
91    JsonStructured, // Structured JSON with metadata for IDE/plugin integration
92    Table,
93    Tsv,
94}
95
96impl OutputFormat {
97    pub fn from_str(s: &str) -> Result<Self> {
98        match s.to_lowercase().as_str() {
99            "csv" => Ok(OutputFormat::Csv),
100            "json" => Ok(OutputFormat::Json),
101            "json-structured" => Ok(OutputFormat::JsonStructured),
102            "table" => Ok(OutputFormat::Table),
103            "tsv" => Ok(OutputFormat::Tsv),
104            _ => Err(anyhow::anyhow!(
105                "Invalid output format: {}. Use csv, json, json-structured, table, or tsv",
106                s
107            )),
108        }
109    }
110}
111
112/// Table styling presets for table output format
113#[derive(Debug, Clone, Copy)]
114pub enum TableStyle {
115    /// Current default ASCII style with borders
116    Default,
117    /// ASCII table with full borders
118    AsciiFull,
119    /// ASCII table with condensed rows
120    AsciiCondensed,
121    /// ASCII table with only outer borders
122    AsciiBordersOnly,
123    /// ASCII table with horizontal lines only
124    AsciiHorizontalOnly,
125    /// ASCII table with no borders
126    AsciiNoBorders,
127    /// Markdown-style table
128    Markdown,
129    /// UTF8 table with box-drawing characters
130    Utf8Full,
131    /// UTF8 table with condensed rows
132    Utf8Condensed,
133    /// UTF8 table with only outer borders
134    Utf8BordersOnly,
135    /// UTF8 table with horizontal lines only
136    Utf8HorizontalOnly,
137    /// UTF8 table with no borders
138    Utf8NoBorders,
139    /// No table formatting, just data
140    Plain,
141}
142
143impl TableStyle {
144    pub fn from_str(s: &str) -> Result<Self> {
145        match s.to_lowercase().as_str() {
146            "default" => Ok(TableStyle::Default),
147            "ascii" | "ascii-full" => Ok(TableStyle::AsciiFull),
148            "ascii-condensed" => Ok(TableStyle::AsciiCondensed),
149            "ascii-borders" | "ascii-borders-only" => Ok(TableStyle::AsciiBordersOnly),
150            "ascii-horizontal" | "ascii-horizontal-only" => Ok(TableStyle::AsciiHorizontalOnly),
151            "ascii-noborders" | "ascii-no-borders" => Ok(TableStyle::AsciiNoBorders),
152            "markdown" | "md" => Ok(TableStyle::Markdown),
153            "utf8" | "utf8-full" => Ok(TableStyle::Utf8Full),
154            "utf8-condensed" => Ok(TableStyle::Utf8Condensed),
155            "utf8-borders" | "utf8-borders-only" => Ok(TableStyle::Utf8BordersOnly),
156            "utf8-horizontal" | "utf8-horizontal-only" => Ok(TableStyle::Utf8HorizontalOnly),
157            "utf8-noborders" | "utf8-no-borders" => Ok(TableStyle::Utf8NoBorders),
158            "plain" | "none" => Ok(TableStyle::Plain),
159            _ => Err(anyhow::anyhow!(
160                "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",
161                s
162            )),
163        }
164    }
165
166    pub fn list_styles() -> &'static str {
167        "Available table styles:
168  default            - Current default ASCII style
169  ascii              - ASCII with full borders
170  ascii-condensed    - ASCII with condensed rows
171  ascii-borders      - ASCII with outer borders only
172  ascii-horizontal   - ASCII with horizontal lines only
173  ascii-noborders    - ASCII with no borders
174  markdown           - GitHub-flavored Markdown table
175  utf8               - UTF8 box-drawing characters
176  utf8-condensed     - UTF8 with condensed rows
177  utf8-borders       - UTF8 with outer borders only
178  utf8-horizontal    - UTF8 with horizontal lines only
179  utf8-noborders     - UTF8 with no borders
180  plain              - No formatting, data only"
181    }
182}
183
184/// Configuration for non-interactive query execution
185pub struct NonInteractiveConfig {
186    pub data_file: String,
187    pub query: String,
188    pub output_format: OutputFormat,
189    pub output_file: Option<String>,
190    pub case_insensitive: bool,
191    pub auto_hide_empty: bool,
192    pub limit: Option<usize>,
193    pub query_plan: bool,
194    pub show_work_units: bool,
195    pub execution_plan: bool,
196    pub show_preprocessing: bool,
197    pub show_transformations: bool,
198    pub cte_info: bool,
199    pub rewrite_analysis: bool,
200    pub lift_in_expressions: bool,
201    pub script_file: Option<String>, // Path to the script file for relative path resolution
202    pub debug_trace: bool,
203    pub max_col_width: Option<usize>, // Maximum column width for table output (None = unlimited)
204    pub col_sample_rows: usize,       // Number of rows to sample for column width (0 = all rows)
205    pub table_style: TableStyle,      // Table styling preset (only affects table output format)
206    pub styled: bool,                 // Whether to apply color styling rules
207    pub style_file: Option<String>,   // Path to YAML style configuration file
208    pub no_where_expansion: bool,     // Disable WHERE clause alias expansion
209    pub no_group_by_expansion: bool,  // Disable GROUP BY clause alias expansion
210    pub no_having_expansion: bool,    // Disable HAVING clause auto-aliasing
211    pub no_order_by_expansion: bool,  // Disable ORDER BY aggregate expansion
212    pub no_qualify_to_where: bool,    // Disable QUALIFY to WHERE transformation
213    pub no_expression_lifter: bool,   // Disable expression lifting transformer
214    pub no_cte_hoister: bool,         // Disable CTE hoisting transformer
215    pub no_in_lifter: bool,           // Disable IN operator lifting transformer
216}
217
218/// Convert NonInteractiveConfig flags to TransformerConfig
219fn make_transformer_config(config: &NonInteractiveConfig) -> crate::query_plan::TransformerConfig {
220    crate::query_plan::TransformerConfig {
221        enable_expression_lifter: !config.no_expression_lifter,
222        enable_where_expansion: !config.no_where_expansion,
223        enable_group_by_expansion: !config.no_group_by_expansion,
224        enable_having_expansion: !config.no_having_expansion,
225        enable_order_by_expansion: !config.no_order_by_expansion,
226        enable_qualify_to_where: !config.no_qualify_to_where,
227        enable_cte_hoister: !config.no_cte_hoister,
228        enable_in_lifter: !config.no_in_lifter,
229    }
230}
231
232/// Execute a query in non-interactive mode
233pub fn execute_non_interactive(config: NonInteractiveConfig) -> Result<()> {
234    let start_time = Instant::now();
235
236    // Phase 2: REMOVED temp table blocking - temp tables now work in single query mode!
237    // check_temp_table_usage(&config.query)?;
238
239    // Phase 3.3: Check for data file hint in query if no data file specified
240    let data_file_to_use = if config.data_file.is_empty() {
241        // Look for -- #! hint in first 10 lines
242        let lines: Vec<&str> = config.query.lines().take(10).collect();
243        let mut hint_path: Option<String> = None;
244
245        for line in lines {
246            let trimmed = line.trim();
247            if trimmed.starts_with("-- #!") {
248                hint_path = Some(trimmed[5..].trim().to_string());
249                break;
250            }
251        }
252
253        if let Some(path) = hint_path {
254            debug!("Found data file hint: {}", path);
255            // Resolve relative paths
256            let resolved_path = if path.starts_with("../") {
257                // Relative to current directory
258                std::path::Path::new(&path).to_path_buf()
259            } else if path.starts_with("data/") {
260                // Relative to project root (assume current dir)
261                std::path::Path::new(&path).to_path_buf()
262            } else {
263                std::path::Path::new(&path).to_path_buf()
264            };
265
266            if resolved_path.exists() {
267                info!("Using data file from hint: {:?}", resolved_path);
268                resolved_path.to_string_lossy().to_string()
269            } else {
270                debug!("Data file hint path does not exist: {:?}", resolved_path);
271                String::new()
272            }
273        } else {
274            String::new()
275        }
276    } else {
277        config.data_file.clone()
278    };
279
280    // Phase 2: Load data file and create unified execution context
281    let (data_table, _is_dual) = if data_file_to_use.is_empty() {
282        info!("No data file provided, using DUAL table");
283        (crate::data::datatable::DataTable::dual(), true)
284    } else {
285        info!("Loading data from: {}", data_file_to_use);
286        let table = load_data_file(&data_file_to_use)?;
287        info!(
288            "Loaded {} rows with {} columns",
289            table.row_count(),
290            table.column_count()
291        );
292        (table, false)
293    };
294    let _table_name = data_table.name.clone();
295
296    // Phase 2: Create unified execution context (replaces standalone DataView)
297    use crate::execution::{ExecutionConfig, ExecutionContext, StatementExecutor};
298    let mut context = ExecutionContext::new(std::sync::Arc::new(data_table));
299
300    // Keep DataView for backward compatibility with special flags
301    let dataview = DataView::new(context.source_table.clone());
302
303    // 3. Execute the query
304    info!("Executing query: {}", config.query);
305
306    // If execution_plan is requested, show detailed execution information
307    if config.execution_plan {
308        println!("\n=== EXECUTION PLAN ===");
309        println!("Query: {}", config.query);
310        println!("\nExecution Steps:");
311        println!("1. PARSE - Parse SQL query");
312        println!("2. LOAD_DATA - Load data from {}", &config.data_file);
313        println!(
314            "   • Loaded {} rows, {} columns",
315            dataview.row_count(),
316            dataview.column_count()
317        );
318    }
319
320    // If show_work_units is requested, analyze and display work units
321    if config.show_work_units {
322        use crate::query_plan::{ExpressionLifter, QueryAnalyzer};
323        use crate::sql::recursive_parser::Parser;
324
325        let mut parser = Parser::new(&config.query);
326        match parser.parse() {
327            Ok(stmt) => {
328                let mut analyzer = QueryAnalyzer::new();
329                let mut lifter = ExpressionLifter::new();
330
331                // Check if the query has liftable expressions
332                let mut stmt_copy = stmt.clone();
333                let lifted = lifter.lift_expressions(&mut stmt_copy);
334
335                // Build the query plan
336                match analyzer.analyze(&stmt_copy, config.query.clone()) {
337                    Ok(plan) => {
338                        println!("\n{}", plan.explain());
339
340                        if !lifted.is_empty() {
341                            println!("\nLifted CTEs:");
342                            for cte in &lifted {
343                                println!("  - {}", cte.name);
344                            }
345                        }
346
347                        return Ok(());
348                    }
349                    Err(e) => {
350                        eprintln!("Error analyzing query: {}", e);
351                        return Err(anyhow::anyhow!("Query analysis failed: {}", e));
352                    }
353                }
354            }
355            Err(e) => {
356                eprintln!("Error parsing query: {}", e);
357                return Err(anyhow::anyhow!("Parse error: {}", e));
358            }
359        }
360    }
361
362    // If query_plan is requested, parse and display the AST
363    if config.query_plan {
364        use crate::sql::recursive_parser::Parser;
365        let mut parser = Parser::new(&config.query);
366        match parser.parse() {
367            Ok(statement) => {
368                println!("\n=== QUERY PLAN (AST) ===");
369                println!("{statement:#?}");
370                println!("=== END QUERY PLAN ===\n");
371            }
372            Err(e) => {
373                eprintln!("Failed to parse query for plan: {e}");
374            }
375        }
376    }
377
378    // If rewrite analysis is requested, analyze query for optimization opportunities
379    if config.rewrite_analysis {
380        use crate::sql::query_rewriter::{QueryRewriter, RewriteAnalysis};
381        use crate::sql::recursive_parser::Parser;
382        use serde_json::json;
383
384        let mut parser = Parser::new(&config.query);
385        match parser.parse() {
386            Ok(statement) => {
387                let mut rewriter = QueryRewriter::new();
388                let suggestions = rewriter.analyze(&statement);
389
390                let analysis = RewriteAnalysis::from_suggestions(suggestions);
391                println!("{}", serde_json::to_string_pretty(&analysis).unwrap());
392                return Ok(());
393            }
394            Err(e) => {
395                let output = json!({
396                    "success": false,
397                    "error": format!("{}", e),
398                    "suggestions": [],
399                    "can_auto_rewrite": false,
400                });
401                println!("{}", serde_json::to_string_pretty(&output).unwrap());
402                return Ok(());
403            }
404        }
405    }
406
407    // If CTE info is requested, parse and output CTE information as JSON
408    if config.cte_info {
409        use crate::sql::recursive_parser::Parser;
410        use serde_json::json;
411
412        let mut parser = Parser::new(&config.query);
413        match parser.parse() {
414            Ok(statement) => {
415                let mut cte_info = Vec::new();
416
417                // Extract CTE information
418                for (index, cte) in statement.ctes.iter().enumerate() {
419                    let cte_json = json!({
420                        "index": index,
421                        "name": cte.name,
422                        "columns": cte.column_list,
423                        // We can't easily get line numbers from the AST,
424                        // but we can provide the structure
425                        "dependencies": extract_cte_dependencies(cte),
426                    });
427                    cte_info.push(cte_json);
428                }
429
430                let output = json!({
431                    "success": true,
432                    "ctes": cte_info,
433                    "total": statement.ctes.len(),
434                    "has_final_select": !statement.columns.is_empty() || !statement.select_items.is_empty(),
435                });
436
437                println!("{}", serde_json::to_string_pretty(&output).unwrap());
438                return Ok(());
439            }
440            Err(e) => {
441                let output = json!({
442                    "success": false,
443                    "error": format!("{}", e),
444                    "ctes": [],
445                    "total": 0,
446                });
447                println!("{}", serde_json::to_string_pretty(&output).unwrap());
448                return Ok(());
449            }
450        }
451    }
452
453    let query_start = Instant::now();
454
455    // Load configuration file to get date notation and other settings
456    let app_config = Config::load().unwrap_or_else(|e| {
457        debug!("Could not load config file: {}. Using defaults.", e);
458        Config::default()
459    });
460
461    // Initialize global config for function registry
462    crate::config::global::init_config(app_config.clone());
463
464    // Phase 2: Create unified execution config from CLI flags
465    let exec_config = ExecutionConfig::from_cli_flags(
466        config.show_preprocessing,
467        config.show_transformations,
468        config.case_insensitive,
469        config.auto_hide_empty,
470        config.no_expression_lifter,
471        config.no_where_expansion,
472        config.no_group_by_expansion,
473        config.no_having_expansion,
474        config.no_order_by_expansion,
475        config.no_qualify_to_where,
476        config.no_cte_hoister,
477        config.no_in_lifter,
478        config.debug_trace,
479    );
480
481    // Phase 2: Create unified statement executor
482    let executor = StatementExecutor::with_config(exec_config);
483
484    // Phase 2: Parse and execute using unified infrastructure
485    let exec_start = Instant::now();
486    let result = {
487        use crate::sql::recursive_parser::Parser;
488
489        // Phase 3.1: Expand templates BEFORE parsing (same as script mode)
490        use crate::sql::template_expander::TemplateExpander;
491        let expander = TemplateExpander::new(&context.temp_tables);
492
493        let query_to_parse = match expander.parse_templates(&config.query) {
494            Ok(vars) => {
495                if vars.is_empty() {
496                    config.query.clone()
497                } else {
498                    match expander.expand(&config.query, &vars) {
499                        Ok(expanded) => {
500                            debug!(
501                                "Template expansion in single query mode: {} vars expanded",
502                                vars.len()
503                            );
504                            for var in &vars {
505                                debug!("  {} -> resolved", var.placeholder);
506                            }
507                            expanded
508                        }
509                        Err(e) => {
510                            return Err(anyhow::anyhow!(
511                                "Template expansion failed: {}. Available tables: {}",
512                                e,
513                                context.temp_tables.list_tables().join(", ")
514                            ));
515                        }
516                    }
517                }
518            }
519            Err(e) => {
520                return Err(anyhow::anyhow!("Template parsing failed: {}", e));
521            }
522        };
523
524        let mut parser = Parser::new(&query_to_parse);
525        match parser.parse() {
526            Ok(stmt) => {
527                // Phase 2: Execute using unified StatementExecutor
528                // This handles:
529                // - Table resolution (base, temp tables, DUAL)
530                // - Preprocessing pipeline (all transformers)
531                // - Direct AST execution (no re-parsing!)
532                match executor.execute(stmt, &mut context) {
533                    Ok(exec_result) => {
534                        // Convert to QueryExecutionResult for compatibility
535                        Ok(
536                            crate::services::query_execution_service::QueryExecutionResult {
537                                dataview: exec_result.dataview,
538                                stats: crate::services::query_execution_service::QueryStats {
539                                    row_count: exec_result.stats.row_count,
540                                    column_count: exec_result.stats.column_count,
541                                    execution_time: exec_start.elapsed(),
542                                    query_engine_time: exec_start.elapsed(),
543                                },
544                                hidden_columns: Vec::new(),
545                                query: config.query.clone(),
546                                execution_plan: None,
547                                debug_trace: None,
548                            },
549                        )
550                    }
551                    Err(e) => Err(e),
552                }
553            }
554            Err(e) => {
555                // Parse failed, return error
556                Err(anyhow::anyhow!("Parse error: {}", e))
557            }
558        }
559    }?;
560    let exec_time = exec_start.elapsed();
561
562    let query_time = query_start.elapsed();
563    info!("Query executed in {:?}", query_time);
564    info!(
565        "Result: {} rows, {} columns",
566        result.dataview.row_count(),
567        result.dataview.column_count()
568    );
569
570    // Show execution plan details if requested
571    if config.execution_plan {
572        // Try to get detailed execution plan
573        use crate::data::query_engine::QueryEngine;
574
575        let query_engine = QueryEngine::new();
576
577        match query_engine.execute_with_plan(
578            std::sync::Arc::new(dataview.source().clone()),
579            &config.query,
580        ) {
581            Ok((_view, plan)) => {
582                // Display the detailed execution plan tree
583                print!("{}", plan.format_tree());
584            }
585            Err(e) => {
586                // Fall back to simple execution plan display
587                eprintln!("Could not generate detailed execution plan: {}", e);
588                println!(
589                    "3. QUERY_EXECUTION [{:.3}ms]",
590                    exec_time.as_secs_f64() * 1000.0
591                );
592
593                // Parse query to understand what operations are being performed
594                use crate::sql::recursive_parser::Parser;
595                let mut parser = Parser::new(&config.query);
596                if let Ok(stmt) = parser.parse() {
597                    if stmt.where_clause.is_some() {
598                        println!("   • WHERE clause filtering applied");
599                        println!("   • Rows after filter: {}", result.dataview.row_count());
600                    }
601
602                    if let Some(ref order_by) = stmt.order_by {
603                        println!("   • ORDER BY: {} column(s)", order_by.len());
604                    }
605
606                    if let Some(ref group_by) = stmt.group_by {
607                        println!("   • GROUP BY: {} column(s)", group_by.len());
608                    }
609
610                    if let Some(limit) = stmt.limit {
611                        println!("   • LIMIT: {} rows", limit);
612                    }
613
614                    if stmt.distinct {
615                        println!("   • DISTINCT applied");
616                    }
617                }
618            }
619        }
620
621        println!("\nExecution Statistics:");
622        println!(
623            "  Preparation:    {:.3}ms",
624            (exec_start - start_time).as_secs_f64() * 1000.0
625        );
626        println!(
627            "  Query time:     {:.3}ms",
628            exec_time.as_secs_f64() * 1000.0
629        );
630        println!(
631            "  Total time:     {:.3}ms",
632            query_time.as_secs_f64() * 1000.0
633        );
634        println!("  Rows returned:  {}", result.dataview.row_count());
635        println!("  Columns:        {}", result.dataview.column_count());
636        println!("\n=== END EXECUTION PLAN ===");
637        println!();
638    }
639
640    // 4. Apply limit if specified
641    let final_view = if let Some(limit) = config.limit {
642        let limited_table = limit_results(&result.dataview, limit)?;
643        DataView::new(std::sync::Arc::new(limited_table))
644    } else {
645        result.dataview
646    };
647
648    // 5. Output debug trace if enabled
649    if let Some(ref trace_output) = result.debug_trace {
650        eprintln!("{}", trace_output);
651    }
652
653    // 6. Output the results
654    let exec_time_ms = exec_time.as_secs_f64() * 1000.0;
655    let output_result = if let Some(ref path) = config.output_file {
656        let mut file = fs::File::create(path)
657            .with_context(|| format!("Failed to create output file: {path}"))?;
658        output_results(
659            &final_view,
660            config.output_format,
661            &mut file,
662            config.max_col_width,
663            config.col_sample_rows,
664            exec_time_ms,
665            config.table_style,
666            config.styled,
667            config.style_file.as_deref(),
668        )?;
669        info!("Results written to: {}", path);
670        Ok(())
671    } else {
672        output_results(
673            &final_view,
674            config.output_format,
675            &mut io::stdout(),
676            config.max_col_width,
677            config.col_sample_rows,
678            exec_time_ms,
679            config.table_style,
680            config.styled,
681            config.style_file.as_deref(),
682        )?;
683        Ok(())
684    };
685
686    let total_time = start_time.elapsed();
687    debug!("Total execution time: {:?}", total_time);
688
689    // Print stats to stderr so they don't interfere with output
690    if config.output_file.is_none() {
691        eprintln!(
692            "\n# Query completed: {} rows in {:?}",
693            final_view.row_count(),
694            query_time
695        );
696    }
697
698    output_result
699}
700
701/// Execute a script file with multiple SQL statements separated by GO
702pub fn execute_script(config: NonInteractiveConfig) -> Result<()> {
703    let _start_time = Instant::now();
704
705    // Parse the script into individual statements with directives
706    let parser = ScriptParser::new(&config.query);
707    let script_statements = parser.parse_script_statements();
708
709    if script_statements.is_empty() {
710        anyhow::bail!("No statements found in script");
711    }
712
713    info!("Found {} statements in script", script_statements.len());
714
715    // Determine data file to use (command-line overrides script hint)
716    let data_file = if !config.data_file.is_empty() {
717        // Command-line argument takes precedence
718        config.data_file.clone()
719    } else if let Some(hint) = parser.data_file_hint() {
720        // Use data file hint from script
721        info!("Using data file from script hint: {}", hint);
722
723        // Resolve relative paths relative to script file if provided
724        if let Some(script_path) = config.script_file.as_ref() {
725            let script_dir = std::path::Path::new(script_path)
726                .parent()
727                .unwrap_or(std::path::Path::new("."));
728            let hint_path = std::path::Path::new(hint);
729
730            if hint_path.is_relative() {
731                script_dir.join(hint_path).to_string_lossy().to_string()
732            } else {
733                hint.to_string()
734            }
735        } else {
736            hint.to_string()
737        }
738    } else {
739        String::new()
740    };
741
742    // Load the data file if provided, otherwise use DUAL
743    let (data_table, _is_dual) = if data_file.is_empty() {
744        // No data file provided, use DUAL table
745        info!("No data file provided, using DUAL table");
746        (DataTable::dual(), true)
747    } else {
748        // Check if file exists before trying to load
749        if !std::path::Path::new(&data_file).exists() {
750            anyhow::bail!(
751                "Data file not found: {}\n\
752                Please check the path is correct",
753                data_file
754            );
755        }
756
757        info!("Loading data from: {}", data_file);
758        let table = load_data_file(&data_file)?;
759        info!(
760            "Loaded {} rows with {} columns",
761            table.row_count(),
762            table.column_count()
763        );
764        (table, false)
765    };
766
767    // Track script results
768    let mut script_result = ScriptResult::new();
769    let mut output = Vec::new();
770
771    // Phase 1: Create unified execution context (replaces TempTableRegistry)
772    use crate::execution::{ExecutionConfig, ExecutionContext, StatementExecutor};
773    let mut context = ExecutionContext::new(std::sync::Arc::new(data_table));
774
775    // Phase 1: Create unified execution config from CLI flags
776    let exec_config = ExecutionConfig::from_cli_flags(
777        config.show_preprocessing,
778        config.show_transformations,
779        config.case_insensitive,
780        config.auto_hide_empty,
781        config.no_expression_lifter,
782        config.no_where_expansion,
783        config.no_group_by_expansion,
784        config.no_having_expansion,
785        config.no_order_by_expansion,
786        config.no_qualify_to_where,
787        config.no_cte_hoister,
788        config.no_in_lifter,
789        false, // debug_trace - not used in script mode
790    );
791
792    // Phase 1: Create unified statement executor
793    let executor = StatementExecutor::with_config(exec_config);
794
795    // Execute each statement
796    for (idx, script_stmt) in script_statements.iter().enumerate() {
797        let statement_num = idx + 1;
798        let stmt_start = Instant::now();
799
800        // Check if this is an EXIT statement
801        if script_stmt.is_exit() {
802            let exit_code = script_stmt.get_exit_code().unwrap_or(0);
803            info!("EXIT statement encountered (code: {})", exit_code);
804
805            // Print message for table format
806            if matches!(config.output_format, OutputFormat::Table) {
807                if idx > 0 {
808                    output.push(String::new());
809                }
810                output.push(format!("-- Statement {} --", statement_num));
811                output.push(format!("Script execution stopped by EXIT {}", exit_code));
812            }
813
814            // Mark as success in results
815            script_result.add_success(
816                statement_num,
817                format!("EXIT {}", exit_code),
818                0,
819                stmt_start.elapsed().as_secs_f64() * 1000.0,
820            );
821
822            // Stop execution
823            break;
824        }
825
826        // Check if this statement should be skipped
827        if script_stmt.should_skip() {
828            info!(
829                "Skipping statement {} due to [SKIP] directive",
830                statement_num
831            );
832
833            // Print message for table format
834            if matches!(config.output_format, OutputFormat::Table) {
835                if idx > 0 {
836                    output.push(String::new());
837                }
838                output.push(format!("-- Statement {} [SKIPPED] --", statement_num));
839            }
840
841            // Mark as success but with 0 rows
842            script_result.add_success(
843                statement_num,
844                "[SKIPPED]".to_string(),
845                0,
846                stmt_start.elapsed().as_secs_f64() * 1000.0,
847            );
848
849            continue;
850        }
851
852        // Get the SQL query
853        let statement = match script_stmt.get_query() {
854            Some(sql) => sql,
855            None => continue, // Should not happen
856        };
857
858        // Print separator for table format
859        if matches!(config.output_format, OutputFormat::Table) {
860            if idx > 0 {
861                output.push(String::new()); // Empty line between queries
862            }
863            output.push(format!("-- Query {} --", statement_num));
864        }
865
866        // Step 1: Expand templates in the SQL string FIRST (before parsing)
867        // Phase 1: Use context.temp_tables instead of temp_tables directly
868        use crate::sql::template_expander::TemplateExpander;
869        let expander = TemplateExpander::new(&context.temp_tables);
870
871        let expanded_statement = match expander.parse_templates(statement) {
872            Ok(vars) => {
873                if vars.is_empty() {
874                    statement.to_string()
875                } else {
876                    match expander.expand(statement, &vars) {
877                        Ok(expanded) => {
878                            debug!("Expanded templates in SQL: {} vars found", vars.len());
879                            for var in &vars {
880                                debug!(
881                                    "  {} -> expanding from {}",
882                                    var.placeholder, var.table_name
883                                );
884                            }
885                            expanded
886                        }
887                        Err(e) => {
888                            script_result.add_failure(
889                                statement_num,
890                                statement.to_string(),
891                                format!("Template expansion error: {}", e),
892                                stmt_start.elapsed().as_secs_f64() * 1000.0,
893                            );
894                            continue; // Skip this statement
895                        }
896                    }
897                }
898            }
899            Err(e) => {
900                script_result.add_failure(
901                    statement_num,
902                    statement.to_string(),
903                    format!("Template parse error: {}", e),
904                    stmt_start.elapsed().as_secs_f64() * 1000.0,
905                );
906                continue; // Skip this statement
907            }
908        };
909
910        // Use the expanded statement for the rest of the processing
911        let statement = expanded_statement.as_str();
912
913        // Step 2: Parse the (possibly expanded) statement
914        let mut parser = Parser::new(statement);
915        let parsed_stmt = match parser.parse() {
916            Ok(stmt) => stmt,
917            Err(e) => {
918                // If parsing fails, record error and stop (scripts stop on first error)
919                script_result.add_failure(
920                    statement_num,
921                    statement.to_string(),
922                    format!("Parse error: {}", e),
923                    stmt_start.elapsed().as_secs_f64() * 1000.0,
924                );
925                break;
926            }
927        };
928
929        // Phase 1: Check if temp table is referenced (for better error message)
930        // The executor will handle resolution, but we check here for early validation
931        if let Some(from_table) = &parsed_stmt.from_table {
932            if from_table.starts_with('#') && !context.has_temp_table(from_table) {
933                script_result.add_failure(
934                    statement_num,
935                    statement.to_string(),
936                    format!("Temporary table {} not found", from_table),
937                    stmt_start.elapsed().as_secs_f64() * 1000.0,
938                );
939                break;
940            }
941        }
942
943        // Phase 1: Remove INTO clause before execution (executor doesn't handle INTO syntax)
944        // We'll handle it ourselves after execution
945        let into_table = parsed_stmt.into_table.clone();
946        let stmt_without_into = if into_table.is_some() {
947            use crate::query_plan::IntoClauseRemover;
948            IntoClauseRemover::remove_into_clause(parsed_stmt)
949        } else {
950            parsed_stmt
951        };
952
953        // Phase 1: Execute using unified StatementExecutor
954        // This handles:
955        // - Table resolution (temp tables, base table, DUAL)
956        // - Preprocessing pipeline (alias expansion, transformers)
957        // - Direct AST execution (no re-parsing!)
958        let result = executor.execute(stmt_without_into, &mut context);
959
960        match result {
961            Ok(exec_result) => {
962                let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
963                let final_view = exec_result.dataview;
964
965                // Phase 1: Check if this is an INTO statement - store result in temp table
966                if let Some(into_table) = &into_table {
967                    // Get the source table from the DataView - this contains the query result
968                    let result_table = final_view.source_arc();
969                    let row_count = result_table.row_count();
970
971                    // Phase 1: Use context.store_temp_table() instead of temp_tables.insert()
972                    match context.store_temp_table(into_table.name.clone(), result_table) {
973                        Ok(_) => {
974                            info!(
975                                "Stored {} rows in temporary table {}",
976                                row_count, into_table.name
977                            );
978
979                            // For INTO statements, output a confirmation message only for table format
980                            // CSV/TSV/JSON formats should not include this message as it pollutes machine-readable output
981                            if matches!(config.output_format, OutputFormat::Table) {
982                                let mut statement_output = Vec::new();
983                                writeln!(
984                                    &mut statement_output,
985                                    "({} rows affected) -> {}",
986                                    row_count, into_table.name
987                                )?;
988                                output.extend(
989                                    String::from_utf8_lossy(&statement_output)
990                                        .lines()
991                                        .map(String::from),
992                                );
993                            }
994
995                            script_result.add_success(
996                                statement_num,
997                                statement.to_string(),
998                                row_count,
999                                exec_time,
1000                            );
1001                            continue; // Skip normal output formatting for INTO statements
1002                        }
1003                        Err(e) => {
1004                            script_result.add_failure(
1005                                statement_num,
1006                                statement.to_string(),
1007                                e.to_string(),
1008                                exec_time,
1009                            );
1010                            break;
1011                        }
1012                    }
1013                }
1014
1015                // Format the output based on the output format (normal query without INTO)
1016                let mut statement_output = Vec::new();
1017                match config.output_format {
1018                    OutputFormat::Csv => {
1019                        output_csv(&final_view, &mut statement_output, ',')?;
1020                    }
1021                    OutputFormat::Json => {
1022                        output_json(&final_view, &mut statement_output)?;
1023                    }
1024                    OutputFormat::JsonStructured => {
1025                        output_json_structured(&final_view, &mut statement_output, exec_time)?;
1026                    }
1027                    OutputFormat::Table => {
1028                        output_table(
1029                            &final_view,
1030                            &mut statement_output,
1031                            config.max_col_width,
1032                            config.col_sample_rows,
1033                            config.table_style,
1034                            config.styled,
1035                            config.style_file.as_deref(),
1036                        )?;
1037                        writeln!(
1038                            &mut statement_output,
1039                            "Query completed: {} rows in {:.2}ms",
1040                            final_view.row_count(),
1041                            exec_time
1042                        )?;
1043                    }
1044                    OutputFormat::Tsv => {
1045                        output_csv(&final_view, &mut statement_output, '\t')?;
1046                    }
1047                }
1048
1049                // Add to overall output
1050                output.extend(
1051                    String::from_utf8_lossy(&statement_output)
1052                        .lines()
1053                        .map(String::from),
1054                );
1055
1056                script_result.add_success(
1057                    statement_num,
1058                    statement.to_string(),
1059                    final_view.row_count(),
1060                    exec_time,
1061                );
1062            }
1063            Err(e) => {
1064                let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
1065                let error_msg = format!("Query {} failed: {}", statement_num, e);
1066
1067                if matches!(config.output_format, OutputFormat::Table) {
1068                    output.push(error_msg.clone());
1069                }
1070
1071                script_result.add_failure(
1072                    statement_num,
1073                    statement.to_string(),
1074                    e.to_string(),
1075                    exec_time,
1076                );
1077
1078                // Continue to next statement (don't stop on error)
1079            }
1080        }
1081    }
1082
1083    // Write output
1084    if let Some(ref output_file) = config.output_file {
1085        let mut file = fs::File::create(output_file)?;
1086        for line in &output {
1087            writeln!(file, "{}", line)?;
1088        }
1089        info!("Results written to: {}", output_file);
1090    } else {
1091        for line in &output {
1092            println!("{}", line);
1093        }
1094    }
1095
1096    // Print summary if in table mode
1097    if matches!(config.output_format, OutputFormat::Table) {
1098        println!("\n=== Script Summary ===");
1099        println!("Total statements: {}", script_result.total_statements);
1100        println!("Successful: {}", script_result.successful_statements);
1101        println!("Failed: {}", script_result.failed_statements);
1102        println!(
1103            "Total execution time: {:.2}ms",
1104            script_result.total_execution_time_ms
1105        );
1106    }
1107
1108    if !script_result.all_successful() {
1109        return Err(anyhow::anyhow!(
1110            "{} of {} statements failed",
1111            script_result.failed_statements,
1112            script_result.total_statements
1113        ));
1114    }
1115
1116    Ok(())
1117}
1118
1119/// Load a data file (CSV or JSON) into a `DataTable`
1120fn load_data_file(path: &str) -> Result<DataTable> {
1121    let path = Path::new(path);
1122
1123    if !path.exists() {
1124        return Err(anyhow::anyhow!("File not found: {}", path.display()));
1125    }
1126
1127    // Determine file type by extension
1128    let extension = path
1129        .extension()
1130        .and_then(|ext| ext.to_str())
1131        .map(str::to_lowercase)
1132        .unwrap_or_default();
1133
1134    let table_name = path
1135        .file_stem()
1136        .and_then(|stem| stem.to_str())
1137        .unwrap_or("data")
1138        .to_string();
1139
1140    match extension.as_str() {
1141        "csv" => load_csv_to_datatable(path, &table_name)
1142            .with_context(|| format!("Failed to load CSV file: {}", path.display())),
1143        "json" => load_json_to_datatable(path, &table_name)
1144            .with_context(|| format!("Failed to load JSON file: {}", path.display())),
1145        _ => Err(anyhow::anyhow!(
1146            "Unsupported file type: {}. Use .csv or .json",
1147            extension
1148        )),
1149    }
1150}
1151
1152/// Limit the number of rows in results
1153fn limit_results(dataview: &DataView, limit: usize) -> Result<DataTable> {
1154    let source = dataview.source();
1155    let mut limited_table = DataTable::new(&source.name);
1156
1157    // Copy columns
1158    for col in &source.columns {
1159        limited_table.add_column(col.clone());
1160    }
1161
1162    // Copy limited rows
1163    let rows_to_copy = dataview.row_count().min(limit);
1164    for i in 0..rows_to_copy {
1165        if let Some(row) = dataview.get_row(i) {
1166            let _ = limited_table.add_row(row.clone());
1167        }
1168    }
1169
1170    Ok(limited_table)
1171}
1172
1173/// Output query results in the specified format
1174fn output_results<W: Write>(
1175    dataview: &DataView,
1176    format: OutputFormat,
1177    writer: &mut W,
1178    max_col_width: Option<usize>,
1179    col_sample_rows: usize,
1180    exec_time_ms: f64,
1181    table_style: TableStyle,
1182    styled: bool,
1183    style_file: Option<&str>,
1184) -> Result<()> {
1185    match format {
1186        OutputFormat::Csv => output_csv(dataview, writer, ','),
1187        OutputFormat::Tsv => output_csv(dataview, writer, '\t'),
1188        OutputFormat::Json => output_json(dataview, writer),
1189        OutputFormat::JsonStructured => output_json_structured(dataview, writer, exec_time_ms),
1190        OutputFormat::Table => output_table(
1191            dataview,
1192            writer,
1193            max_col_width,
1194            col_sample_rows,
1195            table_style,
1196            styled,
1197            style_file,
1198        ),
1199    }
1200}
1201
1202/// Output results as CSV/TSV
1203fn output_csv<W: Write>(dataview: &DataView, writer: &mut W, delimiter: char) -> Result<()> {
1204    // Write headers
1205    let columns = dataview.column_names();
1206    for (i, col) in columns.iter().enumerate() {
1207        if i > 0 {
1208            write!(writer, "{delimiter}")?;
1209        }
1210        write!(writer, "{}", escape_csv_field(col, delimiter))?;
1211    }
1212    writeln!(writer)?;
1213
1214    // Write rows
1215    for row_idx in 0..dataview.row_count() {
1216        if let Some(row) = dataview.get_row(row_idx) {
1217            for (i, value) in row.values.iter().enumerate() {
1218                if i > 0 {
1219                    write!(writer, "{delimiter}")?;
1220                }
1221                write!(
1222                    writer,
1223                    "{}",
1224                    escape_csv_field(&format_value(value), delimiter)
1225                )?;
1226            }
1227            writeln!(writer)?;
1228        }
1229    }
1230
1231    Ok(())
1232}
1233
1234/// Output results as JSON
1235fn output_json<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
1236    let columns = dataview.column_names();
1237    let mut rows = Vec::new();
1238
1239    for row_idx in 0..dataview.row_count() {
1240        if let Some(row) = dataview.get_row(row_idx) {
1241            let mut json_row = serde_json::Map::new();
1242            for (col_idx, value) in row.values.iter().enumerate() {
1243                if col_idx < columns.len() {
1244                    json_row.insert(columns[col_idx].clone(), value_to_json(value));
1245                }
1246            }
1247            rows.push(serde_json::Value::Object(json_row));
1248        }
1249    }
1250
1251    let json = serde_json::to_string_pretty(&rows)?;
1252    writeln!(writer, "{json}")?;
1253
1254    Ok(())
1255}
1256
1257/// Output results as structured JSON with metadata for IDE/plugin integration
1258fn output_json_structured<W: Write>(
1259    dataview: &DataView,
1260    writer: &mut W,
1261    exec_time: f64,
1262) -> Result<()> {
1263    let column_names = dataview.column_names();
1264    let data_table = dataview.source();
1265
1266    // Build column metadata
1267    let mut columns = Vec::new();
1268    for (idx, name) in column_names.iter().enumerate() {
1269        let col_type = data_table
1270            .columns
1271            .get(idx)
1272            .map(|c| format!("{:?}", c.data_type))
1273            .unwrap_or_else(|| "UNKNOWN".to_string());
1274
1275        // Calculate max width for this column
1276        let mut max_width = name.len();
1277        for row_idx in 0..dataview.row_count() {
1278            if let Some(row) = dataview.get_row(row_idx) {
1279                if let Some(value) = row.values.get(idx) {
1280                    let display_width = match value {
1281                        DataValue::Null => 4, // "NULL"
1282                        DataValue::Integer(i) => i.to_string().len(),
1283                        DataValue::Float(f) => format!("{:.2}", f).len(),
1284                        DataValue::String(s) => s.len(),
1285                        DataValue::InternedString(s) => s.len(),
1286                        DataValue::Boolean(b) => {
1287                            if *b {
1288                                4
1289                            } else {
1290                                5
1291                            }
1292                        } // "true" or "false"
1293                        DataValue::DateTime(dt) => dt.len(),
1294                    };
1295                    max_width = max_width.max(display_width);
1296                }
1297            }
1298        }
1299
1300        let alignment = match data_table.columns.get(idx).map(|c| &c.data_type) {
1301            Some(crate::data::datatable::DataType::Integer) => "right",
1302            Some(crate::data::datatable::DataType::Float) => "right",
1303            _ => "left",
1304        };
1305
1306        let col_meta = serde_json::json!({
1307            "name": name,
1308            "type": col_type,
1309            "max_width": max_width,
1310            "alignment": alignment
1311        });
1312        columns.push(col_meta);
1313    }
1314
1315    // Build rows as arrays of strings
1316    let mut rows = Vec::new();
1317    for row_idx in 0..dataview.row_count() {
1318        if let Some(row) = dataview.get_row(row_idx) {
1319            let row_values: Vec<String> = row
1320                .values
1321                .iter()
1322                .map(|v| match v {
1323                    DataValue::Null => String::new(),
1324                    DataValue::Integer(i) => i.to_string(),
1325                    DataValue::Float(f) => format!("{:.2}", f),
1326                    DataValue::String(s) => s.clone(),
1327                    DataValue::InternedString(s) => s.to_string(),
1328                    DataValue::Boolean(b) => b.to_string(),
1329                    DataValue::DateTime(dt) => dt.clone(),
1330                })
1331                .collect();
1332            rows.push(serde_json::Value::Array(
1333                row_values
1334                    .into_iter()
1335                    .map(serde_json::Value::String)
1336                    .collect(),
1337            ));
1338        }
1339    }
1340
1341    // Build complete structured output
1342    let output = serde_json::json!({
1343        "columns": columns,
1344        "rows": rows,
1345        "metadata": {
1346            "total_rows": dataview.row_count(),
1347            "query_time_ms": exec_time
1348        }
1349    });
1350
1351    let json = serde_json::to_string_pretty(&output)?;
1352    writeln!(writer, "{json}")?;
1353
1354    Ok(())
1355}
1356
1357/// Output results using the old custom ASCII table format (for Nvim compatibility)
1358fn output_table_old_style<W: Write>(
1359    dataview: &DataView,
1360    writer: &mut W,
1361    max_col_width: Option<usize>,
1362) -> Result<()> {
1363    let columns = dataview.column_names();
1364
1365    // Calculate column widths
1366    let mut widths = vec![0; columns.len()];
1367    for (i, col) in columns.iter().enumerate() {
1368        widths[i] = col.len();
1369    }
1370
1371    // Scan all rows for width calculation
1372    for row_idx in 0..dataview.row_count() {
1373        if let Some(row) = dataview.get_row(row_idx) {
1374            for (i, value) in row.values.iter().enumerate() {
1375                if i < widths.len() {
1376                    let value_str = format_value(value);
1377                    widths[i] = widths[i].max(display_width(&value_str));
1378                }
1379            }
1380        }
1381    }
1382
1383    // Apply maximum column width if specified
1384    if let Some(max_width) = max_col_width {
1385        for width in &mut widths {
1386            *width = (*width).min(max_width);
1387        }
1388    }
1389
1390    // Print top border
1391    write!(writer, "+")?;
1392    for width in &widths {
1393        write!(writer, "{}", "-".repeat(*width + 2))?;
1394        write!(writer, "+")?;
1395    }
1396    writeln!(writer)?;
1397
1398    // Print headers
1399    write!(writer, "|")?;
1400    for (i, col) in columns.iter().enumerate() {
1401        write!(writer, " {:^width$} |", col, width = widths[i])?;
1402    }
1403    writeln!(writer)?;
1404
1405    // Print header separator
1406    write!(writer, "+")?;
1407    for width in &widths {
1408        write!(writer, "{}", "-".repeat(*width + 2))?;
1409        write!(writer, "+")?;
1410    }
1411    writeln!(writer)?;
1412
1413    // Print data rows (no separators between rows)
1414    for row_idx in 0..dataview.row_count() {
1415        if let Some(row) = dataview.get_row(row_idx) {
1416            write!(writer, "|")?;
1417            for (i, value) in row.values.iter().enumerate() {
1418                if i < widths.len() {
1419                    let value_str = format_value(value);
1420                    let display_len = display_width(&value_str);
1421
1422                    // For ANSI-colored strings, manual padding is needed
1423                    // because format! uses byte length, not display width
1424                    write!(writer, " {}", value_str)?;
1425                    let padding_needed = if display_len < widths[i] {
1426                        widths[i] - display_len
1427                    } else {
1428                        0
1429                    };
1430                    write!(writer, "{} |", " ".repeat(padding_needed))?;
1431                }
1432            }
1433            writeln!(writer)?;
1434        }
1435    }
1436
1437    // Print bottom border
1438    write!(writer, "+")?;
1439    for width in &widths {
1440        write!(writer, "{}", "-".repeat(*width + 2))?;
1441        write!(writer, "+")?;
1442    }
1443    writeln!(writer)?;
1444
1445    Ok(())
1446}
1447
1448/// Output results as a table using comfy-table with styling
1449fn output_table<W: Write>(
1450    dataview: &DataView,
1451    writer: &mut W,
1452    max_col_width: Option<usize>,
1453    _col_sample_rows: usize, // Not needed with comfy-table
1454    style: TableStyle,
1455    styled: bool,
1456    style_file: Option<&str>,
1457) -> Result<()> {
1458    let mut table = Table::new();
1459
1460    // Apply the selected style preset
1461    match style {
1462        TableStyle::Default => {
1463            // Use custom old-style renderer for Nvim compatibility
1464            // This matches what the table navigation parser expects
1465            return output_table_old_style(dataview, writer, max_col_width);
1466        }
1467        TableStyle::AsciiFull => {
1468            table.load_preset(ASCII_FULL);
1469        }
1470        TableStyle::AsciiCondensed => {
1471            table.load_preset(ASCII_FULL_CONDENSED);
1472        }
1473        TableStyle::AsciiBordersOnly => {
1474            table.load_preset(ASCII_BORDERS_ONLY);
1475        }
1476        TableStyle::AsciiHorizontalOnly => {
1477            table.load_preset(ASCII_HORIZONTAL_ONLY);
1478        }
1479        TableStyle::AsciiNoBorders => {
1480            table.load_preset(ASCII_NO_BORDERS);
1481        }
1482        TableStyle::Markdown => {
1483            table.load_preset(ASCII_MARKDOWN);
1484        }
1485        TableStyle::Utf8Full => {
1486            table.load_preset(UTF8_FULL);
1487        }
1488        TableStyle::Utf8Condensed => {
1489            table.load_preset(UTF8_FULL_CONDENSED);
1490        }
1491        TableStyle::Utf8BordersOnly => {
1492            table.load_preset(UTF8_BORDERS_ONLY);
1493        }
1494        TableStyle::Utf8HorizontalOnly => {
1495            table.load_preset(UTF8_HORIZONTAL_ONLY);
1496        }
1497        TableStyle::Utf8NoBorders => {
1498            table.load_preset(UTF8_NO_BORDERS);
1499        }
1500        TableStyle::Plain => {
1501            table.load_preset(NOTHING);
1502        }
1503    }
1504
1505    // Set content arrangement (automatic width adjustment)
1506    if max_col_width.is_some() {
1507        table.set_content_arrangement(ContentArrangement::Dynamic);
1508    }
1509
1510    // Set column headers
1511    let columns = dataview.column_names();
1512
1513    // Apply color styling if requested
1514    if styled {
1515        use crate::output::styled_table::{apply_styles_to_table, StyleConfig};
1516        use std::path::PathBuf;
1517
1518        // Load style configuration
1519        let style_config = if let Some(file_path) = style_file {
1520            let path = PathBuf::from(file_path);
1521            StyleConfig::from_file(&path).ok()
1522        } else {
1523            StyleConfig::load_default()
1524        };
1525
1526        if let Some(config) = style_config {
1527            // Convert DataView rows to Vec<Vec<String>> for styling
1528            let rows: Vec<Vec<String>> = (0..dataview.row_count())
1529                .filter_map(|i| {
1530                    dataview.get_row(i).map(|row| {
1531                        row.values
1532                            .iter()
1533                            .map(|v| {
1534                                let s = format_value(v);
1535                                // Apply max width truncation if specified
1536                                if let Some(max_width) = max_col_width {
1537                                    if s.len() > max_width {
1538                                        format!("{}...", &s[..max_width.saturating_sub(3)])
1539                                    } else {
1540                                        s
1541                                    }
1542                                } else {
1543                                    s
1544                                }
1545                            })
1546                            .collect()
1547                    })
1548                })
1549                .collect();
1550
1551            if let Err(e) = apply_styles_to_table(&mut table, &columns, &rows, &config) {
1552                eprintln!("Warning: Failed to apply styles: {}", e);
1553            }
1554        }
1555    } else {
1556        // No styling - add headers and rows normally
1557        table.set_header(&columns);
1558
1559        // Add data rows
1560        for row_idx in 0..dataview.row_count() {
1561            if let Some(row) = dataview.get_row(row_idx) {
1562                let row_strings: Vec<String> = row
1563                    .values
1564                    .iter()
1565                    .map(|v| {
1566                        let s = format_value(v);
1567                        // Apply max width truncation if specified
1568                        if let Some(max_width) = max_col_width {
1569                            if s.len() > max_width {
1570                                format!("{}...", &s[..max_width.saturating_sub(3)])
1571                            } else {
1572                                s
1573                            }
1574                        } else {
1575                            s
1576                        }
1577                    })
1578                    .collect();
1579                table.add_row(row_strings);
1580            }
1581        }
1582    }
1583
1584    // Write the table to the writer
1585    writeln!(writer, "{}", table)?;
1586
1587    Ok(())
1588}
1589
1590/// Format a `DataValue` for display
1591fn format_value(value: &DataValue) -> String {
1592    match value {
1593        DataValue::Null => String::new(),
1594        DataValue::Integer(i) => i.to_string(),
1595        DataValue::Float(f) => f.to_string(),
1596        DataValue::String(s) => s.clone(),
1597        DataValue::InternedString(s) => s.to_string(),
1598        DataValue::Boolean(b) => b.to_string(),
1599        DataValue::DateTime(dt) => dt.to_string(),
1600    }
1601}
1602
1603/// Convert `DataValue` to JSON
1604fn value_to_json(value: &DataValue) -> serde_json::Value {
1605    match value {
1606        DataValue::Null => serde_json::Value::Null,
1607        DataValue::Integer(i) => serde_json::Value::Number((*i).into()),
1608        DataValue::Float(f) => {
1609            if let Some(n) = serde_json::Number::from_f64(*f) {
1610                serde_json::Value::Number(n)
1611            } else {
1612                serde_json::Value::Null
1613            }
1614        }
1615        DataValue::String(s) => serde_json::Value::String(s.clone()),
1616        DataValue::InternedString(s) => serde_json::Value::String(s.to_string()),
1617        DataValue::Boolean(b) => serde_json::Value::Bool(*b),
1618        DataValue::DateTime(dt) => serde_json::Value::String(dt.to_string()),
1619    }
1620}
1621
1622/// Escape a CSV field if it contains special characters
1623fn escape_csv_field(field: &str, delimiter: char) -> String {
1624    if field.contains(delimiter)
1625        || field.contains('"')
1626        || field.contains('\n')
1627        || field.contains('\r')
1628    {
1629        format!("\"{}\"", field.replace('"', "\"\""))
1630    } else {
1631        field.to_string()
1632    }
1633}