use vibesql_ast::Expression;
use super::ResolvedGroupingSet;
use crate::errors::ExecutorError;
pub fn resolve_group_by_alias(
group_expr: &Expression,
select_list: &[vibesql_ast::SelectItem],
term_index: usize,
) -> Result<Expression, ExecutorError> {
if let Expression::Literal(vibesql_types::SqlValue::Integer(pos)) = group_expr {
if *pos <= 0 || (*pos as usize) > select_list.len() {
return Err(ExecutorError::GroupByOutOfRange {
term_position: term_index + 1, column_number: *pos,
select_list_len: select_list.len(),
});
}
let idx = (*pos as usize) - 1;
if let vibesql_ast::SelectItem::Expression { expr, .. } = &select_list[idx] {
return Ok(expr.clone());
}
}
if let Expression::ColumnRef(col_id) = group_expr {
if col_id.schema_canonical().is_none() && col_id.table_canonical().is_none() {
let column = col_id.column_canonical();
for item in select_list {
if let vibesql_ast::SelectItem::Expression {
expr, alias: Some(alias_name), ..
} = item
{
if alias_name.eq_ignore_ascii_case(column) {
return Ok(expr.clone());
}
}
}
}
}
Ok(group_expr.clone())
}
pub fn resolve_grouping_set_aliases(
set: &ResolvedGroupingSet,
select_list: &[vibesql_ast::SelectItem],
) -> Result<ResolvedGroupingSet, ExecutorError> {
let mut resolved_exprs = Vec::with_capacity(set.group_by_exprs.len());
for (i, expr) in set.group_by_exprs.iter().enumerate() {
resolved_exprs.push(resolve_group_by_alias(expr, select_list, i)?);
}
Ok(ResolvedGroupingSet { group_by_exprs: resolved_exprs, rolled_up: set.rolled_up.clone() })
}
pub fn resolve_base_expressions_aliases(
base_exprs: &[Expression],
select_list: &[vibesql_ast::SelectItem],
) -> Result<Vec<Expression>, ExecutorError> {
let mut resolved = Vec::with_capacity(base_exprs.len());
for (i, expr) in base_exprs.iter().enumerate() {
resolved.push(resolve_group_by_alias(expr, select_list, i)?);
}
Ok(resolved)
}
#[allow(dead_code)]
pub fn resolve_having_aliases(
having_expr: &Expression,
select_list: &[vibesql_ast::SelectItem],
group_by_exprs: &[Expression],
) -> Expression {
resolve_having_aliases_inner(having_expr, select_list, group_by_exprs, None)
}
#[allow(dead_code)]
fn resolve_having_aliases_with_schema(
having_expr: &Expression,
select_list: &[vibesql_ast::SelectItem],
group_by_exprs: &[Expression],
schema_columns: &std::collections::HashSet<String>,
) -> Expression {
resolve_having_aliases_inner(having_expr, select_list, group_by_exprs, Some(schema_columns))
}
pub fn resolve_having_aliases_with_values(
having_expr: &Expression,
select_list: &[vibesql_ast::SelectItem],
group_by_exprs: &[Expression],
schema_columns: &std::collections::HashSet<String>,
alias_values: &std::collections::HashMap<String, vibesql_types::SqlValue>,
expr_values: &[(Expression, vibesql_types::SqlValue)],
) -> Expression {
resolve_having_aliases_with_values_inner(
having_expr,
select_list,
group_by_exprs,
Some(schema_columns),
alias_values,
expr_values,
)
}
fn is_group_by_column(column: &str, group_by_exprs: &[Expression]) -> bool {
for expr in group_by_exprs {
if let Expression::ColumnRef(col_id) = expr {
if col_id.schema_canonical().is_none() {
let group_col = col_id.column_canonical();
if group_col.eq_ignore_ascii_case(column) {
return true;
}
}
}
}
false
}
fn resolve_having_aliases_inner(
having_expr: &Expression,
select_list: &[vibesql_ast::SelectItem],
group_by_exprs: &[Expression],
schema_columns: Option<&std::collections::HashSet<String>>,
) -> Expression {
match having_expr {
Expression::ColumnRef(col_id)
if col_id.schema_canonical().is_none() && col_id.table_canonical().is_none() =>
{
let column = col_id.column_canonical();
if is_group_by_column(column, group_by_exprs) {
return having_expr.clone();
}
if let Some(schema_cols) = schema_columns {
if schema_cols.contains(&column.to_lowercase()) {
return having_expr.clone();
}
}
for item in select_list {
if let vibesql_ast::SelectItem::Expression {
expr, alias: Some(alias_name), ..
} = item
{
if alias_name.eq_ignore_ascii_case(column) {
return expr.clone();
}
}
}
having_expr.clone()
}
Expression::BinaryOp { left, op, right } => Expression::BinaryOp {
left: Box::new(resolve_having_aliases_inner(
left,
select_list,
group_by_exprs,
schema_columns,
)),
op: *op,
right: Box::new(resolve_having_aliases_inner(
right,
select_list,
group_by_exprs,
schema_columns,
)),
},
Expression::UnaryOp { op, expr } => Expression::UnaryOp {
op: *op,
expr: Box::new(resolve_having_aliases_inner(
expr,
select_list,
group_by_exprs,
schema_columns,
)),
},
Expression::Function { name, args, character_unit } => Expression::Function {
name: name.clone(),
args: args
.iter()
.map(|a| {
resolve_having_aliases_inner(a, select_list, group_by_exprs, schema_columns)
})
.collect(),
character_unit: character_unit.clone(),
},
Expression::AggregateFunction { name, distinct, args, order_by, filter } => {
Expression::AggregateFunction {
name: name.clone(),
distinct: *distinct,
args: args
.iter()
.map(|a| {
resolve_having_aliases_inner(a, select_list, group_by_exprs, schema_columns)
})
.collect(),
order_by: order_by.clone(),
filter: filter.as_ref().map(|f| {
Box::new(resolve_having_aliases_inner(
f,
select_list,
group_by_exprs,
schema_columns,
))
}),
}
}
Expression::Case { operand, when_clauses, else_result } => {
let resolved_operand = operand.as_ref().map(|o| {
Box::new(resolve_having_aliases_inner(
o,
select_list,
group_by_exprs,
schema_columns,
))
});
let resolved_when_clauses = when_clauses
.iter()
.map(|wc| vibesql_ast::CaseWhen {
conditions: wc
.conditions
.iter()
.map(|c| {
resolve_having_aliases_inner(
c,
select_list,
group_by_exprs,
schema_columns,
)
})
.collect(),
result: resolve_having_aliases_inner(
&wc.result,
select_list,
group_by_exprs,
schema_columns,
),
})
.collect();
let resolved_else = else_result.as_ref().map(|e| {
Box::new(resolve_having_aliases_inner(
e,
select_list,
group_by_exprs,
schema_columns,
))
});
Expression::Case {
operand: resolved_operand,
when_clauses: resolved_when_clauses,
else_result: resolved_else,
}
}
Expression::Conjunction(terms) => Expression::Conjunction(
terms
.iter()
.map(|t| {
resolve_having_aliases_inner(t, select_list, group_by_exprs, schema_columns)
})
.collect(),
),
Expression::Disjunction(terms) => Expression::Disjunction(
terms
.iter()
.map(|t| {
resolve_having_aliases_inner(t, select_list, group_by_exprs, schema_columns)
})
.collect(),
),
Expression::Between { expr, low, high, negated, symmetric } => Expression::Between {
expr: Box::new(resolve_having_aliases_inner(
expr,
select_list,
group_by_exprs,
schema_columns,
)),
low: Box::new(resolve_having_aliases_inner(
low,
select_list,
group_by_exprs,
schema_columns,
)),
high: Box::new(resolve_having_aliases_inner(
high,
select_list,
group_by_exprs,
schema_columns,
)),
negated: *negated,
symmetric: *symmetric,
},
Expression::IsNull { expr, negated } => Expression::IsNull {
expr: Box::new(resolve_having_aliases_inner(
expr,
select_list,
group_by_exprs,
schema_columns,
)),
negated: *negated,
},
_ => having_expr.clone(),
}
}
fn resolve_having_aliases_with_values_inner(
having_expr: &Expression,
select_list: &[vibesql_ast::SelectItem],
group_by_exprs: &[Expression],
schema_columns: Option<&std::collections::HashSet<String>>,
alias_values: &std::collections::HashMap<String, vibesql_types::SqlValue>,
expr_values: &[(Expression, vibesql_types::SqlValue)],
) -> Expression {
match having_expr {
Expression::ColumnRef(col_id)
if col_id.schema_canonical().is_none() && col_id.table_canonical().is_none() =>
{
let column = col_id.column_canonical();
if is_group_by_column(column, group_by_exprs) {
return having_expr.clone();
}
if let Some(schema_cols) = schema_columns {
if schema_cols.contains(&column.to_lowercase()) {
return having_expr.clone();
}
}
for item in select_list {
if let vibesql_ast::SelectItem::Expression {
expr, alias: Some(alias_name), ..
} = item
{
if alias_name.eq_ignore_ascii_case(column) {
return expr.clone();
}
}
}
having_expr.clone()
}
Expression::BinaryOp { left, op, right } => Expression::BinaryOp {
left: Box::new(resolve_having_aliases_with_values_inner(
left,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
)),
op: *op,
right: Box::new(resolve_having_aliases_with_values_inner(
right,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
)),
},
Expression::UnaryOp { op, expr } => Expression::UnaryOp {
op: *op,
expr: Box::new(resolve_having_aliases_with_values_inner(
expr,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
)),
},
Expression::Function { name, args, character_unit } => Expression::Function {
name: name.clone(),
args: args
.iter()
.map(|a| {
resolve_having_aliases_with_values_inner(
a,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
)
})
.collect(),
character_unit: character_unit.clone(),
},
Expression::AggregateFunction { name, distinct, args, order_by, filter } => {
Expression::AggregateFunction {
name: name.clone(),
distinct: *distinct,
args: args
.iter()
.map(|a| {
resolve_having_aliases_with_values_inner(
a,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
)
})
.collect(),
order_by: order_by.clone(),
filter: filter.as_ref().map(|f| {
Box::new(resolve_having_aliases_with_values_inner(
f,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
))
}),
}
}
Expression::Case { operand, when_clauses, else_result } => {
let resolved_operand = operand.as_ref().map(|o| {
Box::new(resolve_having_aliases_with_values_inner(
o,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
))
});
let resolved_when_clauses = when_clauses
.iter()
.map(|wc| vibesql_ast::CaseWhen {
conditions: wc
.conditions
.iter()
.map(|c| {
resolve_having_aliases_with_values_inner(
c,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
)
})
.collect(),
result: resolve_having_aliases_with_values_inner(
&wc.result,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
),
})
.collect();
let resolved_else = else_result.as_ref().map(|e| {
Box::new(resolve_having_aliases_with_values_inner(
e,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
))
});
Expression::Case {
operand: resolved_operand,
when_clauses: resolved_when_clauses,
else_result: resolved_else,
}
}
Expression::Conjunction(terms) => Expression::Conjunction(
terms
.iter()
.map(|t| {
resolve_having_aliases_with_values_inner(
t,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
)
})
.collect(),
),
Expression::Disjunction(terms) => Expression::Disjunction(
terms
.iter()
.map(|t| {
resolve_having_aliases_with_values_inner(
t,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
)
})
.collect(),
),
Expression::Between { expr, low, high, negated, symmetric } => Expression::Between {
expr: Box::new(resolve_having_aliases_with_values_inner(
expr,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
)),
low: Box::new(resolve_having_aliases_with_values_inner(
low,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
)),
high: Box::new(resolve_having_aliases_with_values_inner(
high,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
)),
negated: *negated,
symmetric: *symmetric,
},
Expression::IsNull { expr, negated } => Expression::IsNull {
expr: Box::new(resolve_having_aliases_with_values_inner(
expr,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
)),
negated: *negated,
},
Expression::Exists { subquery, negated } => {
let resolved_subquery =
resolve_outer_aliases_in_subquery(subquery, alias_values, expr_values);
Expression::Exists { subquery: Box::new(resolved_subquery), negated: *negated }
}
Expression::In { expr, subquery, negated } => {
let resolved_expr = resolve_having_aliases_with_values_inner(
expr,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
);
let resolved_subquery =
resolve_outer_aliases_in_subquery(subquery, alias_values, expr_values);
Expression::In {
expr: Box::new(resolved_expr),
subquery: Box::new(resolved_subquery),
negated: *negated,
}
}
Expression::ScalarSubquery(subquery) => {
let resolved_subquery =
resolve_outer_aliases_in_subquery(subquery, alias_values, expr_values);
Expression::ScalarSubquery(Box::new(resolved_subquery))
}
Expression::QuantifiedComparison { expr, op, quantifier, subquery } => {
let resolved_expr = resolve_having_aliases_with_values_inner(
expr,
select_list,
group_by_exprs,
schema_columns,
alias_values,
expr_values,
);
let resolved_subquery =
resolve_outer_aliases_in_subquery(subquery, alias_values, expr_values);
Expression::QuantifiedComparison {
expr: Box::new(resolved_expr),
op: *op,
quantifier: quantifier.clone(),
subquery: Box::new(resolved_subquery),
}
}
_ => having_expr.clone(),
}
}
fn resolve_outer_aliases_in_subquery(
subquery: &vibesql_ast::SelectStmt,
alias_values: &std::collections::HashMap<String, vibesql_types::SqlValue>,
expr_values: &[(Expression, vibesql_types::SqlValue)],
) -> vibesql_ast::SelectStmt {
let resolved_having = subquery.having.as_ref().map(|having_expr| {
resolve_outer_alias_refs_to_literals(having_expr, alias_values, expr_values)
});
let resolved_where = subquery.where_clause.as_ref().map(|where_expr| {
resolve_outer_alias_refs_to_literals(where_expr, alias_values, expr_values)
});
vibesql_ast::SelectStmt {
with_clause: subquery.with_clause.clone(),
distinct: subquery.distinct,
select_list: subquery.select_list.clone(),
into_table: subquery.into_table.clone(),
into_variables: subquery.into_variables.clone(),
from: subquery.from.clone(),
where_clause: resolved_where,
group_by: subquery.group_by.clone(),
having: resolved_having,
window_definitions: None,
order_by: subquery.order_by.clone(),
limit: subquery.limit.clone(),
offset: subquery.offset.clone(),
set_operation: subquery.set_operation.clone(),
values: subquery.values.clone(),
}
}
fn resolve_outer_alias_refs_to_literals(
expr: &Expression,
alias_values: &std::collections::HashMap<String, vibesql_types::SqlValue>,
expr_values: &[(Expression, vibesql_types::SqlValue)],
) -> Expression {
match expr {
Expression::ColumnRef(col_id)
if col_id.schema_canonical().is_none() && col_id.table_canonical().is_none() =>
{
let column = col_id.column_canonical();
for (alias_name, value) in alias_values {
if alias_name.eq_ignore_ascii_case(column) {
return Expression::Literal(value.clone());
}
}
expr.clone()
}
Expression::BinaryOp { left, op, right } => Expression::BinaryOp {
left: Box::new(resolve_outer_alias_refs_to_literals(left, alias_values, expr_values)),
op: *op,
right: Box::new(resolve_outer_alias_refs_to_literals(right, alias_values, expr_values)),
},
Expression::UnaryOp { op, expr: inner } => Expression::UnaryOp {
op: *op,
expr: Box::new(resolve_outer_alias_refs_to_literals(inner, alias_values, expr_values)),
},
Expression::Function { name, args, character_unit } => {
for (outer_expr, value) in expr_values {
if expr == outer_expr {
return Expression::Literal(value.clone());
}
}
Expression::Function {
name: name.clone(),
args: args
.iter()
.map(|a| resolve_outer_alias_refs_to_literals(a, alias_values, expr_values))
.collect(),
character_unit: character_unit.clone(),
}
}
Expression::AggregateFunction { name, distinct, args, order_by, filter } => {
for (outer_expr, value) in expr_values {
if expr == outer_expr {
return Expression::Literal(value.clone());
}
}
Expression::AggregateFunction {
name: name.clone(),
distinct: *distinct,
args: args
.iter()
.map(|a| resolve_outer_alias_refs_to_literals(a, alias_values, expr_values))
.collect(),
order_by: order_by.clone(),
filter: filter.as_ref().map(|f| {
Box::new(resolve_outer_alias_refs_to_literals(f, alias_values, expr_values))
}),
}
}
Expression::Case { operand, when_clauses, else_result } => Expression::Case {
operand: operand.as_ref().map(|o| {
Box::new(resolve_outer_alias_refs_to_literals(o, alias_values, expr_values))
}),
when_clauses: when_clauses
.iter()
.map(|wc| vibesql_ast::CaseWhen {
conditions: wc
.conditions
.iter()
.map(|c| resolve_outer_alias_refs_to_literals(c, alias_values, expr_values))
.collect(),
result: resolve_outer_alias_refs_to_literals(
&wc.result,
alias_values,
expr_values,
),
})
.collect(),
else_result: else_result.as_ref().map(|e| {
Box::new(resolve_outer_alias_refs_to_literals(e, alias_values, expr_values))
}),
},
Expression::Conjunction(terms) => Expression::Conjunction(
terms
.iter()
.map(|t| resolve_outer_alias_refs_to_literals(t, alias_values, expr_values))
.collect(),
),
Expression::Disjunction(terms) => Expression::Disjunction(
terms
.iter()
.map(|t| resolve_outer_alias_refs_to_literals(t, alias_values, expr_values))
.collect(),
),
Expression::Between { expr, low, high, negated, symmetric } => Expression::Between {
expr: Box::new(resolve_outer_alias_refs_to_literals(expr, alias_values, expr_values)),
low: Box::new(resolve_outer_alias_refs_to_literals(low, alias_values, expr_values)),
high: Box::new(resolve_outer_alias_refs_to_literals(high, alias_values, expr_values)),
negated: *negated,
symmetric: *symmetric,
},
Expression::IsNull { expr, negated } => Expression::IsNull {
expr: Box::new(resolve_outer_alias_refs_to_literals(expr, alias_values, expr_values)),
negated: *negated,
},
Expression::InList { expr, values, negated } => Expression::InList {
expr: Box::new(resolve_outer_alias_refs_to_literals(expr, alias_values, expr_values)),
values: values
.iter()
.map(|v| resolve_outer_alias_refs_to_literals(v, alias_values, expr_values))
.collect(),
negated: *negated,
},
Expression::Like { expr, pattern, negated, escape } => Expression::Like {
expr: Box::new(resolve_outer_alias_refs_to_literals(expr, alias_values, expr_values)),
pattern: Box::new(resolve_outer_alias_refs_to_literals(
pattern,
alias_values,
expr_values,
)),
negated: *negated,
escape: escape.as_ref().map(|e| {
Box::new(resolve_outer_alias_refs_to_literals(e, alias_values, expr_values))
}),
},
Expression::Glob { expr, pattern, negated, escape } => Expression::Glob {
expr: Box::new(resolve_outer_alias_refs_to_literals(expr, alias_values, expr_values)),
pattern: Box::new(resolve_outer_alias_refs_to_literals(
pattern,
alias_values,
expr_values,
)),
negated: *negated,
escape: escape.as_ref().map(|e| {
Box::new(resolve_outer_alias_refs_to_literals(e, alias_values, expr_values))
}),
},
Expression::Cast { expr, data_type } => Expression::Cast {
expr: Box::new(resolve_outer_alias_refs_to_literals(expr, alias_values, expr_values)),
data_type: data_type.clone(),
},
Expression::Exists { subquery, negated } => {
let resolved_subquery =
resolve_outer_aliases_in_subquery(subquery, alias_values, expr_values);
Expression::Exists { subquery: Box::new(resolved_subquery), negated: *negated }
}
Expression::In { expr, subquery, negated } => Expression::In {
expr: Box::new(resolve_outer_alias_refs_to_literals(expr, alias_values, expr_values)),
subquery: Box::new(resolve_outer_aliases_in_subquery(
subquery,
alias_values,
expr_values,
)),
negated: *negated,
},
Expression::ScalarSubquery(subquery) => Expression::ScalarSubquery(Box::new(
resolve_outer_aliases_in_subquery(subquery, alias_values, expr_values),
)),
Expression::QuantifiedComparison { expr, op, quantifier, subquery } => {
Expression::QuantifiedComparison {
expr: Box::new(resolve_outer_alias_refs_to_literals(
expr,
alias_values,
expr_values,
)),
op: *op,
quantifier: quantifier.clone(),
subquery: Box::new(resolve_outer_aliases_in_subquery(
subquery,
alias_values,
expr_values,
)),
}
}
_ => expr.clone(),
}
}
#[cfg(test)]
mod tests {
use vibesql_ast::SelectItem;
use super::*;
fn col(name: &str) -> Expression {
Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(name, false))
}
fn select_item(expr: Expression, alias: Option<&str>) -> SelectItem {
SelectItem::Expression { expr, alias: alias.map(|s| s.to_string()), source_text: None }
}
#[test]
fn test_resolve_group_by_alias_simple() {
let select_list = vec![
select_item(col("n_name"), Some("nation")),
select_item(
Expression::AggregateFunction {
name: vibesql_ast::FunctionIdentifier::new("COUNT"),
distinct: false,
args: vec![Expression::Wildcard],
order_by: None,
filter: None,
},
None,
),
];
let group_expr = col("nation");
let resolved = resolve_group_by_alias(&group_expr, &select_list, 0).unwrap();
assert!(
matches!(&resolved, Expression::ColumnRef(col_id) if col_id.column_canonical() == "n_name")
);
}
#[test]
fn test_resolve_group_by_alias_case_insensitive() {
let select_list = vec![select_item(col("n_name"), Some("NATION"))];
let group_expr = col("nation");
let resolved = resolve_group_by_alias(&group_expr, &select_list, 0).unwrap();
assert!(
matches!(&resolved, Expression::ColumnRef(col_id) if col_id.column_canonical() == "n_name")
);
}
#[test]
fn test_resolve_group_by_alias_numeric_position() {
let select_list =
vec![select_item(col("n_name"), Some("nation")), select_item(col("amount"), None)];
let group_expr = Expression::Literal(vibesql_types::SqlValue::Integer(1));
let resolved = resolve_group_by_alias(&group_expr, &select_list, 0).unwrap();
assert!(
matches!(&resolved, Expression::ColumnRef(col_id) if col_id.column_canonical() == "n_name")
);
let group_expr2 = Expression::Literal(vibesql_types::SqlValue::Integer(2));
let resolved2 = resolve_group_by_alias(&group_expr2, &select_list, 1).unwrap();
assert!(
matches!(&resolved2, Expression::ColumnRef(col_id) if col_id.column_canonical() == "amount")
);
}
#[test]
fn test_resolve_group_by_alias_out_of_range() {
let select_list =
vec![select_item(col("n_name"), Some("nation")), select_item(col("amount"), None)];
let group_expr_zero = Expression::Literal(vibesql_types::SqlValue::Integer(0));
let result = resolve_group_by_alias(&group_expr_zero, &select_list, 0);
assert!(matches!(result, Err(ExecutorError::GroupByOutOfRange { .. })));
let group_expr_three = Expression::Literal(vibesql_types::SqlValue::Integer(3));
let result = resolve_group_by_alias(&group_expr_three, &select_list, 0);
assert!(matches!(result, Err(ExecutorError::GroupByOutOfRange { .. })));
}
#[test]
fn test_resolve_group_by_alias_no_match() {
let select_list = vec![select_item(col("n_name"), Some("nation"))];
let group_expr = col("something_else");
let resolved = resolve_group_by_alias(&group_expr, &select_list, 0).unwrap();
assert!(
matches!(&resolved, Expression::ColumnRef(col_id) if col_id.column_canonical() == "something_else")
);
}
#[test]
fn test_resolve_group_by_alias_qualified_column() {
let select_list = vec![select_item(col("n_name"), Some("nation"))];
let group_expr = Expression::ColumnRef(vibesql_ast::ColumnIdentifier::qualified(
"t", false, "nation", false,
));
let resolved = resolve_group_by_alias(&group_expr, &select_list, 0).unwrap();
assert!(
matches!(&resolved, Expression::ColumnRef(col_id) if col_id.table_canonical() == Some("t") && col_id.column_canonical() == "nation")
);
}
#[test]
fn test_resolve_group_by_alias_expression_alias() {
let substr_expr = Expression::Function {
name: vibesql_ast::FunctionIdentifier::new("SUBSTR"),
args: vec![
col("o_orderdate"),
Expression::Literal(vibesql_types::SqlValue::Integer(1)),
Expression::Literal(vibesql_types::SqlValue::Integer(4)),
],
character_unit: None,
};
let select_list = vec![select_item(substr_expr.clone(), Some("o_year"))];
let group_expr = col("o_year");
let resolved = resolve_group_by_alias(&group_expr, &select_list, 0).unwrap();
assert!(matches!(resolved, Expression::Function { name, .. } if name == "SUBSTR"));
}
#[test]
fn test_resolve_grouping_set_aliases() {
let select_list = vec![
select_item(col("n1_n_name"), Some("supp_nation")),
select_item(col("n2_n_name"), Some("cust_nation")),
];
let original_set = ResolvedGroupingSet {
group_by_exprs: vec![col("supp_nation"), col("cust_nation")],
rolled_up: vec![false, false],
};
let resolved_set = resolve_grouping_set_aliases(&original_set, &select_list).unwrap();
assert_eq!(resolved_set.group_by_exprs.len(), 2);
assert!(
matches!(&resolved_set.group_by_exprs[0], Expression::ColumnRef(col_id) if col_id.column_canonical() == "n1_n_name")
);
assert!(
matches!(&resolved_set.group_by_exprs[1], Expression::ColumnRef(col_id) if col_id.column_canonical() == "n2_n_name")
);
assert_eq!(resolved_set.rolled_up, vec![false, false]);
}
#[test]
fn test_resolve_base_expressions_aliases() {
let select_list =
vec![select_item(col("a_col"), Some("a")), select_item(col("b_col"), Some("b"))];
let base_exprs = vec![col("a"), col("b")];
let resolved = resolve_base_expressions_aliases(&base_exprs, &select_list).unwrap();
assert_eq!(resolved.len(), 2);
assert!(
matches!(&resolved[0], Expression::ColumnRef(col_id) if col_id.column_canonical() == "a_col")
);
assert!(
matches!(&resolved[1], Expression::ColumnRef(col_id) if col_id.column_canonical() == "b_col")
);
}
}