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::Value;
12
13use crate::ast::{Expr, FunctionBinding, FunctionDispatch};
14use crate::error::{Result, SQLError};
15
16use super::context::EvalContext;
17use super::evaluator::eval;
18
19pub(super) fn normalized_function_name(name: &str) -> Cow<'_, str> {
20    let stripped = name.strip_prefix("pg_catalog.").unwrap_or(name);
21    if stripped.bytes().any(|byte| byte.is_ascii_uppercase()) {
22        Cow::Owned(stripped.to_ascii_lowercase())
23    } else {
24        Cow::Borrowed(stripped)
25    }
26}
27
28fn binding_dispatch(binding: Option<&FunctionBinding>) -> Option<FunctionDispatch> {
29    binding.and_then(|binding| binding.dispatch)
30}
31
32fn direct_variadic_argument_value(argument: &Expr) -> Option<&Expr> {
33    let Expr::Func { binding, args, .. } = argument else {
34        return None;
35    };
36    if binding_dispatch(binding.as_ref()) != Some(FunctionDispatch::VariadicArgument) {
37        return None;
38    }
39    let [value] = args.as_slice() else {
40        return None;
41    };
42    Some(value)
43}
44
45fn named_argument_value(argument: &Expr) -> Option<&Expr> {
46    let Expr::Func { binding, args, .. } = argument else {
47        return None;
48    };
49    if binding_dispatch(binding.as_ref()) == Some(FunctionDispatch::NamedArgument) {
50        args.get(1)
51    } else {
52        None
53    }
54}
55
56/// Wrap the last actual argument of an explicit `VARIADIC` invocation while preserving a named-argument marker at the top level.
57#[must_use]
58pub fn wrap_variadic_argument(mut argument: Expr) -> Expr {
59    if variadic_argument_value(&argument).is_some() {
60        return argument;
61    }
62    if let Expr::Func { binding, args, .. } = &mut argument {
63        if binding_dispatch(binding.as_ref()) == Some(FunctionDispatch::NamedArgument)
64            && args.len() == 2
65        {
66            let value = args.remove(1);
67            args.push(variadic_argument_marker(value));
68            return argument;
69        }
70    }
71    variadic_argument_marker(argument)
72}
73
74fn variadic_argument_marker(value: Expr) -> Expr {
75    let binding = FunctionBinding::dispatched(FunctionDispatch::VariadicArgument);
76    Expr::Func {
77        name: binding.name.clone(),
78        binding: Some(binding),
79        args: vec![value],
80        distinct: false,
81        order_by: Vec::new(),
82        filter: None,
83    }
84}
85
86/// Return the value expression carried by an explicit `VARIADIC` marker, including one nested inside a named argument.
87#[must_use]
88pub fn variadic_argument_value(argument: &Expr) -> Option<&Expr> {
89    let value = named_argument_value(argument).unwrap_or(argument);
90    direct_variadic_argument_value(value)
91}
92
93/// Return a call argument's value expression after stripping named and explicit `VARIADIC` syntax markers.
94#[must_use]
95pub fn call_argument_value(argument: &Expr) -> &Expr {
96    let value = named_argument_value(argument).unwrap_or(argument);
97    direct_variadic_argument_value(value).unwrap_or(value)
98}
99
100/// Enforce `PostgreSQL` function-call ordering before overload resolution.
101/// Positional arguments must precede named arguments, and each explicit name
102/// may occur only once.
103pub fn validate_named_argument_order<'a>(
104    argument_names: impl IntoIterator<Item = Option<&'a str>>,
105) -> Result<()> {
106    let mut saw_named = false;
107    let mut named = Vec::new();
108    for argument_name in argument_names {
109        let Some(argument_name) = argument_name else {
110            if saw_named {
111                return Err(SQLError::Routine {
112                    sqlstate: "42601".into(),
113                    message: "positional argument cannot follow named argument".into(),
114                });
115            }
116            continue;
117        };
118        saw_named = true;
119        if named.contains(&argument_name) {
120            return Err(SQLError::Routine {
121                sqlstate: "42601".into(),
122                message: format!("argument name \"{argument_name}\" used more than once"),
123            });
124        }
125        named.push(argument_name);
126    }
127    Ok(())
128}
129
130/// Return the `PostgreSQL` 18 strictness contract for a built-in scalar call when its implemented overload is known.
131pub fn evaluate_call_args(
132    args: &[Expr],
133    ctx: &EvalContext<'_>,
134) -> Result<Vec<(Option<String>, Value)>> {
135    args.iter()
136        .map(|arg| match arg {
137            Expr::Func {
138                binding,
139                args: inner,
140                ..
141            } if binding_dispatch(binding.as_ref()) == Some(FunctionDispatch::NamedArgument) => {
142                let Some(Expr::Literal(Value::Str(arg_name))) = inner.first() else {
143                    return Err(SQLError::Internal("named argument without a name".into()));
144                };
145                let value_expr = inner
146                    .get(1)
147                    .ok_or_else(|| SQLError::Internal("named argument without a value".into()))?;
148                Ok((
149                    Some(arg_name.clone()),
150                    evaluate_call_argument_value(value_expr, ctx)?,
151                ))
152            }
153            other => Ok((None, evaluate_call_argument_value(other, ctx)?)),
154        })
155        .collect()
156}
157
158fn evaluate_call_argument_value(argument: &Expr, ctx: &EvalContext<'_>) -> Result<Value> {
159    if let Expr::Func { binding, args, .. } = argument {
160        if binding_dispatch(binding.as_ref()) == Some(FunctionDispatch::VariadicArgument) {
161            let [value] = args.as_slice() else {
162                return Err(SQLError::Internal(
163                    "VARIADIC argument marker must contain one value".into(),
164                ));
165            };
166            return eval(value, ctx);
167        }
168    }
169    eval(argument, ctx)
170}