use super::super::{SQLError, SQLParam, ScalarExpr};
use super::functions::{
validate_qualified_column, validate_unqualified_column, validate_window_function,
};
use crate::ast::RuleEvent;
use crate::routines::RoutineResolution;
use crate::{FunctionTypeResolver, RowSchema, ScalarFrameBound};
#[expect(
clippy::too_many_lines,
reason = "preserves SELECT schema and row identity"
)]
pub(super) fn validate_expression(
rule_event: Option<RuleEvent>,
routines: &dyn RoutineResolution,
expression: &ScalarExpr,
schema: &RowSchema,
fallback: Option<&RowSchema>,
params: &[SQLParam],
resolver: &dyn FunctionTypeResolver,
) -> Result<(), SQLError> {
match expression {
ScalarExpr::Column(column) => validate_unqualified_column(schema, fallback, column),
ScalarExpr::Position(position) => {
if *position < schema.len() {
Ok(())
} else {
Err(SQLError::UnknownColumn((position + 1).to_string()))
}
}
ScalarExpr::InternalColumn(column) => {
if schema.internal_slot(*column).is_some()
|| fallback.is_some_and(|fallback| fallback.internal_slot(*column).is_some())
{
Ok(())
} else {
Err(SQLError::Internal(format!(
"internal relation attribute {column:?} is outside the bound row scope"
)))
}
}
ScalarExpr::QualifiedColumn { qualifier, column } => {
if !schema.has_qualifier(qualifier)
&& !fallback.is_some_and(|schema| schema.has_qualifier(qualifier))
&& rule_event.is_some_and(|event| {
crate::catalog::events::validation::unavailable_rule_row(event, qualifier)
})
{
return Err(
crate::semantics::rules::action_binding::invalid_rule_relation_reference(
qualifier,
),
);
}
validate_qualified_column(schema, fallback, qualifier, column)
}
ScalarExpr::QualifiedStar(qualifier) => {
if schema.has_qualifier(qualifier)
|| fallback.is_some_and(|fallback| fallback.has_qualifier(qualifier))
{
Ok(())
} else if rule_event.is_some_and(|event| {
crate::catalog::events::validation::unavailable_rule_row(event, qualifier)
}) {
Err(
crate::semantics::rules::action_binding::invalid_rule_relation_reference(
qualifier,
),
)
} else {
Err(SQLError::UnknownTable(qualifier.clone()))
}
}
ScalarExpr::Func {
name,
binding,
args,
order_by,
filter,
..
} => {
for (argument_index, argument) in args.iter().enumerate() {
if crate::semantics::is_semantic_field_argument(name, args, argument_index)? {
continue;
}
validate_expression(
rule_event, routines, argument, schema, None, params, resolver,
)?;
}
if let Some(crate::ast::FunctionDispatch::BooleanTest(test)) =
binding.as_ref().and_then(|binding| binding.dispatch)
{
let [argument] = args.as_slice() else {
return Err(SQLError::Internal("Boolean test takes one input".into()));
};
require_boolean_condition(argument, test.label(), schema, params, resolver)?;
return require_scalar_boolean_test(routines, argument, schema, params, test);
}
for order in order_by {
validate_expression(
rule_event,
routines,
&order.expr,
schema,
None,
params,
resolver,
)?;
}
if let Some(filter) = filter.as_deref() {
validate_expression(rule_event, routines, filter, schema, None, params, resolver)?;
require_boolean_condition(filter, "FILTER", schema, params, resolver)?;
}
super::validate_call(routines, expression, schema, params, resolver)
}
ScalarExpr::WindowCall {
name,
args,
spec,
filter,
modifiers,
} => {
for argument in args {
validate_expression(
rule_event, routines, argument, schema, None, params, resolver,
)?;
}
if let Some(filter) = filter.as_deref() {
validate_expression(rule_event, routines, filter, schema, None, params, resolver)?;
require_boolean_condition(filter, "FILTER", schema, params, resolver)?;
}
for expression in &spec.partition_by {
validate_expression(
rule_event, routines, expression, schema, None, params, resolver,
)?;
}
for order in &spec.order_by {
validate_expression(
rule_event,
routines,
&order.expr,
schema,
None,
params,
resolver,
)?;
}
if let Some(frame) = &spec.frame {
for bound in [&frame.start, &frame.end] {
if let ScalarFrameBound::Preceding(expression)
| ScalarFrameBound::Following(expression) = bound
{
validate_expression(
rule_event, routines, expression, schema, None, params, resolver,
)?;
}
}
}
validate_window_function(
routines,
name,
(args, filter.is_some(), *modifiers),
schema,
params,
resolver,
)
}
ScalarExpr::Array(items)
| ScalarExpr::Row(items)
| ScalarExpr::CompositeRow { items, .. } => {
validate_items(rule_event, routines, items, schema, params, resolver)
}
ScalarExpr::And(items) | ScalarExpr::Or(items) => {
let construct = if matches!(expression, ScalarExpr::And(_)) {
"AND"
} else {
"OR"
};
for item in items {
validate_expression(rule_event, routines, item, schema, None, params, resolver)?;
require_boolean_condition(item, construct, schema, params, resolver)?;
}
Ok(())
}
ScalarExpr::Not(inner) => {
validate_expression(rule_event, routines, inner, schema, None, params, resolver)?;
require_boolean_condition(inner, "NOT", schema, params, resolver)
}
ScalarExpr::Binary { lhs, rhs, .. } => {
validate_expression(rule_event, routines, lhs, schema, None, params, resolver)?;
validate_expression(rule_event, routines, rhs, schema, None, params, resolver)
}
ScalarExpr::UnaryMinus(inner)
| ScalarExpr::IsNull { expr: inner, .. }
| ScalarExpr::Cast { expr: inner, .. }
| ScalarExpr::Collated { expr: inner, .. } => {
validate_expression(rule_event, routines, inner, schema, None, params, resolver)
}
ScalarExpr::Between { expr, low, high } => {
for item in [expr.as_ref(), low.as_ref(), high.as_ref()] {
validate_expression(rule_event, routines, item, schema, None, params, resolver)?;
}
Ok(())
}
ScalarExpr::InList { expr, list, .. } => {
validate_expression(rule_event, routines, expr, schema, None, params, resolver)?;
validate_items(rule_event, routines, list, schema, params, resolver)
}
ScalarExpr::Case {
base,
when,
else_branch,
} => {
if let Some(base) = base.as_deref() {
validate_expression(rule_event, routines, base, schema, None, params, resolver)?;
}
for (condition, value) in when {
validate_expression(
rule_event, routines, condition, schema, None, params, resolver,
)?;
if base.is_none() {
require_boolean_condition(condition, "CASE/WHEN", schema, params, resolver)?;
}
validate_expression(rule_event, routines, value, schema, None, params, resolver)?;
}
if let Some(else_branch) = else_branch.as_deref() {
validate_expression(
rule_event,
routines,
else_branch,
schema,
None,
params,
resolver,
)?;
}
Ok(())
}
ScalarExpr::ScalarSubquery(index)
| ScalarExpr::Exists {
subquery: index, ..
} => resolver
.resolve_scalar_subquery_type(*index, schema, params)
.map(drop),
ScalarExpr::InSubquery { expr, subquery, .. } => {
validate_expression(rule_event, routines, expr, schema, None, params, resolver)?;
resolver
.resolve_scalar_subquery_type(*subquery, schema, params)
.map(drop)
}
ScalarExpr::Star
| ScalarExpr::Default
| ScalarExpr::Literal(_)
| ScalarExpr::TypedLiteral { .. }
| ScalarExpr::Param(_) => Ok(()),
}
}
pub(super) fn require_boolean_condition(
condition: &ScalarExpr,
construct: &str,
schema: &RowSchema,
params: &[SQLParam],
resolver: &dyn FunctionTypeResolver,
) -> Result<(), SQLError> {
if let ScalarExpr::Literal(uqa_core::Value::Str(text)) = condition {
return crate::expr::parse_boolean_input(text)
.map(drop)
.ok_or_else(|| crate::expr::invalid_boolean_input(text));
}
if let ScalarExpr::Func { name, .. } = condition {
if crate::registry::is_registered(&crate::semantics::builtin_function_dispatch_name(name)) {
return Ok(());
}
}
let ty = crate::scalar_type_with_resolver(condition, schema, params, resolver)?;
let Some(ty) = crate::effective_overload_argument_type_with_params(condition, ty, params)
else {
return Ok(());
};
crate::type_resolution::boolean::require_argument_type(Some(&ty), construct, Some(resolver))
}
pub(in crate::binding) fn require_scalar_boolean_test(
routines: &dyn RoutineResolution,
argument: &ScalarExpr,
schema: &RowSchema,
params: &[SQLParam],
test: crate::ast::BooleanTest,
) -> Result<(), SQLError> {
if crate::semantics::sets::validation::expression_may_return_set(
routines, routines, argument, schema, params,
)? {
Err(SQLError::Routine {
sqlstate: "42804".into(),
message: format!("argument of {} must not return a set", test.label()),
})
} else {
Ok(())
}
}
fn validate_items(
rule_event: Option<RuleEvent>,
routines: &dyn RoutineResolution,
items: &[ScalarExpr],
schema: &RowSchema,
params: &[SQLParam],
resolver: &dyn FunctionTypeResolver,
) -> Result<(), SQLError> {
for item in items {
validate_expression(rule_event, routines, item, schema, None, params, resolver)?;
}
Ok(())
}