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