Skip to main content

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