Skip to main content

uqa_sql/expr/
call_arguments.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Named, positional, and explicit variadic call-argument normalization.
8
9use std::borrow::Cow;
10
11use uqa_core::{
12    memory::{Produced, ProductionControl, ProductionString, ProductionVec},
13    Value,
14};
15
16use crate::ast::{Expr, FunctionBinding, FunctionDispatch};
17use crate::error::{Result, SQLError};
18
19use super::context::EvalContext;
20use super::evaluator::eval;
21
22pub(super) fn normalized_function_name(name: &str) -> Cow<'_, str> {
23    normalized_function_name_with_control(name, &ProductionControl::uncontrolled())
24        .expect("ordinary function name normalization")
25        .into_uncontrolled()
26        .expect("ordinary function name has no retained owner")
27}
28
29pub(super) fn normalized_function_name_with_control<'a>(
30    name: &'a str,
31    control: &ProductionControl<'_>,
32) -> Result<Produced<Cow<'a, str>>> {
33    control.check()?;
34    let stripped = name.strip_prefix("pg_catalog.").unwrap_or(name);
35    if !stripped.bytes().any(|byte| byte.is_ascii_uppercase()) {
36        return control
37            .finish(Cow::Borrowed(stripped), control.empty_reservation())
38            .map_err(Into::into);
39    }
40    let mut output = ProductionString::new(*control);
41    output.reserve(stripped.len())?;
42    for character in stripped.chars() {
43        output.push(character.to_ascii_lowercase())?;
44    }
45    let (output, memory) = output.finish()?.into_parts();
46    control
47        .finish(Cow::Owned(output), memory)
48        .map_err(Into::into)
49}
50
51fn binding_dispatch(binding: Option<&FunctionBinding>) -> Option<FunctionDispatch> {
52    binding.and_then(|binding| binding.dispatch)
53}
54
55fn direct_variadic_argument_value(argument: &Expr) -> Option<&Expr> {
56    let Expr::Func { binding, args, .. } = argument else {
57        return None;
58    };
59    if binding_dispatch(binding.as_ref()) != Some(FunctionDispatch::VariadicArgument) {
60        return None;
61    }
62    let [value] = args.as_slice() else {
63        return None;
64    };
65    Some(value)
66}
67
68fn named_argument_value(argument: &Expr) -> Option<&Expr> {
69    let Expr::Func { binding, args, .. } = argument else {
70        return None;
71    };
72    if binding_dispatch(binding.as_ref()) == Some(FunctionDispatch::NamedArgument) {
73        args.get(1)
74    } else {
75        None
76    }
77}
78
79/// Wrap the last actual argument of an explicit `VARIADIC` invocation while preserving a named-argument marker at the top level.
80#[must_use]
81pub fn wrap_variadic_argument(mut argument: Expr) -> Expr {
82    if variadic_argument_value(&argument).is_some() {
83        return argument;
84    }
85    if let Expr::Func { binding, args, .. } = &mut argument {
86        if binding_dispatch(binding.as_ref()) == Some(FunctionDispatch::NamedArgument)
87            && args.len() == 2
88        {
89            let value = args.remove(1);
90            args.push(variadic_argument_marker(value));
91            return argument;
92        }
93    }
94    variadic_argument_marker(argument)
95}
96
97fn variadic_argument_marker(value: Expr) -> Expr {
98    let binding = FunctionBinding::dispatched(FunctionDispatch::VariadicArgument);
99    Expr::Func {
100        order_syntax: crate::ast::FunctionOrderSyntax::Ordinary,
101        name: binding.name.clone(),
102        binding: Some(binding),
103        args: vec![value],
104        distinct: false,
105        order_by: Vec::new(),
106        filter: None,
107    }
108}
109
110/// Return the value expression carried by an explicit `VARIADIC` marker, including one nested inside a named argument.
111#[must_use]
112pub fn variadic_argument_value(argument: &Expr) -> Option<&Expr> {
113    let value = named_argument_value(argument).unwrap_or(argument);
114    direct_variadic_argument_value(value)
115}
116
117/// Return a call argument's value expression after stripping named and explicit `VARIADIC` syntax markers.
118#[must_use]
119pub fn call_argument_value(argument: &Expr) -> &Expr {
120    let value = named_argument_value(argument).unwrap_or(argument);
121    direct_variadic_argument_value(value).unwrap_or(value)
122}
123
124/// Enforce `PostgreSQL` function-call ordering before overload resolution.
125/// Positional arguments must precede named arguments, and each explicit name
126/// may occur only once.
127pub fn validate_named_argument_order<'a>(
128    argument_names: impl IntoIterator<Item = Option<&'a str>>,
129) -> Result<()> {
130    validate_named_argument_order_with_control(argument_names, &ProductionControl::uncontrolled())
131}
132
133pub fn validate_named_argument_order_with_control<'a>(
134    argument_names: impl IntoIterator<Item = Option<&'a str>>,
135    control: &ProductionControl<'_>,
136) -> Result<()> {
137    control.check()?;
138    let mut saw_named = false;
139    let mut named = ProductionVec::new(*control);
140    for argument_name in argument_names {
141        control.check()?;
142        let Some(argument_name) = argument_name else {
143            if saw_named {
144                return Err(SQLError::Routine {
145                    sqlstate: "42601".into(),
146                    message: "positional argument cannot follow named argument".into(),
147                });
148            }
149            continue;
150        };
151        saw_named = true;
152        if named.contains(&argument_name) {
153            return Err(SQLError::Routine {
154                sqlstate: "42601".into(),
155                message: format!("argument name \"{argument_name}\" used more than once"),
156            });
157        }
158        named.push_copy(argument_name)?;
159    }
160    Ok(())
161}
162
163/// Return the `PostgreSQL` 18 strictness contract for a built-in scalar call when its implemented overload is known.
164pub fn evaluate_call_args(
165    args: &[Expr],
166    ctx: &EvalContext<'_>,
167) -> Result<Vec<(Option<String>, Value)>> {
168    args.iter()
169        .map(|arg| match arg {
170            Expr::Func {
171                binding,
172                args: inner,
173                ..
174            } if binding_dispatch(binding.as_ref()) == Some(FunctionDispatch::NamedArgument) => {
175                let Some(Expr::Literal(Value::Str(arg_name))) = inner.first() else {
176                    return Err(SQLError::Internal("named argument without a name".into()));
177                };
178                let value_expr = inner
179                    .get(1)
180                    .ok_or_else(|| SQLError::Internal("named argument without a value".into()))?;
181                Ok((
182                    Some(arg_name.clone()),
183                    evaluate_call_argument_value(value_expr, ctx)?,
184                ))
185            }
186            other => Ok((None, evaluate_call_argument_value(other, ctx)?)),
187        })
188        .collect()
189}
190
191fn evaluate_call_argument_value(argument: &Expr, ctx: &EvalContext<'_>) -> Result<Value> {
192    if let Expr::Func { binding, args, .. } = argument {
193        if binding_dispatch(binding.as_ref()) == Some(FunctionDispatch::VariadicArgument) {
194            let [value] = args.as_slice() else {
195                return Err(SQLError::Internal(
196                    "VARIADIC argument marker must contain one value".into(),
197                ));
198            };
199            return eval(value, ctx);
200        }
201    }
202    eval(argument, ctx)
203}
204
205/// Borrow argument labels while owning only the traversal buffer; the evaluated call retains the names and values.
206pub(super) fn evaluated_argument_names_with_control<'a>(
207    args: &'a [(Option<String>, Value)],
208    control: &ProductionControl<'_>,
209) -> Result<Produced<Vec<Option<&'a str>>>> {
210    let mut names = ProductionVec::new(*control);
211    names.reserve(args.len())?;
212    for (name, _) in args {
213        names.push_copy(name.as_deref())?;
214    }
215    Ok(names.finish()?)
216}