use sqlparser::ast;
use crate::error::Result;
use crate::functions::registry::FunctionRegistry;
use crate::parser::normalize::normalize_ident;
use crate::planner::aggregate::{
expr_column_name, extract_aggregates_from_projection, function_args_exprs, key_column_name,
normalize_function_name,
};
use crate::resolver::expr::convert_expr;
use crate::types::query::AggOutputSlot;
use crate::types_expr::SqlExpr;
pub fn compute_output_order(
projection: &[ast::SelectItem],
group_by: &[SqlExpr],
functions: &FunctionRegistry,
) -> Result<Vec<AggOutputSlot>> {
let real_agg_count = extract_aggregates_from_projection(projection, functions)?.len();
let mut order = Vec::new();
let mut agg_cursor = 0usize;
let mut grouping_cursor = 0usize;
for item in projection {
let expr = match item {
ast::SelectItem::UnnamedExpr(expr) => expr,
ast::SelectItem::ExprWithAlias { expr, .. } => expr,
_ => continue,
};
if let Some(name) = expr_column_name(expr)
&& let Some(index) = group_by
.iter()
.position(|key| key_column_name(key) == Some(name.as_str()))
{
order.push(AggOutputSlot::GroupKey(index));
continue;
}
if let Ok(converted) = convert_expr(expr) {
let rendered = format!("{converted:?}");
if let Some(index) = group_by
.iter()
.position(|key| format!("{key:?}") == rendered)
{
order.push(AggOutputSlot::GroupKey(index));
continue;
}
}
let grouping_count = count_grouping_calls(expr);
if grouping_count > 0 {
for _ in 0..grouping_count {
order.push(AggOutputSlot::Aggregate(real_agg_count + grouping_cursor));
grouping_cursor += 1;
}
continue;
}
if crate::aggregate_walk::contains_aggregate(expr, functions) {
let alias = match item {
ast::SelectItem::ExprWithAlias { alias, .. } => normalize_ident(alias),
_ => format!("{expr}").to_lowercase(),
};
let produced =
crate::aggregate_walk::extract_aggregates(expr, &alias, functions)?.len();
for _ in 0..produced {
order.push(AggOutputSlot::Aggregate(agg_cursor));
agg_cursor += 1;
}
}
}
Ok(order)
}
fn count_grouping_calls(expr: &ast::Expr) -> usize {
match expr {
ast::Expr::Function(f) if normalize_function_name(f).eq_ignore_ascii_case("grouping") => {
function_args_exprs(f).len()
}
ast::Expr::BinaryOp { left, right, .. } => {
count_grouping_calls(left) + count_grouping_calls(right)
}
_ => 0,
}
}