uqa_sql/expr/
call_dispatch.rs1use uqa_core::{ArrayValue, Value};
10
11use crate::error::{Result, SQLError};
12
13use super::call_arguments::normalized_function_name;
14use super::context::EvalContext;
15use super::conversion::to_f64;
16use super::diagnostics::{unknown_function_error, value_type_name};
17use super::json::{jsonpath_candidate, jsonpath_match};
18use super::random;
19use super::scalar_dispatch::{eval_scalar_function, eval_sequence_function};
20
21mod named;
22mod production;
23use named::builtin_named_args;
24pub use production::eval_generated_function_call_with_control;
25
26pub fn eval_function_call(
30 name: &str,
31 call_args: Vec<(Option<String>, Value)>,
32 ctx: &EvalContext<'_>,
33) -> Result<Value> {
34 eval_function_call_inner(name, call_args, ctx, true)
35}
36
37pub fn eval_builtin_function_call(
40 name: &str,
41 call_args: Vec<(Option<String>, Value)>,
42 ctx: &EvalContext<'_>,
43) -> Result<Value> {
44 eval_function_call_inner(name, call_args, ctx, false)
45}
46
47#[expect(
48 clippy::too_many_lines,
49 reason = "builtin dispatch preserves arity, NULL, and error precedence"
50)]
51fn eval_function_call_inner(
52 name: &str,
53 call_args: Vec<(Option<String>, Value)>,
54 ctx: &EvalContext<'_>,
55 allow_dynamic_dispatch: bool,
56) -> Result<Value> {
57 let lower = normalized_function_name(name);
58 let lower = lower.as_ref();
59 let evaluated: Vec<Value> = call_args.iter().map(|(_, value)| value.clone()).collect();
60
61 if let Some(result) = super::current_time::eval_current_time(lower, &evaluated, Some(ctx)) {
62 return result;
63 }
64
65 if lower == "current_setting" {
66 return super::session_settings::current_setting(&evaluated, ctx);
67 }
68
69 if let Some(result) = random::eval_random_function(lower, &call_args, ctx) {
70 return result;
71 }
72 if lower == "random" && !evaluated.is_empty() {
73 return Err(SQLError::TypeMismatch("random takes no arguments".into()));
74 }
75 if lower == "setseed" {
76 let [value] = evaluated.as_slice() else {
77 return Err(SQLError::TypeMismatch("setseed takes 1 arg".into()));
78 };
79 let seed = to_f64(value)?;
80 if !seed.is_finite() || !(-1.0..=1.0).contains(&seed) {
81 return Err(SQLError::Routine {
82 sqlstate: "22023".into(),
83 message: format!("setseed parameter {seed} is out of allowed range [-1,1]"),
84 });
85 }
86 let engine = ctx.engine.ok_or_else(|| {
87 SQLError::Unsupported("setseed requires a logical engine session".into())
88 })?;
89 if !engine.set_random_seed(seed).map_err(SQLError::Internal)? {
90 return Err(SQLError::Unsupported(
91 "engine hook does not provide a session random stream".into(),
92 ));
93 }
94 return Ok(Value::Str(String::new()));
95 }
96
97 if lower == "current_schema" {
98 if !evaluated.is_empty() {
99 return Err(SQLError::TypeMismatch(
100 "current_schema takes no arguments".into(),
101 ));
102 }
103 let schema = ctx
104 .engine
105 .map(|engine| engine.current_schema())
106 .transpose()
107 .map_err(SQLError::Internal)?
108 .flatten()
109 .unwrap_or_else(|| "public".to_string());
110 return Ok(Value::Str(schema));
111 }
112 if lower == "current_schemas" {
113 let [Value::Bool(include_implicit)] = evaluated.as_slice() else {
114 return Err(SQLError::TypeMismatch(
115 "current_schemas takes one boolean argument".into(),
116 ));
117 };
118 let schemas = ctx
119 .engine
120 .map(|engine| engine.current_schemas(*include_implicit))
121 .transpose()
122 .map_err(SQLError::Internal)?
123 .flatten()
124 .unwrap_or_else(|| {
125 let mut schemas = Vec::new();
126 if *include_implicit {
127 schemas.push("pg_catalog".to_string());
128 }
129 schemas.push("public".to_string());
130 schemas
131 });
132 return ArrayValue::try_new(schemas.into_iter().map(Value::Str).collect())
133 .map(Value::Array)
134 .ok_or_else(|| SQLError::TypeMismatch("invalid current_schemas result".into()));
135 }
136 if matches!(lower, "current_user" | "session_user") {
137 if !evaluated.is_empty() {
138 return Err(SQLError::TypeMismatch(format!(
139 "{lower} takes no arguments"
140 )));
141 }
142 let user = ctx
143 .engine
144 .map(|engine| {
145 if lower == "current_user" {
146 engine.current_user()
147 } else {
148 engine.session_user()
149 }
150 })
151 .transpose()?
152 .flatten()
153 .unwrap_or_else(|| "uqa".to_string());
154 return Ok(Value::Str(user));
155 }
156 let regobject_type = match lower {
157 "to_regproc" => Some(crate::ast::ColumnType::Regproc),
158 "to_regprocedure" => Some(crate::ast::ColumnType::Regprocedure),
159 "to_regclass" => Some(crate::ast::ColumnType::Regclass),
160 "to_regnamespace" => Some(crate::ast::ColumnType::Regnamespace),
161 "to_regrole" => Some(crate::ast::ColumnType::Regrole),
162 "to_regtype" => Some(crate::ast::ColumnType::Regtype),
163 _ => None,
164 };
165 if let Some(regobject_type) = regobject_type {
166 let [value] = evaluated.as_slice() else {
167 return Err(SQLError::BadArity {
168 name: lower.into(),
169 expected: "1".into(),
170 actual: evaluated.len(),
171 });
172 };
173 let name = match value {
174 Value::Null => return Ok(Value::Null),
175 Value::Str(name) | Value::FixedChar(name) => name,
176 value => {
177 return Err(SQLError::TypeMismatch(format!(
178 "{lower} requires text, got {}",
179 value_type_name(value)
180 )));
181 }
182 };
183 let oid = ctx
184 .engine
185 .map(|engine| engine.resolve_regobject(®object_type, name))
186 .transpose()?
187 .flatten();
188 return Ok(oid.map_or(Value::Null, Value::Int));
189 }
190
191 if crate::registry::is_registered(lower) {
195 if lower == "fts_match" && jsonpath_candidate(&evaluated) {
196 return jsonpath_match(&evaluated);
197 }
198 return Err(SQLError::Unsupported(format!(
199 "scalar evaluation of `{name}` is not supported (use the function registry)"
200 )));
201 }
202
203 if call_args.iter().any(|(name, _)| name.is_some()) {
204 if let Some(positional) = builtin_named_args(
205 lower,
206 &call_args,
207 &uqa_core::memory::ProductionControl::uncontrolled(),
208 )? {
209 return eval_scalar_function(lower, &positional);
210 }
211 if let Some(engine) = ctx.engine.filter(|_| allow_dynamic_dispatch) {
212 if let Some(result) = engine.call_user_function(lower, &call_args) {
213 return result;
214 }
215 }
216 return Err(unknown_function_error(lower, &call_args));
217 }
218
219 if matches!(lower, "nextval" | "currval" | "lastval" | "setval") {
221 return eval_sequence_function(lower, &evaluated, ctx);
222 }
223 if let Some(engine) = ctx
224 .engine
225 .filter(|engine| allow_dynamic_dispatch && engine.has_scalar_functions())
226 {
227 if let Some(result) = engine.call_scalar_function(lower, &evaluated) {
228 return result;
229 }
230 }
231 match eval_scalar_function(lower, &evaluated) {
232 Err(SQLError::UnknownFunction(_)) => {
235 if let Some(engine) = ctx.engine.filter(|_| allow_dynamic_dispatch) {
236 if let Some(result) = engine.call_user_function(lower, &call_args) {
237 return result;
238 }
239 }
240 Err(unknown_function_error(lower, &call_args))
241 }
242 other => other,
243 }
244}