uqa_sql/ir/
call_arguments.rs1use super::{ScalarExpr, ScalarOrder};
10use crate::ast::{FunctionBinding, FunctionDispatch};
11use crate::SQLError;
12use uqa_core::Value;
13
14#[doc(hidden)]
16#[derive(Debug, Clone, Copy, PartialEq)]
17pub struct ScalarCallArgument<'a> {
18 pub name: Option<&'a str>,
19 pub value: &'a ScalarExpr,
20 pub explicit_variadic: bool,
21}
22
23#[doc(hidden)]
25pub fn scalar_call_arguments(
26 arguments: &[ScalarExpr],
27) -> Result<Vec<ScalarCallArgument<'_>>, SQLError> {
28 let mut decoded = Vec::with_capacity(arguments.len());
29 for argument in arguments {
30 decoded.push(scalar_call_argument(argument)?);
31 }
32 validate_scalar_call_arguments(&decoded)?;
33 Ok(decoded)
34}
35
36#[doc(hidden)]
38pub fn validate_scalar_call_arguments(
39 arguments: &[ScalarCallArgument<'_>],
40) -> Result<bool, SQLError> {
41 let variadic_positions = arguments
42 .iter()
43 .enumerate()
44 .filter_map(|(position, argument)| argument.explicit_variadic.then_some(position))
45 .collect::<Vec<_>>();
46 if variadic_positions.len() > 1 {
47 return Err(malformed_call_argument(
48 "call contains more than one explicit VARIADIC argument",
49 ));
50 }
51 if variadic_positions
52 .first()
53 .is_some_and(|position| position + 1 != arguments.len())
54 {
55 return Err(malformed_call_argument(
56 "explicit VARIADIC argument must be the final call argument",
57 ));
58 }
59 Ok(!variadic_positions.is_empty())
60}
61
62#[doc(hidden)]
64pub fn scalar_call_argument(expression: &ScalarExpr) -> Result<ScalarCallArgument<'_>, SQLError> {
65 let ScalarExpr::Func {
66 name,
67 args,
68 binding,
69 distinct,
70 order_by,
71 filter,
72 } = expression
73 else {
74 return Ok(ScalarCallArgument {
75 name: None,
76 value: expression,
77 explicit_variadic: false,
78 });
79 };
80 if binding.as_ref().and_then(|binding| binding.dispatch)
81 == Some(FunctionDispatch::NamedArgument)
82 {
83 validate_marker_shape(
84 binding.as_ref(),
85 FunctionDispatch::NamedArgument,
86 *distinct,
87 order_by,
88 filter.as_deref(),
89 name,
90 )?;
91 let [ScalarExpr::Literal(Value::Str(argument_name)), value] = args.as_slice() else {
92 return Err(malformed_call_argument(
93 "named argument marker must contain a string name and one value",
94 ));
95 };
96 let (value, explicit_variadic) = direct_variadic_argument(value)?;
97 if !explicit_variadic
98 && matches!(
99 value,
100 ScalarExpr::Func { binding, .. }
101 if binding.as_ref().and_then(|binding| binding.dispatch)
102 == Some(FunctionDispatch::NamedArgument)
103 )
104 {
105 return Err(malformed_call_argument(
106 "call argument contains nested syntax markers",
107 ));
108 }
109 return Ok(ScalarCallArgument {
110 name: Some(argument_name),
111 value,
112 explicit_variadic,
113 });
114 }
115 let (value, explicit_variadic) = direct_variadic_argument(expression)?;
116 Ok(ScalarCallArgument {
117 name: None,
118 value,
119 explicit_variadic,
120 })
121}
122
123fn direct_variadic_argument(expression: &ScalarExpr) -> Result<(&ScalarExpr, bool), SQLError> {
124 let ScalarExpr::Func {
125 name,
126 args,
127 binding,
128 distinct,
129 order_by,
130 filter,
131 } = expression
132 else {
133 return Ok((expression, false));
134 };
135 if binding.as_ref().and_then(|binding| binding.dispatch)
136 != Some(FunctionDispatch::VariadicArgument)
137 {
138 return Ok((expression, false));
139 }
140 validate_marker_shape(
141 binding.as_ref(),
142 FunctionDispatch::VariadicArgument,
143 *distinct,
144 order_by,
145 filter.as_deref(),
146 name,
147 )?;
148 let [value] = args.as_slice() else {
149 return Err(malformed_call_argument(
150 "VARIADIC argument marker must contain exactly one value",
151 ));
152 };
153 if matches!(
154 value,
155 ScalarExpr::Func { binding, .. }
156 if matches!(
157 binding.as_ref().and_then(|binding| binding.dispatch),
158 Some(FunctionDispatch::VariadicArgument | FunctionDispatch::NamedArgument)
159 )
160 ) {
161 return Err(malformed_call_argument(
162 "call argument contains nested syntax markers",
163 ));
164 }
165 Ok((value, true))
166}
167
168fn validate_marker_shape(
169 binding: Option<&FunctionBinding>,
170 expected_dispatch: FunctionDispatch,
171 distinct: bool,
172 order_by: &[ScalarOrder],
173 filter: Option<&ScalarExpr>,
174 name: &str,
175) -> Result<(), SQLError> {
176 if binding.is_none_or(|binding| {
177 !binding.builtin
178 || binding.dispatch != Some(expected_dispatch)
179 || !binding.argument_types.is_empty()
180 || binding.invocation.is_some()
181 || binding.resolution_error.is_some()
182 }) || distinct
183 || !order_by.is_empty()
184 || filter.is_some()
185 {
186 return Err(malformed_call_argument(&format!(
187 "{name} syntax marker contains function-call metadata"
188 )));
189 }
190 Ok(())
191}
192
193fn malformed_call_argument(message: &str) -> SQLError {
194 SQLError::Internal(format!("malformed call argument: {message}"))
195}
196
197pub fn analyze_expression_call_arguments(
199 arguments: &[crate::plan::ExpressionPlan],
200) -> Result<(Vec<ScalarCallArgument<'_>>, bool), SQLError> {
201 let decoded = arguments
202 .iter()
203 .map(|argument| scalar_call_argument(&argument.scalar))
204 .collect::<Result<Vec<_>, _>>()?;
205 let explicit_variadic = validate_scalar_call_arguments(&decoded)?;
206 Ok((decoded, explicit_variadic))
207}