diff --git a/src/cli.rs b/src/cli.rs index 23c173c9..b6974ad3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -5,14 +5,11 @@ Provides commands for executing ggsql queries with various data sources and outp */ use clap::{Parser, Subcommand}; +use ggsql::reader::{Reader, Spec}; +use ggsql::validate::validate; use ggsql::{parser, VERSION}; use std::path::PathBuf; -#[cfg(feature = "duckdb")] -use ggsql::reader::{DuckDBReader, Reader}; -#[cfg(feature = "duckdb")] -use ggsql::validate::validate; - #[cfg(feature = "vegalite")] use ggsql::writer::{VegaLiteWriter, Writer}; @@ -32,11 +29,11 @@ pub enum Commands { /// The ggsql query to execute query: String, - /// Data source connection string + /// Data source connection string (duckdb://, sqlite://, polars://) #[arg(long, default_value = "duckdb://memory")] reader: String, - /// Output format + /// Output format (vegalite) #[arg(long, default_value = "vegalite")] writer: String, @@ -54,11 +51,11 @@ pub enum Commands { /// Path to .sql file containing ggsql query file: PathBuf, - /// Data source connection string + /// Data source connection string (duckdb://, sqlite://, polars://) #[arg(long, default_value = "duckdb://memory")] reader: String, - /// Output format + /// Output format (vegalite) #[arg(long, default_value = "vegalite")] writer: String, @@ -86,7 +83,7 @@ pub enum Commands { /// The ggsql query to validate query: String, - /// Data source connection string (needed for column validation) + /// Data source connection string for column validation (duckdb://, sqlite://, polars://) #[arg(long)] reader: Option, }, @@ -153,23 +150,75 @@ fn cmd_exec(query: String, reader: String, writer: String, output: Option r, + Err(e) => { + eprintln!("Failed to create reader: {}", e); + std::process::exit(1); + } + }; + exec_with_reader(&query, &r, &writer, output, verbose); + } + #[cfg(not(feature = "duckdb"))] + { + eprintln!("DuckDB reader not compiled in. Rebuild with --features duckdb"); + std::process::exit(1); + } + } else if reader.starts_with("polars://") { + #[cfg(feature = "polars-sql")] + { + let r = match ggsql::reader::PolarsReader::from_connection_string(&reader) { + Ok(r) => r, + Err(e) => { + eprintln!("Failed to create reader: {}", e); + std::process::exit(1); + } + }; + exec_with_reader(&query, &r, &writer, output, verbose); + } + #[cfg(not(feature = "polars-sql"))] + { + eprintln!("Polars reader not compiled in. Rebuild with --features polars-sql"); + std::process::exit(1); + } + } else if reader.starts_with("sqlite://") { + #[cfg(feature = "sqlite")] + { + let r = match ggsql::reader::SqliteReader::from_connection_string(&reader) { + Ok(r) => r, + Err(e) => { + eprintln!("Failed to create reader: {}", e); + std::process::exit(1); + } + }; + exec_with_reader(&query, &r, &writer, output, verbose); + } + #[cfg(not(feature = "sqlite"))] + { + eprintln!("SQLite reader not compiled in. Rebuild with --features sqlite"); + std::process::exit(1); + } + } else if reader.starts_with("postgres://") || reader.starts_with("postgresql://") { + eprintln!("PostgreSQL reader is not yet implemented"); std::process::exit(1); - } - - let db_reader = DuckDBReader::from_connection_string(&reader); - if let Err(e) = db_reader { - eprintln!("Failed to create DuckDB reader: {}", e); + } else { + eprintln!("Unsupported connection string: {}", reader); std::process::exit(1); } - let db_reader = db_reader.unwrap(); +} +fn exec_with_reader( + query: &str, + reader: &R, + writer: &str, + output: Option, + verbose: bool, +) { // Use validate() to check if query has visualization - let validated = match validate(&query) { + let validated = match validate(query) { Ok(v) => v, Err(e) => { eprintln!("Failed to validate query: {}", e); @@ -181,12 +230,12 @@ fn cmd_exec(query: String, reader: String, writer: String, output: Option s, Err(e) => { eprintln!("Failed to execute query: {}", e); @@ -194,6 +243,10 @@ fn cmd_exec(query: String, reader: String, writer: String, output: Option, verbose: bool) { if verbose { let metadata = spec.metadata(); eprintln!("\nQuery executed:"); @@ -292,41 +345,32 @@ fn cmd_parse(query: String, format: String) { } fn cmd_validate(query: String, _reader: Option) { - #[cfg(feature = "duckdb")] - { - match validate(&query) { - Ok(validated) if validated.valid() => { - println!("✓ Query syntax is valid"); + match validate(&query) { + Ok(validated) if validated.valid() => { + println!("✓ Query syntax is valid"); + } + Ok(validated) => { + println!("✗ Validation errors:"); + for err in validated.errors() { + println!(" - {}", err.message); } - Ok(validated) => { - println!("✗ Validation errors:"); - for err in validated.errors() { - println!(" - {}", err.message); + if !validated.warnings().is_empty() { + println!("\nWarnings:"); + for warning in validated.warnings() { + println!(" - {}", warning.message); } - if !validated.warnings().is_empty() { - println!("\nWarnings:"); - for warning in validated.warnings() { - println!(" - {}", warning.message); - } - } - std::process::exit(1); - } - Err(e) => { - eprintln!("Error during validation: {}", e); - std::process::exit(1); } + std::process::exit(1); + } + Err(e) => { + eprintln!("Error during validation: {}", e); + std::process::exit(1); } - } - - #[cfg(not(feature = "duckdb"))] - { - eprintln!("Validation requires the duckdb feature"); - std::process::exit(1); } } // Prints a CSV-like output to stdout with aligned columns -fn print_table_fallback(query: &str, reader: &DuckDBReader, max_rows: usize) { +fn print_table_fallback(query: &str, reader: &R, max_rows: usize) { let source_tree = match parser::SourceTree::new(query) { Ok(st) => st, Err(e) => {