uqa_sql/expr/call_dispatch/
production.rs1use super::named::builtin_named_args;
10use crate::{
11 ast::{FunctionBinding, FunctionDispatch},
12 error::{Result, SQLError},
13};
14use uqa_core::{
15 memory::{MemoryReservation, Produced, ProductionControl, ProductionVec},
16 Value,
17};
18
19pub fn eval_generated_function_call_with_control(
21 name: &str,
22 binding: Option<&FunctionBinding>,
23 arguments: Produced<Vec<(Option<String>, Value)>>,
24 control: &ProductionControl<'_>,
25) -> Result<Produced<Value>> {
26 eval_builtin_function_call_with_control(
27 name,
28 binding,
29 arguments,
30 control,
31 &super::EvalContext::new(None, &[]),
32 )
33}
34
35pub fn eval_builtin_function_call_with_control(
37 name: &str,
38 binding: Option<&FunctionBinding>,
39 arguments: Produced<Vec<(Option<String>, Value)>>,
40 control: &ProductionControl<'_>,
41 context: &super::EvalContext<'_>,
42) -> Result<Produced<Value>> {
43 if let Some(error) = binding.and_then(|binding| binding.resolution_error.as_ref()) {
44 return Err(error.sql_error());
45 }
46 control.check()?;
47 if binding.is_some_and(|binding| !binding.builtin) {
48 return Err(SQLError::Unsupported(
49 "bound user function requires a logical engine session".into(),
50 ));
51 }
52 if let (Some(binding), Some(hook)) = (binding, context.engine) {
53 hook.require_builtin_execute(binding)?;
54 }
55 if let Some((binding, dispatch)) =
56 binding.and_then(|binding| binding.dispatch.map(|dispatch| (binding, dispatch)))
57 {
58 if arguments.iter().any(|(name, _)| name.is_some()) {
59 return Err(SQLError::Internal(format!(
60 "bound {} expression retained a named argument",
61 dispatch.label(),
62 )));
63 }
64 let evaluated = MovedArguments::new(arguments, control)?;
65 return eval_dispatched(binding, dispatch, &evaluated.values, control, context);
66 }
67 let name = binding.map_or(name, |binding| binding.name.as_str());
68 let normalized =
69 super::super::call_arguments::normalized_function_name_with_control(name, control)?;
70 let name = normalized.as_ref();
71 let value = if arguments.iter().any(|(name, _)| name.is_some()) {
72 let Some(positional) = builtin_named_args(name, &arguments, control)? else {
73 return Err(super::super::diagnostics::unknown_function_error(
74 name, &arguments,
75 ));
76 };
77 eval_scalar(name, &positional, control, context)?
78 } else {
79 let evaluated = MovedArguments::new(arguments, control)?;
80 eval_scalar(name, &evaluated.values, control, context)?
81 };
82 if matches!(*value, Value::Int(_) | Value::Float(_)) {
83 if let Some(binding) = binding {
84 if let Some(ty) = crate::fixed_builtin_return_type_with_control(binding, control)? {
85 if matches!(
86 *ty,
87 crate::ColumnType::SmallInteger
88 | crate::ColumnType::Integer
89 | crate::ColumnType::Real
90 ) {
91 let name = ty.sql_name_with_control(control)?;
92 return super::super::cast_value_from_with_control(
93 &value, &name, None, control,
94 );
95 }
96 }
97 }
98 }
99 Ok(value)
100}
101
102fn eval_dispatched(
103 binding: &FunctionBinding,
104 dispatch: FunctionDispatch,
105 arguments: &[Value],
106 control: &ProductionControl<'_>,
107 context: &super::EvalContext<'_>,
108) -> Result<Produced<Value>> {
109 if let FunctionDispatch::Enum {
110 operation,
111 type_oid,
112 } = dispatch
113 {
114 return super::super::enums::enum_function_value_with_control(
115 context
116 .engine
117 .and_then(super::super::EngineHook::enum_labels),
118 operation,
119 type_oid,
120 arguments,
121 context.enum_comparison_state(),
122 control,
123 );
124 }
125 if let Some(result) = super::super::scalar_postgres::eval_comparison_with_context(
126 dispatch, arguments, control, context,
127 ) {
128 return result;
129 }
130 super::super::builtin::eval_dispatched_builtin_with_control(
131 binding, dispatch, arguments, control, context,
132 )
133}
134
135fn eval_scalar(
136 name: &str,
137 arguments: &[Value],
138 control: &ProductionControl<'_>,
139 context: &super::EvalContext<'_>,
140) -> Result<Produced<Value>> {
141 if matches!(name, "coalesce" | "nullif" | "greatest" | "least") {
142 return super::super::scalar_core::selection_with_context(
143 name, arguments, control, context,
144 );
145 }
146 super::super::scalar_dispatch::eval_generated_scalar_function(name, arguments, control)
147}
148
149struct MovedArguments {
151 values: Vec<Value>,
152 remaining: std::vec::IntoIter<(Option<String>, Value)>,
153 _memory: Option<MemoryReservation>,
154}
155
156impl MovedArguments {
157 fn new(
158 arguments: Produced<Vec<(Option<String>, Value)>>,
159 control: &ProductionControl<'_>,
160 ) -> Result<Self> {
161 let mut values = ProductionVec::new(*control);
162 values.reserve(arguments.len())?;
163 let (values, values_memory) = values.finish()?.into_parts();
164 let (arguments, argument_memory) = arguments.into_parts();
165 let mut output = Self {
166 values,
167 remaining: arguments.into_iter(),
168 _memory: control.combine(values_memory, argument_memory),
169 };
170 for (_, value) in output.remaining.by_ref() {
171 control.check()?;
172 output.values.push(value);
173 }
174 control.check()?;
175 Ok(output)
176 }
177}
178
179#[cfg(test)]
180mod tests;