uqa_sql/catalog/node_tree/expressions/
functions.rs1use super::{
10 ColumnType, Expr, ExpressionContext, Field, FunctionBinding, Node, RoutineIdentity, SQLError,
11 TypedNode,
12};
13use crate::ast::{
14 FunctionCallSyntax, FunctionDispatch, RoutineInvocationBinding, RoutineVariadicMode,
15};
16use crate::catalog::type_metadata::{pg_type_collation_oid, pg_type_oid};
17use crate::expr::{call_argument_name, call_argument_value, variadic_argument_value};
18use crate::type_resolution::RoutineCallDescriptor;
19
20impl RoutineIdentity {
21 #[must_use]
23 pub fn positional(oid: i64, argument_types: Vec<ColumnType>, result_type: ColumnType) -> Self {
24 let names = argument_types
25 .iter()
26 .map(ColumnType::catalog_name)
27 .collect::<Vec<_>>();
28 Self {
29 oid,
30 invocation: RoutineInvocationBinding {
31 argument_positions: (0..names.len()).collect(),
32 argument_targets: names.clone(),
33 argument_sources: Vec::new(),
34 parameter_types: names,
35 return_type: Some(result_type.catalog_name()),
36 variadic_mode: RoutineVariadicMode::None,
37 },
38 result_type,
39 }
40 }
41}
42
43impl ExpressionContext<'_> {
44 pub(super) fn function(
45 &self,
46 name: &str,
47 binding: Option<&FunctionBinding>,
48 arguments: &[Expr],
49 syntax: FunctionCallSyntax,
50 ) -> Result<TypedNode, SQLError> {
51 let types = arguments
52 .iter()
53 .map(|arg| self.expression_type(call_argument_value(arg)))
54 .collect::<Result<Vec<_>, _>>()?;
55 let names = arguments
56 .iter()
57 .map(|arg| call_argument_name(arg).map(|name| name.map(str::to_owned)))
58 .collect::<Result<Vec<_>, _>>()?;
59 let helper = if let Some(dispatch @ FunctionDispatch::PatternEscape { similar }) =
60 binding.and_then(|binding| binding.dispatch)
61 {
62 if !(1..=2).contains(&types.len()) {
63 return Err(SQLError::Internal("invalid pattern escape operands".into()));
64 }
65 let inputs = [
66 types.first().and_then(Option::as_ref),
67 types.get(1).and_then(Option::as_ref),
68 ];
69 let ty = crate::type_resolution::pattern::select_escape(
70 similar,
71 &inputs[..types.len()],
72 &uqa_core::memory::ProductionControl::uncontrolled(),
73 )?;
74 let mut selected = FunctionBinding::dispatched(dispatch);
75 selected.argument_types = vec![ty.sql_name(); types.len()];
76 Some(selected)
77 } else {
78 None
79 };
80 let name = helper
81 .as_ref()
82 .map_or(name, |binding| binding.name.as_str());
83 let routine = self.routines.resolve(
84 name,
85 helper.as_ref().or(binding),
86 RoutineCallDescriptor {
87 argument_names: &names,
88 argument_types: &types,
89 explicit_variadic: arguments
90 .last()
91 .is_some_and(|arg| variadic_argument_value(arg).is_some()),
92 },
93 )?;
94 let (arguments, variadic) =
95 self.routine_arguments(arguments, &names, &routine.invocation)?;
96 Ok(TypedNode {
97 node: Node::new(
98 "FUNCEXPR",
99 [
100 ("funcid", routine.oid.into()),
101 ("funcresulttype", pg_type_oid(&routine.result_type).into()),
102 ("funcretset", false.into()),
103 ("funcvariadic", variadic.into()),
104 (
105 "funcformat",
106 if syntax == FunctionCallSyntax::Extract {
107 3.into()
108 } else {
109 0.into()
110 },
111 ),
112 (
113 "funccollid",
114 pg_type_collation_oid(&routine.result_type).into(),
115 ),
116 ("inputcollid", 0.into()),
117 ("args", Field::List(arguments)),
118 ("location", (-1).into()),
119 ],
120 ),
121 ty: Some(routine.result_type),
122 })
123 }
124
125 fn routine_arguments(
126 &self,
127 arguments: &[Expr],
128 names: &[Option<String>],
129 invocation: &RoutineInvocationBinding,
130 ) -> Result<(Vec<Field>, bool), SQLError> {
131 if invocation.argument_targets.len() != arguments.len()
132 || invocation.argument_positions.len() != arguments.len()
133 {
134 return Err(SQLError::Internal(
135 "catalog invocation differs from supplied arguments".into(),
136 ));
137 }
138 let packed = match invocation.variadic_mode {
139 RoutineVariadicMode::Expanded { parameter_index }
140 if invocation
141 .parameter_types
142 .get(parameter_index)
143 .is_some_and(|ty| ty != "any") =>
144 {
145 Some(parameter_index)
146 }
147 _ => None,
148 };
149 let mut fields = Vec::with_capacity(arguments.len());
150 let mut elements = Vec::new();
151 for (index, argument) in arguments.iter().enumerate() {
152 let position = invocation.argument_positions[index];
153 if position >= invocation.parameter_types.len() {
154 return Err(SQLError::Internal(
155 "invalid catalog invocation parameter".into(),
156 ));
157 }
158 let value = call_argument_value(argument);
159 let target = &invocation.argument_targets[index];
160 let node = if target == "unknown" {
161 let Expr::Literal(value) = value else {
162 return Err(SQLError::Internal(
163 "unknown routine argument is not a literal".into(),
164 ));
165 };
166 super::values::unknown_constant(value)?
167 } else {
168 self.encode(value, Some(&self.resolve_type(target)?))?.node
169 };
170 if packed == Some(position) {
171 if names[index].is_some() {
172 return Err(SQLError::Internal(
173 "expanded variadic argument has a name".into(),
174 ));
175 }
176 elements.push(node.into());
177 } else if let Some(name) = &names[index] {
178 fields.push(named_argument(node, name, position)?.into());
179 } else {
180 fields.push(node.into());
181 }
182 }
183 if let Some(position) = packed {
184 let ty = self.resolve_type(&invocation.parameter_types[position])?;
185 fields.push(variadic_array(&ty, elements)?.into());
186 }
187 Ok((
188 fields,
189 packed.is_some()
190 || matches!(
191 invocation.variadic_mode,
192 RoutineVariadicMode::Explicit { .. }
193 ),
194 ))
195 }
196}
197
198fn named_argument(node: Node, name: &str, position: usize) -> Result<Node, SQLError> {
199 let position = i64::try_from(position)
200 .map_err(|_| super::super::invalid("routine argument position exceeds catalog integer"))?;
201 Ok(Node::new(
202 "NAMEDARGEXPR",
203 [
204 ("arg", node.into()),
205 ("name", Field::Atom(name.into())),
206 ("argnumber", position.into()),
207 ("location", (-1).into()),
208 ],
209 ))
210}
211
212fn variadic_array(ty: &ColumnType, elements: Vec<Field>) -> Result<Node, SQLError> {
213 let ColumnType::Array(element) = ty else {
214 return Err(SQLError::Internal(
215 "expanded routine parameter is not an array".into(),
216 ));
217 };
218 Ok(Node::new(
219 "ARRAYEXPR",
220 [
221 ("array_typeid", pg_type_oid(ty).into()),
222 ("array_collid", pg_type_collation_oid(ty).into()),
223 ("element_typeid", pg_type_oid(element).into()),
224 ("elements", Field::List(elements)),
225 ("multidims", false.into()),
226 ("list_start", (-1).into()),
227 ("list_end", (-1).into()),
228 ("location", (-1).into()),
229 ],
230 ))
231}