Skip to main content

uqa_sql/prepared/
arguments.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Static EXECUTE argument shape, assignment compatibility, and safe constant evaluation.
8
9use super::error;
10use crate::{ast::FunctionVolatility, plan::ExpressionPlan, ColumnType, SQLError, ScalarExpr};
11
12pub struct ArgumentValidationContext<'a> {
13    pub aggregates: &'a dyn crate::plan::AggregateClassifier,
14    pub volatility: &'a dyn crate::semantics::volatility::VolatilityCatalog,
15    pub cast_type: &'a dyn Fn(&str) -> Option<ColumnType>,
16}
17
18pub fn validate_assignment_type(
19    index: usize,
20    source: &ColumnType,
21    target: &ColumnType,
22) -> Result<(), SQLError> {
23    if !crate::assignment_type_compatible(source, target) {
24        return Err(error(
25            "42804",
26            format!(
27                "parameter ${} of type {} cannot be coerced to the expected type {}",
28                index + 1,
29                source.sql_name(),
30                target.sql_name()
31            ),
32        ));
33    }
34    Ok(())
35}
36
37pub fn parameter_base_type(mut ty: &ColumnType) -> &ColumnType {
38    while let ColumnType::Domain { base, .. } = ty {
39        ty = base;
40    }
41    ty
42}
43
44pub fn contains_domain(ty: &ColumnType) -> bool {
45    match ty {
46        ColumnType::Domain { .. } => true,
47        ColumnType::Array(element) => contains_domain(element),
48        _ => false,
49    }
50}
51
52pub fn immutable_argument(
53    context: &ArgumentValidationContext<'_>,
54    expression: &ScalarExpr,
55) -> bool {
56    let mut immutable = true;
57    expression.visit(&mut |expression| match expression {
58        ScalarExpr::Param(_)
59        | ScalarExpr::Column(_)
60        | ScalarExpr::QualifiedColumn { .. }
61        | ScalarExpr::InternalColumn(_)
62        | ScalarExpr::Position(_) => immutable = false,
63        ScalarExpr::Func {
64            name,
65            binding,
66            args,
67            ..
68        } => {
69            immutable &= crate::semantics::volatility::function_volatility_with_binding(
70                context.volatility,
71                name,
72                binding.as_ref(),
73                args.len(),
74            ) == FunctionVolatility::Immutable;
75        }
76        ScalarExpr::Cast { ty, .. } => {
77            immutable &= !(context.cast_type)(ty)
78                .as_ref()
79                .is_some_and(contains_domain);
80        }
81        _ => {}
82    });
83    immutable
84}
85
86pub fn validate_argument(
87    aggregates: &dyn crate::plan::AggregateClassifier,
88    argument: &ExpressionPlan,
89) -> Result<(), SQLError> {
90    if !argument.subqueries.is_empty() {
91        return Err(error("0A000", "cannot use subquery in EXECUTE parameter"));
92    }
93    if argument.scalar.contains_window() {
94        return Err(error(
95            "42P20",
96            "window functions are not allowed in EXECUTE parameters",
97        ));
98    }
99    if crate::semantics::aggregates::contains_aggregate(aggregates, &argument.scalar) {
100        return Err(error(
101            "42803",
102            "aggregate functions are not allowed in EXECUTE parameters",
103        ));
104    }
105    Ok(())
106}