use std::borrow::Cow;
use super::position::{
count_select_columns, extract_column_position, resolve_position_to_column_name,
resolve_position_with_wildcards, validate_column_position, ColumnPositionResult,
ResolvedPosition,
};
use crate::{errors::ExecutorError, schema::CombinedSchema};
pub(crate) fn resolve_order_by_for_aggregates(
order_expr: &vibesql_ast::Expression,
select_list: &[vibesql_ast::SelectItem],
term_index: usize, schema: Option<&CombinedSchema>,
) -> Result<vibesql_ast::Expression, ExecutorError> {
let column_count = count_select_columns(select_list, schema);
match extract_column_position(order_expr) {
ColumnPositionResult::Position(pos) => {
let idx = validate_column_position(pos, column_count, term_index)?;
if let Some(col_name) = resolve_position_to_column_name(idx, select_list) {
return Ok(vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::simple(&col_name, false),
));
}
}
ColumnPositionResult::Negative(pos) => {
return Err(ExecutorError::OrderByOutOfRange {
term_position: term_index + 1,
column_number: -pos,
select_list_len: column_count,
});
}
ColumnPositionResult::NotAPosition => {
}
}
Ok(resolve_order_by_for_aggregates_inner(order_expr, select_list))
}
fn resolve_order_by_for_aggregates_inner(
order_expr: &vibesql_ast::Expression,
select_list: &[vibesql_ast::SelectItem],
) -> vibesql_ast::Expression {
if let vibesql_ast::Expression::Literal(vibesql_types::SqlValue::Integer(pos)) = order_expr {
if *pos > 0 && (*pos as usize) <= select_list.len() {
let idx = (*pos as usize) - 1;
if let vibesql_ast::SelectItem::Expression { expr, alias, .. } = &select_list[idx] {
let col_name = if let Some(alias_name) = alias {
alias_name.clone()
} else if let vibesql_ast::Expression::ColumnRef(col_id) = expr {
col_id.column_canonical().to_string()
} else if let vibesql_ast::Expression::AggregateFunction { name, .. } = expr {
name.to_lowercase()
} else {
format!("col{}", idx + 1)
};
return vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
&col_name, false,
));
}
}
return order_expr.clone();
}
if let vibesql_ast::Expression::ColumnRef(col_id) = order_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 { alias: Some(alias_name), .. } = item {
if alias_name.eq_ignore_ascii_case(column) {
return vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::simple(alias_name, false),
);
}
}
}
for item in select_list {
if let vibesql_ast::SelectItem::Expression {
expr: vibesql_ast::Expression::ColumnRef(select_col_id),
alias: Some(alias_name),
..
} = item
{
if select_col_id.schema_canonical().is_none() {
let select_col = select_col_id.column_canonical();
if select_col.eq_ignore_ascii_case(column) {
return vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::simple(alias_name, false),
);
}
}
}
}
for item in select_list {
if let vibesql_ast::SelectItem::Expression {
expr: vibesql_ast::Expression::ColumnRef(select_col_id),
alias: None,
..
} = item
{
if select_col_id.schema_canonical().is_none() {
let select_col = select_col_id.column_canonical();
if select_col.eq_ignore_ascii_case(column) {
return order_expr.clone();
}
}
}
}
}
}
if let Some(alias) = find_matching_select_expression(order_expr, select_list) {
return vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
&alias, false,
));
}
if let vibesql_ast::Expression::Case { operand, when_clauses, else_result } = order_expr {
let resolved_operand = operand
.as_ref()
.map(|op| Box::new(resolve_order_by_for_aggregates_inner(op, select_list)));
let mut resolved_when_clauses: Vec<vibesql_ast::CaseWhen> = Vec::new();
for clause in when_clauses {
let mut resolved_conditions = Vec::new();
for cond in &clause.conditions {
resolved_conditions.push(resolve_order_by_for_aggregates_inner(cond, select_list));
}
resolved_when_clauses.push(vibesql_ast::CaseWhen {
conditions: resolved_conditions,
result: resolve_order_by_for_aggregates_inner(&clause.result, select_list),
});
}
let resolved_else = else_result
.as_ref()
.map(|e| Box::new(resolve_order_by_for_aggregates_inner(e, select_list)));
return vibesql_ast::Expression::Case {
operand: resolved_operand,
when_clauses: resolved_when_clauses,
else_result: resolved_else,
};
}
if let vibesql_ast::Expression::BinaryOp { left, op, right } = order_expr {
let resolved_left = resolve_order_by_for_aggregates_inner(left, select_list);
let resolved_right = resolve_order_by_for_aggregates_inner(right, select_list);
return vibesql_ast::Expression::BinaryOp {
left: Box::new(resolved_left),
op: *op,
right: Box::new(resolved_right),
};
}
if let vibesql_ast::Expression::Function { name, .. } = order_expr {
if name.eq_ignore_ascii_case("GROUPING") || name.eq_ignore_ascii_case("GROUPING_ID") {
if let Some(alias) = find_matching_select_expression(order_expr, select_list) {
return vibesql_ast::Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
&alias, false,
));
}
}
}
order_expr.clone()
}
fn find_matching_select_expression(
expr: &vibesql_ast::Expression,
select_list: &[vibesql_ast::SelectItem],
) -> Option<String> {
for (idx, item) in select_list.iter().enumerate() {
if let vibesql_ast::SelectItem::Expression { expr: select_expr, alias, .. } = item {
if expressions_equal(expr, select_expr) {
return Some(if let Some(alias_name) = alias {
alias_name.clone()
} else if let vibesql_ast::Expression::ColumnRef(col_id) = select_expr {
col_id.column_canonical().to_string()
} else if let vibesql_ast::Expression::AggregateFunction { name, .. } = select_expr
{
name.to_lowercase()
} else {
format!("col{}", idx + 1)
});
}
}
}
None
}
fn expressions_equal(a: &vibesql_ast::Expression, b: &vibesql_ast::Expression) -> bool {
match (a, b) {
(
vibesql_ast::Expression::ColumnRef(col_id1),
vibesql_ast::Expression::ColumnRef(col_id2),
) => {
col_id1.schema_canonical().is_none()
&& col_id2.schema_canonical().is_none()
&& col_id1.table_canonical() == col_id2.table_canonical()
&& col_id1.column_canonical() == col_id2.column_canonical()
}
(vibesql_ast::Expression::Literal(v1), vibesql_ast::Expression::Literal(v2)) => v1 == v2,
(
vibesql_ast::Expression::BinaryOp { left: l1, op: o1, right: r1 },
vibesql_ast::Expression::BinaryOp { left: l2, op: o2, right: r2 },
) => o1 == o2 && expressions_equal(l1, l2) && expressions_equal(r1, r2),
(
vibesql_ast::Expression::Function { name: n1, args: a1, .. },
vibesql_ast::Expression::Function { name: n2, args: a2, .. },
) => {
n1 == n2 && a1.len() == a2.len()
&& a1.iter().zip(a2.iter()).all(|(x, y)| expressions_equal(x, y))
}
_ => format!("{:?}", a) == format!("{:?}", b),
}
}
pub(crate) fn resolve_order_by_alias<'a>(
order_expr: &'a vibesql_ast::Expression,
select_list: &'a [vibesql_ast::SelectItem],
term_index: usize, schema: Option<&CombinedSchema>,
) -> Result<Cow<'a, vibesql_ast::Expression>, ExecutorError> {
let column_count = count_select_columns(select_list, schema);
match extract_column_position(order_expr) {
ColumnPositionResult::Position(pos) => {
let idx = validate_column_position(pos, column_count, term_index)?;
match resolve_position_with_wildcards(idx, select_list, schema) {
ResolvedPosition::Expression(expr) => {
return Ok(Cow::Borrowed(expr));
}
ResolvedPosition::ColumnName { table, column } => {
let col_id = match table {
Some(table_name) => vibesql_ast::ColumnIdentifier::qualified(
&table_name,
true,
&column,
true,
),
None => vibesql_ast::ColumnIdentifier::simple(&column, false),
};
return Ok(Cow::Owned(vibesql_ast::Expression::ColumnRef(col_id)));
}
ResolvedPosition::NotFound => {
}
}
}
ColumnPositionResult::Negative(pos) => {
return Err(ExecutorError::OrderByOutOfRange {
term_position: term_index + 1,
column_number: -pos,
select_list_len: column_count,
});
}
ColumnPositionResult::NotAPosition => {
}
}
if let vibesql_ast::Expression::ColumnRef(col_id) = order_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(Cow::Borrowed(expr));
}
}
}
let column_exists_in_schema = schema.is_some_and(|s| {
s.table_schemas.values().any(|(_start_idx, table_schema)| {
table_schema.get_column_index(column).is_some()
})
});
if !column_exists_in_schema {
for item in select_list {
if let vibesql_ast::SelectItem::Expression {
expr: vibesql_ast::Expression::ColumnRef(select_col_id),
alias: Some(alias_name),
..
} = item
{
if select_col_id.schema_canonical().is_none() {
let select_col = select_col_id.column_canonical();
if select_col.eq_ignore_ascii_case(column) {
return Ok(Cow::Owned(vibesql_ast::Expression::ColumnRef(
vibesql_ast::ColumnIdentifier::simple(alias_name, false),
)));
}
}
}
}
}
}
}
if let Some(resolved) = resolve_aliases_in_expression(order_expr, select_list) {
return Ok(Cow::Owned(resolved));
}
Ok(Cow::Borrowed(order_expr))
}
fn resolve_aliases_in_expression(
expr: &vibesql_ast::Expression,
select_list: &[vibesql_ast::SelectItem],
) -> Option<vibesql_ast::Expression> {
match expr {
vibesql_ast::Expression::UnaryOp { op, expr: inner } => {
resolve_alias_or_clone(inner, select_list).map(|resolved_inner| {
vibesql_ast::Expression::UnaryOp { op: *op, expr: Box::new(resolved_inner) }
})
}
vibesql_ast::Expression::BinaryOp { left, op, right } => {
let resolved_left = resolve_alias_or_clone(left, select_list);
let resolved_right = resolve_alias_or_clone(right, select_list);
if resolved_left.is_some() || resolved_right.is_some() {
Some(vibesql_ast::Expression::BinaryOp {
left: Box::new(resolved_left.unwrap_or_else(|| left.as_ref().clone())),
op: *op,
right: Box::new(resolved_right.unwrap_or_else(|| right.as_ref().clone())),
})
} else {
None
}
}
vibesql_ast::Expression::Function { name, args, character_unit } => {
let mut any_resolved = false;
let resolved_args: Vec<_> = args
.iter()
.map(|arg| {
if let Some(resolved) = resolve_alias_or_clone(arg, select_list) {
any_resolved = true;
resolved
} else {
arg.clone()
}
})
.collect();
if any_resolved {
Some(vibesql_ast::Expression::Function {
name: name.clone(),
args: resolved_args,
character_unit: character_unit.clone(),
})
} else {
None
}
}
vibesql_ast::Expression::Case { operand, when_clauses, else_result } => {
let mut any_resolved = false;
let resolved_operand = operand.as_ref().map(|op| {
if let Some(resolved) = resolve_alias_or_clone(op, select_list) {
any_resolved = true;
Box::new(resolved)
} else {
op.clone()
}
});
let resolved_when_clauses: Vec<_> = when_clauses
.iter()
.map(|clause| {
let resolved_conditions: Vec<_> = clause
.conditions
.iter()
.map(|cond| {
if let Some(resolved) = resolve_alias_or_clone(cond, select_list) {
any_resolved = true;
resolved
} else {
cond.clone()
}
})
.collect();
let resolved_result = if let Some(resolved) =
resolve_alias_or_clone(&clause.result, select_list)
{
any_resolved = true;
resolved
} else {
clause.result.clone()
};
vibesql_ast::CaseWhen {
conditions: resolved_conditions,
result: resolved_result,
}
})
.collect();
let resolved_else = else_result.as_ref().map(|e| {
if let Some(resolved) = resolve_alias_or_clone(e, select_list) {
any_resolved = true;
Box::new(resolved)
} else {
e.clone()
}
});
if any_resolved {
Some(vibesql_ast::Expression::Case {
operand: resolved_operand,
when_clauses: resolved_when_clauses,
else_result: resolved_else,
})
} else {
None
}
}
vibesql_ast::Expression::IsDistinctFrom { left, right, negated } => {
let resolved_left = resolve_alias_or_clone(left, select_list);
let resolved_right = resolve_alias_or_clone(right, select_list);
if resolved_left.is_some() || resolved_right.is_some() {
Some(vibesql_ast::Expression::IsDistinctFrom {
left: Box::new(resolved_left.unwrap_or_else(|| left.as_ref().clone())),
right: Box::new(resolved_right.unwrap_or_else(|| right.as_ref().clone())),
negated: *negated,
})
} else {
None
}
}
vibesql_ast::Expression::IsNull { expr: inner, negated } => {
resolve_alias_or_clone(inner, select_list).map(|resolved_inner| {
vibesql_ast::Expression::IsNull {
expr: Box::new(resolved_inner),
negated: *negated,
}
})
}
vibesql_ast::Expression::IsTruthValue { expr: inner, truth_value, negated } => {
resolve_alias_or_clone(inner, select_list).map(|resolved_inner| {
vibesql_ast::Expression::IsTruthValue {
expr: Box::new(resolved_inner),
truth_value: *truth_value,
negated: *negated,
}
})
}
vibesql_ast::Expression::Cast { expr: inner, data_type } => {
resolve_alias_or_clone(inner, select_list).map(|resolved_inner| {
vibesql_ast::Expression::Cast {
expr: Box::new(resolved_inner),
data_type: data_type.clone(),
}
})
}
vibesql_ast::Expression::Collate { expr: inner, collation } => {
resolve_alias_or_clone(inner, select_list).map(|resolved_inner| {
vibesql_ast::Expression::Collate {
expr: Box::new(resolved_inner),
collation: collation.clone(),
}
})
}
vibesql_ast::Expression::Between { expr: inner, low, high, negated, symmetric } => {
let resolved_inner = resolve_alias_or_clone(inner, select_list);
let resolved_low = resolve_alias_or_clone(low, select_list);
let resolved_high = resolve_alias_or_clone(high, select_list);
if resolved_inner.is_some() || resolved_low.is_some() || resolved_high.is_some() {
Some(vibesql_ast::Expression::Between {
expr: Box::new(resolved_inner.unwrap_or_else(|| inner.as_ref().clone())),
low: Box::new(resolved_low.unwrap_or_else(|| low.as_ref().clone())),
high: Box::new(resolved_high.unwrap_or_else(|| high.as_ref().clone())),
negated: *negated,
symmetric: *symmetric,
})
} else {
None
}
}
vibesql_ast::Expression::InList { expr: inner, values, negated } => {
let resolved_inner = resolve_alias_or_clone(inner, select_list);
let mut any_resolved = resolved_inner.is_some();
let resolved_values: Vec<_> = values
.iter()
.map(|v| {
if let Some(resolved) = resolve_alias_or_clone(v, select_list) {
any_resolved = true;
resolved
} else {
v.clone()
}
})
.collect();
if any_resolved {
Some(vibesql_ast::Expression::InList {
expr: Box::new(resolved_inner.unwrap_or_else(|| inner.as_ref().clone())),
values: resolved_values,
negated: *negated,
})
} else {
None
}
}
_ => None,
}
}
fn resolve_alias_or_clone(
expr: &vibesql_ast::Expression,
select_list: &[vibesql_ast::SelectItem],
) -> Option<vibesql_ast::Expression> {
if let vibesql_ast::Expression::ColumnRef(col_id) = 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: select_expr,
alias: Some(alias_name),
..
} = item
{
if alias_name.eq_ignore_ascii_case(column) {
return Some(select_expr.clone());
}
}
}
}
}
resolve_aliases_in_expression(expr, select_list)
}
pub(crate) fn resolve_where_aliases(
where_expr: &vibesql_ast::Expression,
select_list: &[vibesql_ast::SelectItem],
) -> vibesql_ast::Expression {
if !select_list_has_aliases(select_list) {
return where_expr.clone();
}
resolve_where_expression_with_schema(where_expr, select_list, &std::collections::HashSet::new())
}
#[inline]
pub(crate) fn select_list_has_aliases(select_list: &[vibesql_ast::SelectItem]) -> bool {
select_list
.iter()
.any(|item| matches!(item, vibesql_ast::SelectItem::Expression { alias: Some(_), .. }))
}
pub(crate) fn resolve_where_aliases_with_schema(
where_expr: &vibesql_ast::Expression,
select_list: &[vibesql_ast::SelectItem],
schema: &CombinedSchema,
) -> vibesql_ast::Expression {
if !select_list_has_aliases(select_list) {
return where_expr.clone();
}
resolve_where_expression_with_schema_ref(where_expr, select_list, schema)
}
fn resolve_where_expression_with_schema_ref(
expr: &vibesql_ast::Expression,
select_list: &[vibesql_ast::SelectItem],
schema: &CombinedSchema,
) -> vibesql_ast::Expression {
use vibesql_ast::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();
if schema.has_column(column) {
return expr.clone();
}
for item in select_list {
if let vibesql_ast::SelectItem::Expression {
expr: Expression::ColumnRef(sel_col_id),
..
} = item
{
if sel_col_id.schema_canonical().is_none()
&& sel_col_id.table_canonical().is_none()
{
let col_name = sel_col_id.column_canonical();
if col_name.eq_ignore_ascii_case(column) {
return expr.clone();
}
}
if sel_col_id.schema_canonical().is_none()
&& sel_col_id.table_canonical().is_some()
{
let col_name = sel_col_id.column_canonical();
if col_name.eq_ignore_ascii_case(column) {
return expr.clone();
}
}
}
}
for item in select_list {
if let vibesql_ast::SelectItem::Expression {
expr: select_expr,
alias: Some(alias_name),
..
} = item
{
if alias_name.eq_ignore_ascii_case(column) {
return select_expr.clone();
}
}
}
expr.clone()
}
Expression::ColumnRef(col_id)
if col_id.schema_canonical().is_none() && col_id.table_canonical().is_some() =>
{
expr.clone()
}
Expression::ColumnRef(col_id) if col_id.schema_canonical().is_some() => expr.clone(),
Expression::ColumnRef(_) => expr.clone(),
Expression::BinaryOp { left, op, right } => Expression::BinaryOp {
left: Box::new(resolve_where_expression_with_schema_ref(left, select_list, schema)),
op: *op,
right: Box::new(resolve_where_expression_with_schema_ref(right, select_list, schema)),
},
Expression::UnaryOp { op, expr: inner } => Expression::UnaryOp {
op: *op,
expr: Box::new(resolve_where_expression_with_schema_ref(inner, select_list, schema)),
},
Expression::Function { name, args, character_unit } => Expression::Function {
name: name.clone(),
args: args
.iter()
.map(|arg| resolve_where_expression_with_schema_ref(arg, select_list, schema))
.collect(),
character_unit: character_unit.clone(),
},
Expression::Case { operand, when_clauses, else_result } => Expression::Case {
operand: operand.as_ref().map(|op| {
Box::new(resolve_where_expression_with_schema_ref(op, select_list, schema))
}),
when_clauses: when_clauses
.iter()
.map(|clause| vibesql_ast::CaseWhen {
conditions: clause
.conditions
.iter()
.map(|cond| {
resolve_where_expression_with_schema_ref(cond, select_list, schema)
})
.collect(),
result: resolve_where_expression_with_schema_ref(
&clause.result,
select_list,
schema,
),
})
.collect(),
else_result: else_result.as_ref().map(|e| {
Box::new(resolve_where_expression_with_schema_ref(e, select_list, schema))
}),
},
Expression::IsNull { expr: inner, negated } => Expression::IsNull {
expr: Box::new(resolve_where_expression_with_schema_ref(inner, select_list, schema)),
negated: *negated,
},
Expression::IsDistinctFrom { left, right, negated } => Expression::IsDistinctFrom {
left: Box::new(resolve_where_expression_with_schema_ref(left, select_list, schema)),
right: Box::new(resolve_where_expression_with_schema_ref(right, select_list, schema)),
negated: *negated,
},
Expression::IsTruthValue { expr: inner, truth_value, negated } => {
Expression::IsTruthValue {
expr: Box::new(resolve_where_expression_with_schema_ref(
inner,
select_list,
schema,
)),
truth_value: *truth_value,
negated: *negated,
}
}
Expression::InList { expr: inner, values, negated } => Expression::InList {
expr: Box::new(resolve_where_expression_with_schema_ref(inner, select_list, schema)),
values: values
.iter()
.map(|v| resolve_where_expression_with_schema_ref(v, select_list, schema))
.collect(),
negated: *negated,
},
Expression::In { expr: inner, subquery, negated } => Expression::In {
expr: Box::new(resolve_where_expression_with_schema_ref(inner, select_list, schema)),
subquery: subquery.clone(),
negated: *negated,
},
Expression::Between { expr: inner, low, high, negated, symmetric } => Expression::Between {
expr: Box::new(resolve_where_expression_with_schema_ref(inner, select_list, schema)),
low: Box::new(resolve_where_expression_with_schema_ref(low, select_list, schema)),
high: Box::new(resolve_where_expression_with_schema_ref(high, select_list, schema)),
negated: *negated,
symmetric: *symmetric,
},
Expression::Like { expr: inner, pattern, negated, escape } => Expression::Like {
expr: Box::new(resolve_where_expression_with_schema_ref(inner, select_list, schema)),
pattern: Box::new(resolve_where_expression_with_schema_ref(
pattern,
select_list,
schema,
)),
negated: *negated,
escape: escape.as_ref().map(|e| {
Box::new(resolve_where_expression_with_schema_ref(e, select_list, schema))
}),
},
Expression::Glob { expr: inner, pattern, negated, escape } => Expression::Glob {
expr: Box::new(resolve_where_expression_with_schema_ref(inner, select_list, schema)),
pattern: Box::new(resolve_where_expression_with_schema_ref(
pattern,
select_list,
schema,
)),
negated: *negated,
escape: escape.as_ref().map(|e| {
Box::new(resolve_where_expression_with_schema_ref(e, select_list, schema))
}),
},
Expression::Cast { expr: inner, data_type } => Expression::Cast {
expr: Box::new(resolve_where_expression_with_schema_ref(inner, select_list, schema)),
data_type: data_type.clone(),
},
Expression::Conjunction(children) => Expression::Conjunction(
children
.iter()
.map(|c| resolve_where_expression_with_schema_ref(c, select_list, schema))
.collect(),
),
Expression::Disjunction(children) => Expression::Disjunction(
children
.iter()
.map(|c| resolve_where_expression_with_schema_ref(c, select_list, schema))
.collect(),
),
Expression::AggregateFunction { name, args, distinct, order_by, filter } => {
Expression::AggregateFunction {
name: name.clone(),
args: args
.iter()
.map(|arg| resolve_where_expression_with_schema_ref(arg, select_list, schema))
.collect(),
distinct: *distinct,
order_by: order_by.as_ref().map(|items| {
items
.iter()
.map(|item| vibesql_ast::OrderByItem {
expr: resolve_where_expression_with_schema_ref(
&item.expr,
select_list,
schema,
),
direction: item.direction.clone(),
nulls_order: item.nulls_order,
})
.collect()
}),
filter: filter.as_ref().map(|f| {
Box::new(resolve_where_expression_with_schema_ref(f, select_list, schema))
}),
}
}
Expression::RowValueConstructor(children) => Expression::RowValueConstructor(
children
.iter()
.map(|c| resolve_where_expression_with_schema_ref(c, select_list, schema))
.collect(),
),
Expression::Collate { expr, collation } => Expression::Collate {
expr: Box::new(resolve_where_expression_with_schema_ref(expr, select_list, schema)),
collation: collation.clone(),
},
_ => expr.clone(),
}
}
fn resolve_where_expression_with_schema(
expr: &vibesql_ast::Expression,
select_list: &[vibesql_ast::SelectItem],
table_columns: &std::collections::HashSet<String>,
) -> vibesql_ast::Expression {
use vibesql_ast::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();
if table_columns.contains(&column.to_lowercase()) {
return expr.clone();
}
for item in select_list {
if let vibesql_ast::SelectItem::Expression {
expr: Expression::ColumnRef(sel_col_id),
..
} = item
{
if sel_col_id.schema_canonical().is_none()
&& sel_col_id.table_canonical().is_none()
{
let col_name = sel_col_id.column_canonical();
if col_name.eq_ignore_ascii_case(column) {
return expr.clone();
}
}
if sel_col_id.schema_canonical().is_none()
&& sel_col_id.table_canonical().is_some()
{
let col_name = sel_col_id.column_canonical();
if col_name.eq_ignore_ascii_case(column) {
return expr.clone();
}
}
}
}
for item in select_list {
if let vibesql_ast::SelectItem::Expression {
expr: select_expr,
alias: Some(alias_name),
..
} = item
{
if alias_name.eq_ignore_ascii_case(column) {
return select_expr.clone();
}
}
}
expr.clone()
}
Expression::ColumnRef(col_id)
if col_id.schema_canonical().is_none() && col_id.table_canonical().is_some() =>
{
expr.clone()
}
Expression::ColumnRef(col_id) if col_id.schema_canonical().is_some() => expr.clone(),
Expression::ColumnRef(_) => expr.clone(),
Expression::BinaryOp { left, op, right } => Expression::BinaryOp {
left: Box::new(resolve_where_expression_with_schema(left, select_list, table_columns)),
op: *op,
right: Box::new(resolve_where_expression_with_schema(
right,
select_list,
table_columns,
)),
},
Expression::UnaryOp { op, expr: inner } => Expression::UnaryOp {
op: *op,
expr: Box::new(resolve_where_expression_with_schema(inner, select_list, table_columns)),
},
Expression::Function { name, args, character_unit } => Expression::Function {
name: name.clone(),
args: args
.iter()
.map(|arg| resolve_where_expression_with_schema(arg, select_list, table_columns))
.collect(),
character_unit: character_unit.clone(),
},
Expression::Case { operand, when_clauses, else_result } => Expression::Case {
operand: operand.as_ref().map(|op| {
Box::new(resolve_where_expression_with_schema(op, select_list, table_columns))
}),
when_clauses: when_clauses
.iter()
.map(|clause| vibesql_ast::CaseWhen {
conditions: clause
.conditions
.iter()
.map(|cond| {
resolve_where_expression_with_schema(cond, select_list, table_columns)
})
.collect(),
result: resolve_where_expression_with_schema(
&clause.result,
select_list,
table_columns,
),
})
.collect(),
else_result: else_result.as_ref().map(|e| {
Box::new(resolve_where_expression_with_schema(e, select_list, table_columns))
}),
},
Expression::IsNull { expr: inner, negated } => Expression::IsNull {
expr: Box::new(resolve_where_expression_with_schema(inner, select_list, table_columns)),
negated: *negated,
},
Expression::IsDistinctFrom { left, right, negated } => Expression::IsDistinctFrom {
left: Box::new(resolve_where_expression_with_schema(left, select_list, table_columns)),
right: Box::new(resolve_where_expression_with_schema(
right,
select_list,
table_columns,
)),
negated: *negated,
},
Expression::IsTruthValue { expr: inner, truth_value, negated } => {
Expression::IsTruthValue {
expr: Box::new(resolve_where_expression_with_schema(
inner,
select_list,
table_columns,
)),
truth_value: *truth_value,
negated: *negated,
}
}
Expression::InList { expr: inner, values, negated } => Expression::InList {
expr: Box::new(resolve_where_expression_with_schema(inner, select_list, table_columns)),
values: values
.iter()
.map(|v| resolve_where_expression_with_schema(v, select_list, table_columns))
.collect(),
negated: *negated,
},
Expression::In { expr: inner, subquery, negated } => Expression::In {
expr: Box::new(resolve_where_expression_with_schema(inner, select_list, table_columns)),
subquery: subquery.clone(),
negated: *negated,
},
Expression::Between { expr: inner, low, high, negated, symmetric } => Expression::Between {
expr: Box::new(resolve_where_expression_with_schema(inner, select_list, table_columns)),
low: Box::new(resolve_where_expression_with_schema(low, select_list, table_columns)),
high: Box::new(resolve_where_expression_with_schema(high, select_list, table_columns)),
negated: *negated,
symmetric: *symmetric,
},
Expression::Like { expr: inner, pattern, negated, escape } => Expression::Like {
expr: Box::new(resolve_where_expression_with_schema(inner, select_list, table_columns)),
pattern: Box::new(resolve_where_expression_with_schema(
pattern,
select_list,
table_columns,
)),
negated: *negated,
escape: escape.as_ref().map(|e| {
Box::new(resolve_where_expression_with_schema(e, select_list, table_columns))
}),
},
Expression::Glob { expr: inner, pattern, negated, escape } => Expression::Glob {
expr: Box::new(resolve_where_expression_with_schema(inner, select_list, table_columns)),
pattern: Box::new(resolve_where_expression_with_schema(
pattern,
select_list,
table_columns,
)),
negated: *negated,
escape: escape.as_ref().map(|e| {
Box::new(resolve_where_expression_with_schema(e, select_list, table_columns))
}),
},
Expression::Cast { expr: inner, data_type } => Expression::Cast {
expr: Box::new(resolve_where_expression_with_schema(inner, select_list, table_columns)),
data_type: data_type.clone(),
},
Expression::Conjunction(children) => Expression::Conjunction(
children
.iter()
.map(|c| resolve_where_expression_with_schema(c, select_list, table_columns))
.collect(),
),
Expression::Disjunction(children) => Expression::Disjunction(
children
.iter()
.map(|c| resolve_where_expression_with_schema(c, select_list, table_columns))
.collect(),
),
Expression::AggregateFunction { name, args, distinct, order_by, filter } => {
Expression::AggregateFunction {
name: name.clone(),
args: args
.iter()
.map(|arg| {
resolve_where_expression_with_schema(arg, select_list, table_columns)
})
.collect(),
distinct: *distinct,
order_by: order_by.as_ref().map(|items| {
items
.iter()
.map(|item| vibesql_ast::OrderByItem {
expr: resolve_where_expression_with_schema(
&item.expr,
select_list,
table_columns,
),
direction: item.direction.clone(),
nulls_order: item.nulls_order,
})
.collect()
}),
filter: filter.as_ref().map(|f| {
Box::new(resolve_where_expression_with_schema(f, select_list, table_columns))
}),
}
}
Expression::RowValueConstructor(children) => Expression::RowValueConstructor(
children
.iter()
.map(|c| resolve_where_expression_with_schema(c, select_list, table_columns))
.collect(),
),
Expression::Collate { expr, collation } => Expression::Collate {
expr: Box::new(resolve_where_expression_with_schema(expr, select_list, table_columns)),
collation: collation.clone(),
},
Expression::Trim { position, removal_char, string } => Expression::Trim {
position: position.clone(),
removal_char: removal_char.as_ref().map(|c| {
Box::new(resolve_where_expression_with_schema(c, select_list, table_columns))
}),
string: Box::new(resolve_where_expression_with_schema(
string,
select_list,
table_columns,
)),
},
Expression::Position { substring, string, character_unit } => Expression::Position {
substring: Box::new(resolve_where_expression_with_schema(
substring,
select_list,
table_columns,
)),
string: Box::new(resolve_where_expression_with_schema(
string,
select_list,
table_columns,
)),
character_unit: character_unit.clone(),
},
Expression::Extract { field, expr: inner } => Expression::Extract {
field: field.clone(),
expr: Box::new(resolve_where_expression_with_schema(inner, select_list, table_columns)),
},
Expression::Interval { value, unit, leading_precision, fractional_precision } => {
Expression::Interval {
value: Box::new(resolve_where_expression_with_schema(
value,
select_list,
table_columns,
)),
unit: unit.clone(),
leading_precision: *leading_precision,
fractional_precision: *fractional_precision,
}
}
Expression::QuantifiedComparison { expr: inner, op, quantifier, subquery } => {
Expression::QuantifiedComparison {
expr: Box::new(resolve_where_expression_with_schema(
inner,
select_list,
table_columns,
)),
op: *op,
quantifier: quantifier.clone(),
subquery: subquery.clone(),
}
}
Expression::Raise { action, error_message } => Expression::Raise {
action: *action,
error_message: error_message.as_ref().map(|msg| {
Box::new(resolve_where_expression_with_schema(msg, select_list, table_columns))
}),
},
Expression::Literal(_)
| Expression::Wildcard
| Expression::CurrentDate
| Expression::CurrentTime { .. }
| Expression::CurrentTimestamp { .. }
| Expression::Default
| Expression::NextValue { .. }
| Expression::SessionVariable { .. }
| Expression::Placeholder(_)
| Expression::NumberedPlaceholder(_)
| Expression::NamedPlaceholder(_)
| Expression::PseudoVariable { .. }
| Expression::DuplicateKeyValue { .. }
| Expression::ScalarSubquery(_)
| Expression::Exists { .. }
| Expression::WindowFunction { .. }
| Expression::MatchAgainst { .. } => expr.clone(),
}
}
pub(crate) fn extract_order_by_aggregates(
order_by: &[vibesql_ast::OrderByItem],
) -> Vec<vibesql_ast::Expression> {
let mut aggregates = Vec::new();
for item in order_by {
collect_aggregates_from_expr(&item.expr, &mut aggregates);
}
aggregates
}
pub(crate) fn extract_window_aggregates(
select_list: &[vibesql_ast::SelectItem],
) -> Vec<vibesql_ast::Expression> {
use vibesql_ast::SelectItem;
let mut aggregates = Vec::new();
for item in select_list {
if let SelectItem::Expression { expr, .. } = item {
collect_window_over_aggregates_from_expr(expr, &mut aggregates);
}
}
aggregates
}
fn collect_window_over_aggregates_from_expr(
expr: &vibesql_ast::Expression,
aggregates: &mut Vec<vibesql_ast::Expression>,
) {
use vibesql_ast::Expression;
match expr {
Expression::WindowFunction { function, over } => {
let args = match function {
vibesql_ast::WindowFunctionSpec::Aggregate { args, .. }
| vibesql_ast::WindowFunctionSpec::Ranking { args, .. }
| vibesql_ast::WindowFunctionSpec::Value { args, .. } => args,
};
for arg in args {
collect_aggregates_from_expr(arg, aggregates);
}
if let Some(partition_by) = &over.partition_by {
for p_expr in partition_by {
collect_aggregates_from_expr(p_expr, aggregates);
}
}
if let Some(order_by) = &over.order_by {
for ob_item in order_by {
collect_aggregates_from_expr(&ob_item.expr, aggregates);
}
}
if let Some(frame) = &over.frame {
if let vibesql_ast::FrameBound::Preceding(e)
| vibesql_ast::FrameBound::Following(e) = &frame.start
{
collect_aggregates_from_expr(e, aggregates);
}
if let Some(end) = &frame.end {
if let vibesql_ast::FrameBound::Preceding(e)
| vibesql_ast::FrameBound::Following(e) = end
{
collect_aggregates_from_expr(e, aggregates);
}
}
}
}
Expression::BinaryOp { left, right, .. } => {
collect_window_over_aggregates_from_expr(left, aggregates);
collect_window_over_aggregates_from_expr(right, aggregates);
}
Expression::UnaryOp { expr: inner, .. }
| Expression::Cast { expr: inner, .. }
| Expression::IsNull { expr: inner, .. }
| Expression::IsTruthValue { expr: inner, .. }
| Expression::Collate { expr: inner, .. } => {
collect_window_over_aggregates_from_expr(inner, aggregates);
}
Expression::IsDistinctFrom { left, right, .. } => {
collect_window_over_aggregates_from_expr(left, aggregates);
collect_window_over_aggregates_from_expr(right, aggregates);
}
Expression::Function { args, .. } => {
for arg in args {
collect_window_over_aggregates_from_expr(arg, aggregates);
}
}
Expression::Case { operand, when_clauses, else_result } => {
if let Some(op) = operand {
collect_window_over_aggregates_from_expr(op, aggregates);
}
for when_clause in when_clauses {
for cond in &when_clause.conditions {
collect_window_over_aggregates_from_expr(cond, aggregates);
}
collect_window_over_aggregates_from_expr(&when_clause.result, aggregates);
}
if let Some(else_expr) = else_result {
collect_window_over_aggregates_from_expr(else_expr, aggregates);
}
}
Expression::Between { expr, low, high, .. } => {
collect_window_over_aggregates_from_expr(expr, aggregates);
collect_window_over_aggregates_from_expr(low, aggregates);
collect_window_over_aggregates_from_expr(high, aggregates);
}
Expression::InList { expr, values, .. } => {
collect_window_over_aggregates_from_expr(expr, aggregates);
for item in values {
collect_window_over_aggregates_from_expr(item, aggregates);
}
}
Expression::Like { expr, pattern, .. } | Expression::Glob { expr, pattern, .. } => {
collect_window_over_aggregates_from_expr(expr, aggregates);
collect_window_over_aggregates_from_expr(pattern, aggregates);
}
Expression::Conjunction(exprs)
| Expression::Disjunction(exprs)
| Expression::RowValueConstructor(exprs) => {
for e in exprs {
collect_window_over_aggregates_from_expr(e, aggregates);
}
}
Expression::In { expr, .. } | Expression::QuantifiedComparison { expr, .. } => {
collect_window_over_aggregates_from_expr(expr, aggregates);
}
_ => {}
}
}
fn collect_aggregates_from_expr(
expr: &vibesql_ast::Expression,
aggregates: &mut Vec<vibesql_ast::Expression>,
) {
use vibesql_ast::Expression;
match expr {
Expression::AggregateFunction { .. } => {
let already_exists = aggregates
.iter()
.any(|existing| format!("{:?}", existing) == format!("{:?}", expr));
if !already_exists {
aggregates.push(expr.clone());
}
}
Expression::BinaryOp { left, right, .. } => {
collect_aggregates_from_expr(left, aggregates);
collect_aggregates_from_expr(right, aggregates);
}
Expression::UnaryOp { expr: inner, .. } => {
collect_aggregates_from_expr(inner, aggregates);
}
Expression::Function { args, .. } => {
for arg in args {
collect_aggregates_from_expr(arg, aggregates);
}
}
Expression::Case { operand, when_clauses, else_result } => {
if let Some(op) = operand {
collect_aggregates_from_expr(op, aggregates);
}
for when_clause in when_clauses {
for cond in &when_clause.conditions {
collect_aggregates_from_expr(cond, aggregates);
}
collect_aggregates_from_expr(&when_clause.result, aggregates);
}
if let Some(else_expr) = else_result {
collect_aggregates_from_expr(else_expr, aggregates);
}
}
Expression::Cast { expr: inner, .. } => {
collect_aggregates_from_expr(inner, aggregates);
}
Expression::Between { expr, low, high, .. } => {
collect_aggregates_from_expr(expr, aggregates);
collect_aggregates_from_expr(low, aggregates);
collect_aggregates_from_expr(high, aggregates);
}
Expression::InList { expr, values, .. } => {
collect_aggregates_from_expr(expr, aggregates);
for item in values {
collect_aggregates_from_expr(item, aggregates);
}
}
Expression::Literal(_)
| Expression::ColumnRef(_)
| Expression::Wildcard
| Expression::CurrentDate
| Expression::CurrentTime { .. }
| Expression::CurrentTimestamp { .. }
| Expression::Default
| Expression::NextValue { .. }
| Expression::Placeholder(_)
| Expression::NumberedPlaceholder(_)
| Expression::NamedPlaceholder(_)
| Expression::PseudoVariable { .. }
| Expression::DuplicateKeyValue { .. }
| Expression::SessionVariable { .. } => {}
Expression::ScalarSubquery(_)
| Expression::Exists { .. }
| Expression::In { .. }
| Expression::QuantifiedComparison { .. }
| Expression::WindowFunction { .. } => {}
Expression::Like { expr, pattern, .. } | Expression::Glob { expr, pattern, .. } => {
collect_aggregates_from_expr(expr, aggregates);
collect_aggregates_from_expr(pattern, aggregates);
}
Expression::IsNull { expr, .. } => {
collect_aggregates_from_expr(expr, aggregates);
}
Expression::IsDistinctFrom { left, right, .. } => {
collect_aggregates_from_expr(left, aggregates);
collect_aggregates_from_expr(right, aggregates);
}
Expression::IsTruthValue { expr, .. } => {
collect_aggregates_from_expr(expr, aggregates);
}
Expression::Conjunction(terms) | Expression::Disjunction(terms) => {
for term in terms {
collect_aggregates_from_expr(term, aggregates);
}
}
Expression::RowValueConstructor(items) => {
for item in items {
collect_aggregates_from_expr(item, aggregates);
}
}
Expression::Position { substring, string, .. } => {
collect_aggregates_from_expr(substring, aggregates);
collect_aggregates_from_expr(string, aggregates);
}
Expression::Trim { string, removal_char, .. } => {
collect_aggregates_from_expr(string, aggregates);
if let Some(c) = removal_char {
collect_aggregates_from_expr(c, aggregates);
}
}
Expression::Extract { expr, .. } => {
collect_aggregates_from_expr(expr, aggregates);
}
Expression::Interval { value, .. } => {
collect_aggregates_from_expr(value, aggregates);
}
Expression::Collate { expr: inner, .. } => {
collect_aggregates_from_expr(inner, aggregates);
}
Expression::Raise { error_message, .. } => {
if let Some(msg) = error_message {
collect_aggregates_from_expr(msg, aggregates);
}
}
Expression::MatchAgainst { .. } => {}
}
}