uqa_sql/expr/
call_arguments.rs1use 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#[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#[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#[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
100pub 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
130pub 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}