Skip to main content

uqa_sql/ir/
call_arguments.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Named and explicit variadic SQL argument validation.
8
9use super::{ScalarExpr, ScalarOrder};
10use crate::ast::{FunctionBinding, FunctionDispatch};
11use crate::SQLError;
12use uqa_core::{
13    memory::{Produced, ProductionControl, ProductionVec},
14    Value,
15};
16
17/// A SQL call argument after removing the compiler's named and explicit `VARIADIC` syntax markers.
18#[doc(hidden)]
19#[derive(Debug, Clone, Copy, PartialEq)]
20pub struct ScalarCallArgument<'a> {
21    pub name: Option<&'a str>,
22    pub value: &'a ScalarExpr,
23    pub explicit_variadic: bool,
24}
25
26/// Decode and validate all compiler-owned call-argument markers. `PostgreSQL` permits one explicit `VARIADIC` argument and requires it to be the final argument.
27#[doc(hidden)]
28pub fn scalar_call_arguments(
29    arguments: &[ScalarExpr],
30) -> Result<Vec<ScalarCallArgument<'_>>, SQLError> {
31    scalar_call_arguments_with_control(arguments, &ProductionControl::uncontrolled()).map(
32        |decoded| {
33            decoded
34                .into_uncontrolled()
35                .expect("ordinary call argument decoding has no reservation")
36        },
37    )
38}
39
40/// Decode the same borrowed markers into an admitted temporary container. Names and expression nodes remain borrowed from the input IR owner.
41pub fn scalar_call_arguments_with_control<'a>(
42    arguments: &'a [ScalarExpr],
43    control: &ProductionControl<'_>,
44) -> Result<Produced<Vec<ScalarCallArgument<'a>>>, SQLError> {
45    let mut decoded = ProductionVec::new(*control);
46    decoded.reserve(arguments.len())?;
47    for argument in arguments {
48        control.check()?;
49        decoded.push_copy(scalar_call_argument(argument)?)?;
50    }
51    validate_scalar_call_arguments(&decoded)?;
52    decoded.finish().map_err(Into::into)
53}
54
55/// Validate cross-argument invariants after individual syntax markers have been decoded, returning whether the call used explicit `VARIADIC` syntax.
56#[doc(hidden)]
57pub fn validate_scalar_call_arguments(
58    arguments: &[ScalarCallArgument<'_>],
59) -> Result<bool, SQLError> {
60    let mut count = 0;
61    let mut last_position = None;
62    for (position, argument) in arguments.iter().enumerate() {
63        if argument.explicit_variadic {
64            count += 1;
65            last_position = Some(position);
66        }
67    }
68    if count > 1 {
69        return Err(malformed_call_argument(
70            "call contains more than one explicit VARIADIC argument",
71        ));
72    }
73    if last_position.is_some_and(|position| position + 1 != arguments.len()) {
74        return Err(malformed_call_argument(
75            "explicit VARIADIC argument must be the final call argument",
76        ));
77    }
78    Ok(count != 0)
79}
80
81/// Decode one compiler-owned call-argument marker. Use [`scalar_call_arguments`] for a complete call so duplicate and ordering invariants are also checked.
82#[doc(hidden)]
83pub fn scalar_call_argument(expression: &ScalarExpr) -> Result<ScalarCallArgument<'_>, SQLError> {
84    let ScalarExpr::Func {
85        name,
86        args,
87        binding,
88        distinct,
89        order_by,
90        filter,
91    } = expression
92    else {
93        return Ok(ScalarCallArgument {
94            name: None,
95            value: expression,
96            explicit_variadic: false,
97        });
98    };
99    if binding.as_ref().and_then(|binding| binding.dispatch)
100        == Some(FunctionDispatch::NamedArgument)
101    {
102        validate_marker_shape(
103            binding.as_ref(),
104            FunctionDispatch::NamedArgument,
105            *distinct,
106            order_by,
107            filter.as_deref(),
108            name,
109        )?;
110        let [ScalarExpr::Literal(Value::Str(argument_name)), value] = args.as_slice() else {
111            return Err(malformed_call_argument(
112                "named argument marker must contain a string name and one value",
113            ));
114        };
115        let (value, explicit_variadic) = direct_variadic_argument(value)?;
116        if !explicit_variadic
117            && matches!(
118                value,
119                ScalarExpr::Func { binding, .. }
120                    if binding.as_ref().and_then(|binding| binding.dispatch)
121                        == Some(FunctionDispatch::NamedArgument)
122            )
123        {
124            return Err(malformed_call_argument(
125                "call argument contains nested syntax markers",
126            ));
127        }
128        return Ok(ScalarCallArgument {
129            name: Some(argument_name),
130            value,
131            explicit_variadic,
132        });
133    }
134    let (value, explicit_variadic) = direct_variadic_argument(expression)?;
135    Ok(ScalarCallArgument {
136        name: None,
137        value,
138        explicit_variadic,
139    })
140}
141
142fn direct_variadic_argument(expression: &ScalarExpr) -> Result<(&ScalarExpr, bool), SQLError> {
143    let ScalarExpr::Func {
144        name,
145        args,
146        binding,
147        distinct,
148        order_by,
149        filter,
150    } = expression
151    else {
152        return Ok((expression, false));
153    };
154    if binding.as_ref().and_then(|binding| binding.dispatch)
155        != Some(FunctionDispatch::VariadicArgument)
156    {
157        return Ok((expression, false));
158    }
159    validate_marker_shape(
160        binding.as_ref(),
161        FunctionDispatch::VariadicArgument,
162        *distinct,
163        order_by,
164        filter.as_deref(),
165        name,
166    )?;
167    let [value] = args.as_slice() else {
168        return Err(malformed_call_argument(
169            "VARIADIC argument marker must contain exactly one value",
170        ));
171    };
172    if matches!(
173        value,
174        ScalarExpr::Func { binding, .. }
175            if matches!(
176                binding.as_ref().and_then(|binding| binding.dispatch),
177                Some(FunctionDispatch::VariadicArgument | FunctionDispatch::NamedArgument)
178            )
179    ) {
180        return Err(malformed_call_argument(
181            "call argument contains nested syntax markers",
182        ));
183    }
184    Ok((value, true))
185}
186
187fn validate_marker_shape(
188    binding: Option<&FunctionBinding>,
189    expected_dispatch: FunctionDispatch,
190    distinct: bool,
191    order_by: &[ScalarOrder],
192    filter: Option<&ScalarExpr>,
193    name: &str,
194) -> Result<(), SQLError> {
195    if binding.is_none_or(|binding| {
196        !binding.builtin
197            || binding.dispatch != Some(expected_dispatch)
198            || !binding.argument_types.is_empty()
199            || binding.invocation.is_some()
200            || binding.resolution_error.is_some()
201    }) || distinct
202        || !order_by.is_empty()
203        || filter.is_some()
204    {
205        return Err(malformed_call_argument(&format!(
206            "{name} syntax marker contains function-call metadata"
207        )));
208    }
209    Ok(())
210}
211
212fn malformed_call_argument(message: &str) -> SQLError {
213    SQLError::Internal(format!("malformed call argument: {message}"))
214}
215
216/// Decode SQL call markers carried by expression plans without evaluating arguments.
217pub fn analyze_expression_call_arguments(
218    arguments: &[crate::plan::ExpressionPlan],
219) -> Result<(Vec<ScalarCallArgument<'_>>, bool), SQLError> {
220    let decoded = arguments
221        .iter()
222        .map(|argument| scalar_call_argument(&argument.scalar))
223        .collect::<Result<Vec<_>, _>>()?;
224    let explicit_variadic = validate_scalar_call_arguments(&decoded)?;
225    Ok((decoded, explicit_variadic))
226}
227
228#[cfg(test)]
229mod production_tests;