use anyhow::Result;
use ggsql::{
reader::{DuckDBReader, Reader},
validate::validate,
writer::{VegaLiteWriter, Writer},
};
use polars::frame::DataFrame;
#[derive(Debug)]
pub enum ExecutionResult {
DataFrame(DataFrame),
Visualization {
spec: String, },
}
pub struct QueryExecutor {
reader: DuckDBReader,
writer: VegaLiteWriter,
}
impl QueryExecutor {
pub fn new() -> Result<Self> {
tracing::info!("Initializing query executor with in-memory DuckDB");
let reader = DuckDBReader::from_connection_string("duckdb://memory")?;
let writer = VegaLiteWriter::new();
Ok(Self { reader, writer })
}
pub fn execute(&self, code: &str) -> Result<ExecutionResult> {
tracing::debug!("Executing query: {} chars", code.len());
let validated = validate(code)?;
if !validated.has_visual() {
let df = self.reader.execute_sql(code)?;
tracing::info!(
"Pure SQL executed: {} rows, {} cols",
df.height(),
df.width()
);
return Ok(ExecutionResult::DataFrame(df));
}
let spec = self.reader.execute(code)?;
tracing::info!(
"Query executed: {} rows, {} layers",
spec.metadata().rows,
spec.metadata().layer_count
);
let vega_json = self.writer.render(&spec)?;
tracing::debug!("Generated Vega-Lite spec: {} chars", vega_json.len());
Ok(ExecutionResult::Visualization { spec: vega_json })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_visualization() {
let executor = QueryExecutor::new().unwrap();
let code = "SELECT 1 as x, 2 as y VISUALISE x, y DRAW point";
let result = executor.execute(code).unwrap();
assert!(matches!(result, ExecutionResult::Visualization { .. }));
}
#[test]
fn test_pure_sql() {
let executor = QueryExecutor::new().unwrap();
let code = "SELECT 1 as x, 2 as y";
let result = executor.execute(code).unwrap();
assert!(matches!(result, ExecutionResult::DataFrame(_)));
}
#[test]
fn test_error_handling() {
let executor = QueryExecutor::new().unwrap();
let code = "SELECT * FROM nonexistent_table";
let result = executor.execute(code);
assert!(result.is_err());
}
}