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