use super::builder::SelectExecutor;
fn expression_references_column_inner(expr: &vibesql_ast::Expression, count_pseudo: bool) -> bool {
let expression_references_column =
|e: &vibesql_ast::Expression| expression_references_column_inner(e, count_pseudo);
match expr {
vibesql_ast::Expression::ColumnRef(_) => true,
vibesql_ast::Expression::PseudoVariable { .. } => count_pseudo,
vibesql_ast::Expression::Default => false, vibesql_ast::Expression::DuplicateKeyValue { .. } => false,
vibesql_ast::Expression::BinaryOp { left, right, .. } => {
expression_references_column(left) || expression_references_column(right)
}
vibesql_ast::Expression::UnaryOp { expr, .. } => expression_references_column(expr),
vibesql_ast::Expression::Function { args, .. } => {
args.iter().any(expression_references_column)
}
vibesql_ast::Expression::AggregateFunction { args, .. } => {
args.iter().any(expression_references_column)
}
vibesql_ast::Expression::IsNull { expr, .. } => expression_references_column(expr),
vibesql_ast::Expression::IsDistinctFrom { left, right, .. } => {
expression_references_column(left) || expression_references_column(right)
}
vibesql_ast::Expression::IsTruthValue { expr, .. } => expression_references_column(expr),
vibesql_ast::Expression::InList { expr, values, .. } => {
expression_references_column(expr) || values.iter().any(expression_references_column)
}
vibesql_ast::Expression::Between { expr, low, high, .. } => {
expression_references_column(expr)
|| expression_references_column(low)
|| expression_references_column(high)
}
vibesql_ast::Expression::Cast { expr, .. } => expression_references_column(expr),
vibesql_ast::Expression::Interval { value, .. } => expression_references_column(value),
vibesql_ast::Expression::Position { substring, string, character_unit: _ } => {
expression_references_column(substring) || expression_references_column(string)
}
vibesql_ast::Expression::Trim { removal_char, string, .. } => {
removal_char.as_ref().is_some_and(|e| expression_references_column(e))
|| expression_references_column(string)
}
vibesql_ast::Expression::Extract { expr, .. } => expression_references_column(expr),
vibesql_ast::Expression::Like { expr, pattern, .. }
| vibesql_ast::Expression::Glob { expr, pattern, .. } => {
expression_references_column(expr) || expression_references_column(pattern)
}
vibesql_ast::Expression::In { expr, .. } => {
expression_references_column(expr)
}
vibesql_ast::Expression::QuantifiedComparison { expr, .. } => {
expression_references_column(expr)
}
vibesql_ast::Expression::Case { operand, when_clauses, else_result } => {
operand.as_ref().is_some_and(|e| expression_references_column(e))
|| when_clauses.iter().any(|when_clause| {
when_clause.conditions.iter().any(expression_references_column)
|| expression_references_column(&when_clause.result)
})
|| else_result.as_ref().is_some_and(|e| expression_references_column(e))
}
vibesql_ast::Expression::WindowFunction { function, over } => {
let args_reference_column = match function {
vibesql_ast::WindowFunctionSpec::Aggregate { args, .. }
| vibesql_ast::WindowFunctionSpec::Ranking { args, .. }
| vibesql_ast::WindowFunctionSpec::Value { args, .. } => {
args.iter().any(expression_references_column)
}
};
let partition_references = over
.partition_by
.as_ref()
.is_some_and(|exprs| exprs.iter().any(expression_references_column));
let order_references = over.order_by.as_ref().is_some_and(|items| {
items.iter().any(|item| expression_references_column(&item.expr))
});
args_reference_column || partition_references || order_references
}
vibesql_ast::Expression::Literal(_) => false,
vibesql_ast::Expression::Wildcard => false,
vibesql_ast::Expression::ScalarSubquery(_) => false, vibesql_ast::Expression::Exists { .. } => false, vibesql_ast::Expression::CurrentDate => false,
vibesql_ast::Expression::CurrentTime { .. } => false,
vibesql_ast::Expression::CurrentTimestamp { .. } => false,
vibesql_ast::Expression::NextValue { .. } => false, vibesql_ast::Expression::SessionVariable { .. } => false, vibesql_ast::Expression::MatchAgainst { columns, search_modifier, .. } => {
!columns.is_empty() || expression_references_column(search_modifier)
}
vibesql_ast::Expression::Placeholder(_)
| vibesql_ast::Expression::NumberedPlaceholder(_)
| vibesql_ast::Expression::NamedPlaceholder(_) => false,
vibesql_ast::Expression::Conjunction(children)
| vibesql_ast::Expression::Disjunction(children) => {
children.iter().any(expression_references_column)
}
vibesql_ast::Expression::RowValueConstructor(values) => {
values.iter().any(expression_references_column)
}
vibesql_ast::Expression::Collate { expr, .. } => expression_references_column(expr),
vibesql_ast::Expression::Raise { error_message, .. } => {
error_message.as_ref().is_some_and(|msg| expression_references_column(msg))
}
}
}
impl SelectExecutor<'_> {
pub(super) fn expression_references_column(&self, expr: &vibesql_ast::Expression) -> bool {
expression_references_column_inner(expr, true)
}
pub(super) fn expression_references_non_pseudo_column(
&self,
expr: &vibesql_ast::Expression,
) -> bool {
expression_references_column_inner(expr, false)
}
pub(super) fn eval_limit_offset_expr(
&self,
expr: &vibesql_ast::Expression,
clause_name: &str,
) -> Result<usize, crate::errors::ExecutorError> {
use crate::evaluator::ExpressionEvaluator;
let mut col_refs = Vec::new();
crate::select::executor::validation::extract_column_refs(expr, &mut col_refs);
if let Some(first) = col_refs.into_iter().next() {
let column_ref = match first.table {
Some(t) => format!("{}.{}", t, first.column),
None => first.column,
};
return Err(crate::errors::ExecutorError::NoSuchColumn { column_ref });
}
let empty_schema = vibesql_catalog::TableSchema::new("".to_string(), vec![]);
let evaluator = ExpressionEvaluator::with_database(&empty_schema, self.database);
let empty_row = vibesql_storage::Row::new(vec![]);
let value = evaluator.eval(expr, &empty_row)?;
match value {
vibesql_types::SqlValue::Integer(n) => {
if n < 0 {
if clause_name == "OFFSET" {
Ok(0)
} else {
Ok(usize::MAX)
}
} else {
Ok(n as usize)
}
}
vibesql_types::SqlValue::Null => {
Err(crate::errors::ExecutorError::InvalidLimitOffset {
clause: clause_name.to_string(),
value: "NULL".to_string(),
reason: "must be an integer".to_string(),
})
}
other => Err(crate::errors::ExecutorError::InvalidLimitOffset {
clause: clause_name.to_string(),
value: format!("{:?}", other),
reason: "must be an integer".to_string(),
}),
}
}
}