1use std::borrow::Cow;
11
12use uqa_core::{ArrayValue, DecimalValue, TemporalValue, Value};
13
14use crate::ast::{BinaryOp, Expr};
15use crate::error::{Result, SQLError};
16use crate::params::SQLParam;
17use crate::result::ResultRow;
18
19mod encoding;
20mod json;
21mod time;
22
23use encoding::{base64_decode, base64_encode, md5_hex};
24pub use json::value_to_json_text;
25use json::{
26 format_jsonb_pretty, json_build_array_value, json_build_object_value, json_concat,
27 json_contained_by, json_contains, json_delete, json_delete_path, json_extract_path,
28 json_has_key, json_has_keys, json_typeof, jsonb_insert, jsonb_set, jsonpath_candidate,
29 jsonpath_exists, jsonpath_match, parse_json, strip_nulls, typed_json_value, value_to_json,
30};
31use time::{
32 age_between, coerce_temporal, date_trunc_value, extract_from_value, format_pg_number,
33 format_temporal, generate_random_uuid, generate_uuid_v7, hex_encode, make_timestamp,
34 parse_timestamp, pg_to_chrono_fmt,
35};
36mod binary;
37mod casting;
38mod conversion;
39mod scalar_array;
40mod scalar_core;
41mod scalar_dispatch;
42mod scalar_geospatial;
43mod scalar_helpers;
44mod scalar_json;
45mod scalar_math;
46mod scalar_postgres;
47mod scalar_temporal;
48
49use binary::{
50 compare, compare_nullable, eval_binary, eval_comparison_op, values_equal, values_equal_nullable,
51};
52pub(crate) use binary::{division_by_zero, out_of_range};
53pub use binary::{
54 eval_binary_values, eval_binary_values_with_integer_width, eval_comparison_truth,
55 integer_width_for_literal, integer_width_for_type, truthy, IntegerWidth,
56};
57pub use casting::{
58 array_dimensions, cast_value, cast_value_from, negate_value, parse_pg_array_literal,
59};
60pub(crate) use conversion::to_f64;
61use conversion::{
62 allocation_error, coerce_i64, expect_str, float1, float_to_i64_rounded, float_to_i64_trunc,
63 gcd_i64, initcap_str, nonnegative_usize, string1, to_decimal, to_i64,
64};
65pub use conversion::{array_value_to_string, value_to_string};
66pub use conversion::{value_to_tensor, value_to_vector};
67use scalar_dispatch::{eval_scalar_function, eval_sequence_function};
68use scalar_helpers::{
69 compile_pg_regex, like_match, point_xy, quote_literal, similar_to_regex, trim_chars,
70 typeof_value,
71};
72pub use scalar_helpers::{quote_ident, CompiledLikePattern};
73
74pub trait EngineHook {
79 fn nextval(&self, name: &str) -> std::result::Result<i64, String>;
80 fn currval(&self, name: &str) -> std::result::Result<i64, String>;
81 fn setval(&self, name: &str, value: i64) -> std::result::Result<i64, String>;
82
83 fn call_scalar_function(&self, _name: &str, _args: &[Value]) -> Option<Result<Value>> {
84 None
85 }
86
87 fn has_scalar_functions(&self) -> bool {
88 true
89 }
90
91 fn current_schema(&self) -> std::result::Result<Option<String>, String> {
95 Ok(None)
96 }
97
98 fn current_schemas(
100 &self,
101 _include_implicit: bool,
102 ) -> std::result::Result<Option<Vec<String>>, String> {
103 Ok(None)
104 }
105
106 fn random_value(&self) -> std::result::Result<Option<f64>, String> {
109 Ok(None)
110 }
111
112 fn set_random_seed(&self, _seed: f64) -> std::result::Result<bool, String> {
115 Ok(false)
116 }
117
118 fn call_user_function(
123 &self,
124 _name: &str,
125 _args: &[(Option<String>, Value)],
126 ) -> Option<Result<Value>> {
127 None
128 }
129
130 fn call_bound_user_function(
131 &self,
132 _binding: &crate::ast::FunctionBinding,
133 _args: &[(Option<String>, Value)],
134 ) -> Option<Result<Value>> {
135 None
136 }
137}
138
139pub trait RowLookup {
143 fn column(&self, name: &str) -> Option<&Value>;
144
145 fn column_is_ambiguous(&self, _name: &str) -> bool {
149 false
150 }
151
152 fn qualified_column(&self, qualifier: &str, column: &str) -> Option<&Value>;
153
154 fn qualified_column_is_ambiguous(&self, _qualifier: &str, _column: &str) -> bool {
156 false
157 }
158
159 fn positional_column(&self, _index: usize) -> Option<&Value> {
164 None
165 }
166
167 fn visit_columns(&self, _visitor: &mut dyn FnMut(&str, &Value)) {}
172}
173
174impl RowLookup for ResultRow {
175 fn column(&self, name: &str) -> Option<&Value> {
176 self.get(name)
177 }
178
179 fn qualified_column(&self, _qualifier: &str, _column: &str) -> Option<&Value> {
180 None
181 }
182
183 fn visit_columns(&self, visitor: &mut dyn FnMut(&str, &Value)) {
184 for (column, value) in self {
185 visitor(column, value);
186 }
187 }
188}
189
190pub struct EvalContext<'a> {
191 pub row: Option<&'a ResultRow>,
192 row_lookup: Option<&'a dyn RowLookup>,
193 pub params: &'a [SQLParam],
194 pub engine: Option<&'a dyn EngineHook>,
195}
196
197impl<'a> EvalContext<'a> {
198 pub fn new(row: Option<&'a ResultRow>, params: &'a [SQLParam]) -> Self {
199 Self {
200 row,
201 row_lookup: row.map(|row| row as &dyn RowLookup),
202 params,
203 engine: None,
204 }
205 }
206
207 pub fn from_row_lookup(row: &'a dyn RowLookup, params: &'a [SQLParam]) -> Self {
208 Self {
209 row: None,
213 row_lookup: Some(row),
214 params,
215 engine: None,
216 }
217 }
218
219 pub fn with_engine(mut self, engine: &'a dyn EngineHook) -> Self {
220 self.engine = Some(engine);
221 self
222 }
223
224 fn row_lookup(&self) -> Result<&'a dyn RowLookup> {
225 self.row_lookup
226 .ok_or_else(|| SQLError::Internal("column reference without row context".into()))
227 }
228
229 pub fn column_value(&self, name: &str) -> Result<Value> {
233 if self.row_lookup()?.column_is_ambiguous(name) {
234 return Err(SQLError::AmbiguousColumn(name.to_string()));
235 }
236 Ok(self
237 .row_lookup()?
238 .column(name)
239 .cloned()
240 .unwrap_or(Value::Null))
241 }
242
243 pub fn qualified_column_value(&self, qualifier: &str, column: &str) -> Result<Value> {
245 if self
246 .row_lookup()?
247 .qualified_column_is_ambiguous(qualifier, column)
248 {
249 return Err(SQLError::AmbiguousColumn(format!("{qualifier}.{column}")));
250 }
251 Ok(self
252 .row_lookup()?
253 .qualified_column(qualifier, column)
254 .cloned()
255 .unwrap_or(Value::Null))
256 }
257}
258
259pub fn eval(expr: &Expr, ctx: &EvalContext<'_>) -> Result<Value> {
264 match expr {
265 Expr::Default => Err(SQLError::Internal(
266 "DEFAULT reached scalar expression evaluation without a mutation target".into(),
267 )),
268 Expr::Literal(v) => Ok(v.clone()),
269 Expr::Param(i) => match i.checked_sub(1).and_then(|index| ctx.params.get(index)) {
270 Some(SQLParam::Scalar(v)) => Ok(v.clone()),
271 Some(SQLParam::Vector(v)) => Ok(Value::List(
272 v.iter().map(|x| Value::Float(f64::from(*x))).collect(),
273 )),
274 Some(SQLParam::Tensor(vectors)) => Ok(Value::List(
275 vectors
276 .iter()
277 .map(|vector| {
278 Value::List(vector.iter().map(|x| Value::Float(f64::from(*x))).collect())
279 })
280 .collect(),
281 )),
282 None => Err(SQLError::MissingParam(*i)),
283 },
284 Expr::Column(name) => {
285 if ctx.row_lookup()?.column_is_ambiguous(name) {
289 return Err(SQLError::AmbiguousColumn(name.clone()));
290 }
291 Ok(ctx
292 .row_lookup()?
293 .column(name)
294 .cloned()
295 .unwrap_or(Value::Null))
296 }
297 Expr::QualifiedColumn { qualifier, column } => {
298 if ctx
299 .row_lookup()?
300 .qualified_column_is_ambiguous(qualifier, column)
301 {
302 return Err(SQLError::AmbiguousColumn(format!("{qualifier}.{column}")));
303 }
304 Ok(ctx
305 .row_lookup()?
306 .qualified_column(qualifier, column)
307 .cloned()
308 .unwrap_or(Value::Null))
309 }
310 Expr::Array(elements) => {
311 let mut out = Vec::with_capacity(elements.len());
312 for e in elements {
313 out.push(eval(e, ctx)?);
314 }
315 ArrayValue::try_new(out).map(Value::Array).ok_or_else(|| {
316 SQLError::TypeMismatch(
317 "multidimensional arrays must have matching dimensions".into(),
318 )
319 })
320 }
321 Expr::Row(elements) => {
322 let mut out = Vec::with_capacity(elements.len());
323 for element in elements {
324 out.push(eval(element, ctx)?);
325 }
326 Ok(Value::Row(out))
327 }
328 Expr::Star | Expr::QualifiedStar(_) => {
329 Err(SQLError::Internal("`*` cannot be evaluated".into()))
330 }
331 Expr::Func {
332 name,
333 binding,
334 args,
335 ..
336 } => {
337 let call_args = evaluate_call_args(args, ctx)?;
338 if let Some(binding) = binding {
339 let engine = ctx.engine.ok_or_else(|| {
340 SQLError::Unsupported(
341 "bound user function requires a logical engine session".into(),
342 )
343 })?;
344 engine
345 .call_bound_user_function(binding, &call_args)
346 .unwrap_or_else(|| Err(SQLError::UnknownFunction(binding.name.clone())))
347 } else {
348 eval_function_call(name, call_args, ctx)
349 }
350 }
351 Expr::WindowCall { name, .. } => Err(SQLError::Unsupported(format!(
352 "window function `{name}` must be evaluated by the window-aware executor"
353 ))),
354 Expr::Case {
355 base,
356 when,
357 else_branch,
358 } => {
359 let base_value = match base {
360 Some(b) => Some(eval(b, ctx)?),
361 None => None,
362 };
363 for (cond, result) in when {
364 let matched = match &base_value {
365 Some(bv) => values_equal(bv, &eval(cond, ctx)?),
366 None => truthy(&eval(cond, ctx)?),
367 };
368 if matched {
369 return eval(result, ctx);
370 }
371 }
372 match else_branch {
373 Some(e) => eval(e, ctx),
374 None => Ok(Value::Null),
375 }
376 }
377 Expr::Cast { expr, ty } => {
378 let source_ty = explicit_expr_type(expr);
379 let v = eval(expr, ctx)?;
380 cast_value_from(&v, ty, source_ty)
381 }
382 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {
383 Err(SQLError::Unsupported(
384 "query-valued expressions must be lowered to physical ScalarExpr/QueryPlan slots"
385 .into(),
386 ))
387 }
388 Expr::Binary { op, lhs, rhs } => eval_binary(*op, lhs, rhs, ctx),
389 Expr::UnaryMinus(inner) => {
390 let source_ty = explicit_expr_type(inner);
391 let value = eval(inner, ctx)?;
392 negate_value(&value, source_ty)
393 }
394 Expr::Not(inner) => {
395 let v = eval(inner, ctx)?;
397 if matches!(v, Value::Null) {
398 return Ok(Value::Null);
399 }
400 Ok(Value::Bool(!truthy(&v)))
401 }
402 Expr::And(items) => {
403 let mut saw_null = false;
405 for item in items {
406 let v = eval(item, ctx)?;
407 if matches!(v, Value::Null) {
408 saw_null = true;
409 } else if !truthy(&v) {
410 return Ok(Value::Bool(false));
411 }
412 }
413 if saw_null {
414 return Ok(Value::Null);
415 }
416 Ok(Value::Bool(true))
417 }
418 Expr::Or(items) => {
419 let mut saw_null = false;
421 for item in items {
422 let v = eval(item, ctx)?;
423 if matches!(v, Value::Null) {
424 saw_null = true;
425 } else if truthy(&v) {
426 return Ok(Value::Bool(true));
427 }
428 }
429 if saw_null {
430 return Ok(Value::Null);
431 }
432 Ok(Value::Bool(false))
433 }
434 Expr::IsNull { expr, negated } => {
435 let v = eval(expr, ctx)?;
436 let is_null = matches!(v, Value::Null);
437 Ok(Value::Bool(if *negated { !is_null } else { is_null }))
438 }
439 Expr::Between { expr, low, high } => {
440 let v = eval(expr, ctx)?;
441 let lo = eval(low, ctx)?;
442 let hi = eval(high, ctx)?;
443 eval_between(&v, &lo, &hi)
444 }
445 Expr::InList {
446 expr,
447 list,
448 negated,
449 } => {
450 let v = eval(expr, ctx)?;
453 let mut saw_null = matches!(v, Value::Null);
454 for item in list {
455 let candidate = eval(item, ctx)?;
456 match values_equal_nullable(&v, &candidate) {
457 Some(true) => return Ok(Value::Bool(!*negated)),
458 Some(false) => {}
459 None => saw_null = true,
460 }
461 }
462 if saw_null {
463 return Ok(Value::Null);
464 }
465 Ok(Value::Bool(*negated))
466 }
467 }
468}
469
470fn explicit_expr_type(expr: &Expr) -> Option<&str> {
471 match expr {
472 Expr::Cast { ty, .. } => Some(ty),
473 Expr::Literal(Value::Int(value)) if i32::try_from(*value).is_ok() => Some("integer"),
474 Expr::Literal(Value::Int(_)) => Some("bigint"),
475 Expr::Literal(Value::Bytes(_)) => Some("bytea"),
476 _ => None,
477 }
478}
479
480fn eval_between(v: &Value, lo: &Value, hi: &Value) -> Result<Value> {
483 let ge = compare_nullable(v, lo)?.map(|ord| ord.is_ge());
484 let le = compare_nullable(v, hi)?.map(|ord| ord.is_le());
485 Ok(match (ge, le) {
486 (Some(false), _) | (_, Some(false)) => Value::Bool(false),
487 (Some(true), Some(true)) => Value::Bool(true),
488 _ => Value::Null,
489 })
490}
491
492fn normalized_function_name(name: &str) -> Cow<'_, str> {
493 let stripped = name.strip_prefix("pg_catalog.").unwrap_or(name);
494 if stripped.bytes().any(|byte| byte.is_ascii_uppercase()) {
495 Cow::Owned(stripped.to_ascii_lowercase())
496 } else {
497 Cow::Borrowed(stripped)
498 }
499}
500
501pub const NAMED_ARG_FUNCTION: &str = "__named_arg";
504
505pub const TO_HEX_INT4_FUNCTION: &str = "__to_hex_int4";
507pub const TO_HEX_INT8_FUNCTION: &str = "__to_hex_int8";
508
509#[must_use]
511pub fn builtin_scalar_function_strictness(name: &str, argument_count: usize) -> Option<bool> {
512 let normalized = normalized_function_name(name);
513 match normalized.as_ref() {
514 "coalesce" | "greatest" | "least" if argument_count >= 1 => Some(false),
515 "nullif" | "concat_op" if argument_count == 2 => Some(false),
516 "concat" | "format" | "json_build_array" | "jsonb_build_array" | "json_build_object"
517 | "jsonb_build_object" | "num_nulls" | "num_nonnulls" => Some(false),
518 "concat_ws" if argument_count >= 1 => Some(false),
519 "quote_nullable" | "pg_typeof" | "typeof" if argument_count == 1 => Some(false),
520 "array_cat" | "array_append" | "array_prepend" | "array_remove" | "array_positions"
521 if argument_count == 2 =>
522 {
523 Some(false)
524 }
525 "array_position" if matches!(argument_count, 2 | 3) => Some(false),
526 "array_replace" if argument_count == 3 => Some(false),
527 "array_fill" if matches!(argument_count, 2 | 3) => Some(false),
528 "array_to_string" if argument_count == 3 => Some(false),
529 "string_to_array" | "string_to_table" if matches!(argument_count, 2 | 3) => Some(false),
530 "overlaps" if argument_count == 4 => Some(false),
531 "abs" | "acos" | "array_dims" | "array_ndims" | "array_reverse" | "ascii" | "asin"
532 | "atan" | "bit_length" | "cardinality" | "casefold" | "cbrt" | "ceil" | "ceiling"
533 | "char_length" | "character_length" | "chr" | "cos" | "cosh" | "current_schemas"
534 | "degrees" | "exp" | "factorial" | "floor" | "gamma" | "initcap" | "isfinite"
535 | "json_array_length" | "jsonb_array_length" | "json_typeof" | "jsonb_typeof"
536 | "jsonb_pretty" | "justify_hours" | "length" | "lgamma" | "ln" | "log10" | "log2"
537 | "lower" | "md5" | "octet_length" | "quote_ident" | "quote_literal" | "radians"
538 | "reverse" | "row_to_json" | "sign" | "sin" | "sinh" | "sqrt" | "tan" | "tanh"
539 | "to_hex" | TO_HEX_INT4_FUNCTION | TO_HEX_INT8_FUNCTION | "to_json" | "to_jsonb"
540 | "to_timestamp" | "upper"
541 if argument_count == 1 =>
542 {
543 Some(true)
544 }
545 "age" | "btrim" | "ltrim" | "rtrim" | "trim" | "log" | "round" | "trunc"
546 | "json_strip_nulls" | "jsonb_strip_nulls"
547 if matches!(argument_count, 1 | 2) =>
548 {
549 Some(true)
550 }
551 "array_sort" if matches!(argument_count, 1..=3) => Some(true),
552 "array_length" | "array_lower" | "array_upper" | "atan2" | "date_part" | "date_trunc"
553 | "decode" | "encode" | "extract" | "gcd" | "lcm" | "left" | "mod" | "power" | "pow"
554 | "repeat" | "right" | "starts_with" | "position" | "strpos" | "to_char" | "to_date"
555 | "to_number" | "trim_array" | "like" | "ilike" | "similar_to" | "point"
556 | "st_distance" | "st_within"
557 if argument_count == 2 =>
558 {
559 Some(true)
560 }
561 "array_to_string" if argument_count == 2 => Some(true),
562 "substring" | "substr" | "lpad" | "rpad" if matches!(argument_count, 2 | 3) => Some(true),
563 "regexp_count" if matches!(argument_count, 2..=4) => Some(true),
564 "regexp_instr" if matches!(argument_count, 2..=7) => Some(true),
565 "regexp_like" | "regexp_match" | "regexp_matches" if matches!(argument_count, 2 | 3) => {
566 Some(true)
567 }
568 "regexp_replace" if matches!(argument_count, 3..=6) => Some(true),
569 "regexp_substr" if matches!(argument_count, 2..=6) => Some(true),
570 "replace" | "split_part" | "translate" | "make_date" if argument_count == 3 => Some(true),
571 "__between_symmetric" if argument_count == 3 => Some(true),
572 "overlay" | "jsonb_set" | "jsonb_insert" if matches!(argument_count, 3 | 4) => Some(true),
573 "json_extract_path"
574 | "jsonb_extract_path"
575 | "json_extract_path_text"
576 | "jsonb_extract_path_text"
577 if argument_count >= 2 =>
578 {
579 Some(true)
580 }
581 "json_contains" | "json_contained_by" | "json_delete_path" | "json_has_key"
582 | "json_has_any_key" | "json_has_all_keys" | "jsonb_path_exists" | "jsonpath_exists"
583 | "jsonb_path_match" | "jsonpath_match"
584 if argument_count == 2 =>
585 {
586 Some(true)
587 }
588 "make_timestamp" if matches!(argument_count, 6 | 7) => Some(true),
589 "make_interval" if argument_count <= 7 => Some(true),
590 "width_bucket" if argument_count == 4 => Some(true),
591 "st_dwithin" if matches!(argument_count, 2 | 3) => Some(true),
592 _ => None,
593 }
594}
595
596pub fn evaluate_call_args(
599 args: &[Expr],
600 ctx: &EvalContext<'_>,
601) -> Result<Vec<(Option<String>, Value)>> {
602 args.iter()
603 .map(|arg| match arg {
604 Expr::Func {
605 name, args: inner, ..
606 } if name == NAMED_ARG_FUNCTION => {
607 let Some(Expr::Literal(Value::Str(arg_name))) = inner.first() else {
608 return Err(SQLError::Internal("named argument without a name".into()));
609 };
610 let value_expr = inner
611 .get(1)
612 .ok_or_else(|| SQLError::Internal("named argument without a value".into()))?;
613 Ok((Some(arg_name.clone()), eval(value_expr, ctx)?))
614 }
615 other => Ok((None, eval(other, ctx)?)),
616 })
617 .collect()
618}
619
620pub fn eval_function_call(
628 name: &str,
629 call_args: Vec<(Option<String>, Value)>,
630 ctx: &EvalContext<'_>,
631) -> Result<Value> {
632 let lower = normalized_function_name(name);
633 let lower = lower.as_ref();
634 let evaluated: Vec<Value> = call_args.iter().map(|(_, value)| value.clone()).collect();
635
636 if lower == "random" {
637 if !evaluated.is_empty() {
638 return Err(SQLError::TypeMismatch("random takes no arguments".into()));
639 }
640 if let Some(engine) = ctx.engine {
641 if let Some(value) = engine.random_value().map_err(SQLError::Internal)? {
642 return Ok(Value::Float(value));
643 }
644 }
645 }
646 if lower == "setseed" {
647 let [value] = evaluated.as_slice() else {
648 return Err(SQLError::TypeMismatch("setseed takes 1 arg".into()));
649 };
650 let seed = to_f64(value)?;
651 if !seed.is_finite() || !(-1.0..=1.0).contains(&seed) {
652 return Err(SQLError::Routine {
653 sqlstate: "22023".into(),
654 message: format!("setseed parameter {seed} is out of allowed range [-1,1]"),
655 });
656 }
657 let engine = ctx.engine.ok_or_else(|| {
658 SQLError::Unsupported("setseed requires a logical engine session".into())
659 })?;
660 if !engine.set_random_seed(seed).map_err(SQLError::Internal)? {
661 return Err(SQLError::Unsupported(
662 "engine hook does not provide a session random stream".into(),
663 ));
664 }
665 return Ok(Value::Str(String::new()));
666 }
667
668 if lower == "current_schema" {
669 if !evaluated.is_empty() {
670 return Err(SQLError::TypeMismatch(
671 "current_schema takes no arguments".into(),
672 ));
673 }
674 let schema = ctx
675 .engine
676 .map(|engine| engine.current_schema())
677 .transpose()
678 .map_err(SQLError::Internal)?
679 .flatten()
680 .unwrap_or_else(|| "public".to_string());
681 return Ok(Value::Str(schema));
682 }
683 if lower == "current_schemas" {
684 let [Value::Bool(include_implicit)] = evaluated.as_slice() else {
685 return Err(SQLError::TypeMismatch(
686 "current_schemas takes one boolean argument".into(),
687 ));
688 };
689 let schemas = ctx
690 .engine
691 .map(|engine| engine.current_schemas(*include_implicit))
692 .transpose()
693 .map_err(SQLError::Internal)?
694 .flatten()
695 .unwrap_or_else(|| {
696 let mut schemas = Vec::new();
697 if *include_implicit {
698 schemas.push("pg_catalog".to_string());
699 }
700 schemas.push("public".to_string());
701 schemas
702 });
703 return ArrayValue::try_new(schemas.into_iter().map(Value::Str).collect())
704 .map(Value::Array)
705 .ok_or_else(|| SQLError::TypeMismatch("invalid current_schemas result".into()));
706 }
707
708 if crate::registry::is_registered(lower) {
712 if lower == "fts_match" && jsonpath_candidate(&evaluated) {
713 return jsonpath_match(&evaluated);
714 }
715 return Err(SQLError::Unsupported(format!(
716 "scalar evaluation of `{name}` is not supported (use the function registry)"
717 )));
718 }
719
720 if call_args.iter().any(|(name, _)| name.is_some()) {
721 if let Some(positional) = builtin_named_args(lower, &call_args) {
722 return eval_scalar_function(lower, &positional);
723 }
724 if let Some(engine) = ctx.engine {
725 if let Some(result) = engine.call_user_function(lower, &call_args) {
726 return result;
727 }
728 }
729 return Err(unknown_function_error(lower, &call_args));
730 }
731
732 if matches!(lower, "nextval" | "currval" | "setval") {
735 return eval_sequence_function(lower, &evaluated, ctx);
736 }
737 if let Some(engine) = ctx.engine.filter(|engine| engine.has_scalar_functions()) {
738 if let Some(result) = engine.call_scalar_function(lower, &evaluated) {
739 return result;
740 }
741 }
742 match eval_scalar_function(lower, &evaluated) {
743 Err(SQLError::UnknownFunction(_)) => {
746 if let Some(engine) = ctx.engine {
747 if let Some(result) = engine.call_user_function(lower, &call_args) {
748 return result;
749 }
750 }
751 Err(unknown_function_error(lower, &call_args))
752 }
753 other => other,
754 }
755}
756
757fn builtin_named_args(function: &str, call_args: &[(Option<String>, Value)]) -> Option<Vec<Value>> {
758 let names: &[&str] = match function {
759 "regexp_count" => match call_args.len() {
760 2 => &["string", "pattern"],
761 3 => &["string", "pattern", "start"],
762 4 => &["string", "pattern", "start", "flags"],
763 _ => return None,
764 },
765 "regexp_like" => match call_args.len() {
766 2 => &["string", "pattern"],
767 3 => &["string", "pattern", "flags"],
768 _ => return None,
769 },
770 "regexp_substr" => match call_args.len() {
771 2 => &["string", "pattern"],
772 3 => &["string", "pattern", "start"],
773 4 => &["string", "pattern", "start", "N"],
774 5 => &["string", "pattern", "start", "N", "flags"],
775 6 => &["string", "pattern", "start", "N", "flags", "subexpr"],
776 _ => return None,
777 },
778 "regexp_instr" => match call_args.len() {
779 2 => &["string", "pattern"],
780 3 => &["string", "pattern", "start"],
781 4 => &["string", "pattern", "start", "N"],
782 5 => &["string", "pattern", "start", "N", "endoption"],
783 6 => &["string", "pattern", "start", "N", "endoption", "flags"],
784 7 => &[
785 "string",
786 "pattern",
787 "start",
788 "N",
789 "endoption",
790 "flags",
791 "subexpr",
792 ],
793 _ => return None,
794 },
795 "regexp_replace" => match call_args.len() {
796 3 => &["string", "pattern", "replacement"],
797 4 if call_args
798 .iter()
799 .any(|(name, _)| name.as_deref() == Some("flags")) =>
800 {
801 &["string", "pattern", "replacement", "flags"]
802 }
803 4 => &["string", "pattern", "replacement", "start"],
804 5 => &["string", "pattern", "replacement", "start", "N"],
805 6 => &["string", "pattern", "replacement", "start", "N", "flags"],
806 _ => return None,
807 },
808 "make_interval" => return make_interval_named_args(call_args),
809 _ => return None,
810 };
811 reorder_named_args(call_args, names)
812}
813
814fn reorder_named_args(
815 call_args: &[(Option<String>, Value)],
816 parameter_names: &[&str],
817) -> Option<Vec<Value>> {
818 if call_args.len() != parameter_names.len() {
819 return None;
820 }
821 let mut slots = vec![None; parameter_names.len()];
822 let mut positional_index = 0;
823 let mut saw_named = false;
824 for (name, value) in call_args {
825 let slot = if let Some(name) = name {
826 saw_named = true;
827 parameter_names
828 .iter()
829 .position(|candidate| candidate == name)?
830 } else {
831 if saw_named {
832 return None;
833 }
834 let slot = positional_index;
835 positional_index += 1;
836 slot
837 };
838 if slots.get(slot)?.is_some() {
839 return None;
840 }
841 slots[slot] = Some(value.clone());
842 }
843 slots.into_iter().collect()
844}
845
846fn make_interval_named_args(call_args: &[(Option<String>, Value)]) -> Option<Vec<Value>> {
850 const NAMES: [&str; 7] = ["years", "months", "weeks", "days", "hours", "mins", "secs"];
851 let mut positional = vec![Value::Int(0); NAMES.len()];
852 let mut positional_index = 0;
853 let mut saw_named = false;
854 let mut assigned = [false; NAMES.len()];
855 for (name, value) in call_args {
856 let slot = if let Some(name) = name {
857 saw_named = true;
858 NAMES.iter().position(|candidate| candidate == name)?
859 } else {
860 if saw_named {
861 return None;
862 }
863 let slot = positional_index;
864 positional_index += 1;
865 slot
866 };
867 if slot >= NAMES.len() || assigned[slot] {
868 return None;
869 }
870 assigned[slot] = true;
871 positional[slot] = value.clone();
872 }
873 Some(positional)
874}
875
876pub fn value_type_name(v: &Value) -> &'static str {
878 match v {
879 Value::Null => "unknown",
880 Value::Bool(_) => "boolean",
881 Value::Int(_) => "integer",
882 Value::Float(_) => "double precision",
883 Value::Str(_) => "text",
884 Value::FixedChar(_) => "character",
885 Value::Bytes(_) => "bytea",
886 Value::Temporal(TemporalValue::Interval { .. }) => "interval",
887 Value::Temporal(_) => "timestamp",
888 Value::Decimal(_) => "numeric",
889 Value::Json(_) => "json",
890 Value::JsonB(_) => "jsonb",
891 Value::Array(_) => "anyarray",
892 Value::List(_) => "anyarray",
893 Value::Row(_) | Value::Record(_) => "record",
894 Value::Map(_) => "jsonb",
895 }
896}
897
898pub fn unknown_function_error(name: &str, args: &[(Option<String>, Value)]) -> SQLError {
901 let types = args
902 .iter()
903 .map(|(arg_name, value)| match arg_name {
904 Some(arg_name) => format!("{arg_name} => {}", value_type_name(value)),
905 None => value_type_name(value).to_string(),
906 })
907 .collect::<Vec<_>>()
908 .join(", ");
909 SQLError::Routine {
910 sqlstate: "42883".into(),
911 message: format!("function {name}({types}) does not exist"),
912 }
913}
914
915#[cfg(test)]
916mod tests;