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        name: binding.name.clone(),
101        binding: Some(binding),
102        args: vec![value],
103        distinct: false,
104        order_by: Vec::new(),
105        filter: None,
106    }
107}
108
109/// Return the value expression carried by an explicit `VARIADIC` marker, including one nested inside a named argument.
110#[must_use]
111pub fn variadic_argument_value(argument: &Expr) -> Option<&Expr> {
112    let value = named_argument_value(argument).unwrap_or(argument);
113    direct_variadic_argument_value(value)
114}
115
116/// Return a call argument's value expression after stripping named and explicit `VARIADIC` syntax markers.
117#[must_use]
118pub fn call_argument_value(argument: &Expr) -> &Expr {
119    let value = named_argument_value(argument).unwrap_or(argument);
120    direct_variadic_argument_value(value).unwrap_or(value)
121}
122
123/// Enforce `PostgreSQL` function-call ordering before overload resolution.
124/// Positional arguments must precede named arguments, and each explicit name
125/// may occur only once.
126pub fn validate_named_argument_order<'a>(
127    argument_names: impl IntoIterator<Item = Option<&'a str>>,
128) -> Result<()> {
129    validate_named_argument_order_with_control(argument_names, &ProductionControl::uncontrolled())
130}
131
132pub fn validate_named_argument_order_with_control<'a>(
133    argument_names: impl IntoIterator<Item = Option<&'a str>>,
134    control: &ProductionControl<'_>,
135) -> Result<()> {
136    control.check()?;
137    let mut saw_named = false;
138    let mut named = ProductionVec::new(*control);
139    for argument_name in argument_names {
140        control.check()?;
141        let Some(argument_name) = argument_name else {
142            if saw_named {
143                return Err(SQLError::Routine {
144                    sqlstate: "42601".into(),
145                    message: "positional argument cannot follow named argument".into(),
146                });
147            }
148            continue;
149        };
150        saw_named = true;
151        if named.contains(&argument_name) {
152            return Err(SQLError::Routine {
153                sqlstate: "42601".into(),
154                message: format!("argument name \"{argument_name}\" used more than once"),
155            });
156        }
157        named.push_copy(argument_name)?;
158    }
159    Ok(())
160}
161
162/// Return the `PostgreSQL` 18 strictness contract for a built-in scalar call when its implemented overload is known.
163pub fn evaluate_call_args(
164    args: &[Expr],
165    ctx: &EvalContext<'_>,
166) -> Result<Vec<(Option<String>, Value)>> {
167    args.iter()
168        .map(|arg| match arg {
169            Expr::Func {
170                binding,
171                args: inner,
172                ..
173            } if binding_dispatch(binding.as_ref()) == Some(FunctionDispatch::NamedArgument) => {
174                let Some(Expr::Literal(Value::Str(arg_name))) = inner.first() else {
175                    return Err(SQLError::Internal("named argument without a name".into()));
176                };
177                let value_expr = inner
178                    .get(1)
179                    .ok_or_else(|| SQLError::Internal("named argument without a value".into()))?;
180                Ok((
181                    Some(arg_name.clone()),
182                    evaluate_call_argument_value(value_expr, ctx)?,
183                ))
184            }
185            other => Ok((None, evaluate_call_argument_value(other, ctx)?)),
186        })
187        .collect()
188}
189
190fn evaluate_call_argument_value(argument: &Expr, ctx: &EvalContext<'_>) -> Result<Value> {
191    if let Expr::Func { binding, args, .. } = argument {
192        if binding_dispatch(binding.as_ref()) == Some(FunctionDispatch::VariadicArgument) {
193            let [value] = args.as_slice() else {
194                return Err(SQLError::Internal(
195                    "VARIADIC argument marker must contain one value".into(),
196                ));
197            };
198            return eval(value, ctx);
199        }
200    }
201    eval(argument, ctx)
202}
203
204/// Borrow argument labels while owning only the traversal buffer; the evaluated call retains the names and values.
205pub(super) fn evaluated_argument_names_with_control<'a>(
206    args: &'a [(Option<String>, Value)],
207    control: &ProductionControl<'_>,
208) -> Result<Produced<Vec<Option<&'a str>>>> {
209    let mut names = ProductionVec::new(*control);
210    names.reserve(args.len())?;
211    for (name, _) in args {
212        names.push_copy(name.as_deref())?;
213    }
214    Ok(names.finish()?)
215}