use sqlparser::ast;
use crate::error::Result;
use crate::functions::registry::FunctionRegistry;
use crate::parser::normalize::normalize_ident;
use crate::planner::aggregate::{
extract_aggregates_from_projection, function_args_exprs, normalize_function_name,
};
use crate::planner::group_by::{expr_column_name, key_column_name};
use crate::resolver::ColumnScope;
use crate::resolver::columns::TableScope;
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,
scope: &TableScope,
) -> Result<Vec<AggOutputSlot>> {
Ok(
compute_output_order_by_item(projection, group_by, functions, scope)?
.into_iter()
.flatten()
.collect(),
)
}
pub fn compute_output_order_by_item(
projection: &[ast::SelectItem],
group_by: &[SqlExpr],
functions: &FunctionRegistry,
scope: &TableScope,
) -> Result<Vec<Vec<AggOutputSlot>>> {
let real_agg_count = extract_aggregates_from_projection(projection, functions, scope)?.len();
let mut by_item = Vec::with_capacity(projection.len());
let mut agg_cursor = 0usize;
let mut grouping_cursor = 0usize;
for item in projection {
let mut order = Vec::new();
let expr = match item {
ast::SelectItem::UnnamedExpr(expr) => expr,
ast::SelectItem::ExprWithAlias { expr, .. } => expr,
_ => {
by_item.push(order);
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));
by_item.push(order);
continue;
}
if let Ok(converted) = convert_expr(expr, &ColumnScope::Relations(scope)) {
let rendered = format!("{converted:?}");
if let Some(index) = group_by
.iter()
.position(|key| format!("{key:?}") == rendered)
{
order.push(AggOutputSlot::GroupKey(index));
by_item.push(order);
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;
}
by_item.push(order);
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, scope)?.len();
for _ in 0..produced {
order.push(AggOutputSlot::Aggregate(agg_cursor));
agg_cursor += 1;
}
}
by_item.push(order);
}
Ok(by_item)
}
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,
}
}