pub mod executor;
pub use executor::QueryExecutor;
use sqlparser::ast::{Expr, Query, Select, SelectItem, SetExpr, Statement};
use sqlparser::dialect::GenericDialect;
use sqlparser::parser::Parser;
pub type Aggregation = (AggregateFunction, String, Option<String>);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QueryPlan {
pub columns: Vec<String>,
pub table: String,
pub filter: Option<String>,
pub group_by: Vec<String>,
pub aggregations: Vec<Aggregation>,
pub order_by: Vec<(String, OrderDirection)>,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AggregateFunction {
Sum,
Avg,
Count,
Min,
Max,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrderDirection {
Asc,
Desc,
}
pub struct QueryEngine {
dialect: GenericDialect,
}
impl Default for QueryEngine {
fn default() -> Self {
Self::new()
}
}
impl QueryEngine {
#[must_use]
pub const fn new() -> Self {
Self { dialect: GenericDialect {} }
}
pub fn parse(&self, sql: &str) -> crate::Result<QueryPlan> {
if sql.trim().is_empty() {
return Ok(QueryPlan {
columns: vec!["*".to_string()],
table: String::new(),
filter: None,
group_by: Vec::new(),
aggregations: Vec::new(),
order_by: Vec::new(),
limit: None,
});
}
let statements = Parser::parse_sql(&self.dialect, sql)
.map_err(|e| crate::Error::ParseError(format!("SQL parse error: {e}")))?;
if statements.len() != 1 {
return Err(crate::Error::ParseError("Only single statements supported".to_string()));
}
let stmt = &statements[0];
let Statement::Query(query) = stmt else {
return Err(crate::Error::ParseError("Only SELECT queries supported".to_string()));
};
Self::parse_select_query(query)
}
fn parse_select_query(query: &Query) -> crate::Result<QueryPlan> {
let SetExpr::Select(select) = query.body.as_ref() else {
return Err(crate::Error::ParseError("Only SELECT queries supported".to_string()));
};
let table = Self::extract_table_name(select)?;
let (columns, aggregations) = Self::extract_columns(&select.projection)?;
let filter = select.selection.as_ref().map(ToString::to_string);
let group_by = Self::extract_group_by(&select.group_by);
let order_by = Self::extract_order_by(query.order_by.as_ref());
let limit = Self::extract_limit(query.limit.as_ref());
Ok(QueryPlan { columns, table, filter, group_by, aggregations, order_by, limit })
}
fn extract_table_name(select: &Select) -> crate::Result<String> {
if select.from.is_empty() {
return Ok(String::new());
}
if select.from.len() > 1 {
return Err(crate::Error::ParseError(
"Multiple tables not supported in Phase 1".to_string(),
));
}
let table_with_joins = &select.from[0];
if !table_with_joins.joins.is_empty() {
return Err(crate::Error::ParseError("JOINs not supported in Phase 1".to_string()));
}
Ok(table_with_joins.relation.to_string())
}
fn extract_columns(
projection: &[SelectItem],
) -> crate::Result<(Vec<String>, Vec<Aggregation>)> {
let mut columns = Vec::new();
let mut aggregations = Vec::new();
for item in projection {
match item {
SelectItem::Wildcard(_) => {
columns.push("*".to_string());
}
SelectItem::UnnamedExpr(expr) => {
if let Some((func, col)) = Self::extract_aggregate(expr) {
aggregations.push((func, col, None));
} else {
columns.push(expr.to_string());
}
}
SelectItem::ExprWithAlias { expr, alias } => {
if let Some((func, col)) = Self::extract_aggregate(expr) {
aggregations.push((func, col, Some(alias.value.clone())));
} else {
columns.push(alias.value.clone());
}
}
SelectItem::QualifiedWildcard(..) => {
return Err(crate::Error::ParseError(
"Qualified wildcards not supported".to_string(),
))
}
}
}
Ok((columns, aggregations))
}
fn extract_aggregate(expr: &Expr) -> Option<(AggregateFunction, String)> {
if let Expr::Function(func) = expr {
let func_name = func.name.to_string().to_uppercase();
let agg_func = match func_name.as_str() {
"SUM" => AggregateFunction::Sum,
"AVG" => AggregateFunction::Avg,
"COUNT" => AggregateFunction::Count,
"MIN" => AggregateFunction::Min,
"MAX" => AggregateFunction::Max,
_ => return None,
};
let col = match &func.args {
sqlparser::ast::FunctionArguments::List(func_arg_list) => {
func_arg_list.args.first().map_or_else(|| "*".to_string(), ToString::to_string)
}
_ => "*".to_string(),
};
return Some((agg_func, col));
}
None
}
fn extract_group_by(group_by: &sqlparser::ast::GroupByExpr) -> Vec<String> {
match group_by {
sqlparser::ast::GroupByExpr::All(_) => Vec::new(),
sqlparser::ast::GroupByExpr::Expressions(exprs, _) => {
exprs.iter().map(ToString::to_string).collect()
}
}
}
fn extract_order_by(
order_by: Option<&sqlparser::ast::OrderBy>,
) -> Vec<(String, OrderDirection)> {
order_by
.map(|ob| {
ob.exprs
.iter()
.map(|o| {
let col = o.expr.to_string();
let dir = if o.asc.unwrap_or(true) {
OrderDirection::Asc
} else {
OrderDirection::Desc
};
(col, dir)
})
.collect()
})
.unwrap_or_default()
}
fn extract_limit(limit: Option<&Expr>) -> Option<usize> {
limit.and_then(|expr| {
if let Expr::Value(sqlparser::ast::Value::Number(n, _)) = expr {
n.parse().ok()
} else {
None
}
})
}
}