1use std::borrow::Cow;
11
12use uqa_core::{ArrayValue, DecimalValue, TemporalValue, Value};
13
14use crate::ast::{
15 BinaryOp, ColumnType, Expr, FunctionBinding, FunctionDispatch, FunctionResolutionError,
16 InternalColumnRef,
17};
18use crate::error::{Result, SQLError};
19use crate::params::SQLParam;
20use crate::result::ResultRow;
21
22mod array_transform;
23mod encoding;
24mod json;
25mod json_strip;
26mod random;
27mod range;
28mod time;
29mod uuid;
30
31pub use array_transform::argument_positions as array_transform_argument_positions;
32use encoding::{base64_decode, base64_encode, md5_hex};
33pub use json::value_to_json_text;
34use json::{
35 format_jsonb_pretty, json_build_array_value, json_build_object_value, json_concat,
36 json_contained_by, json_contains, json_delete, json_delete_path, json_extract_path,
37 json_has_key, json_has_keys, json_typeof, jsonb_insert, jsonb_set, jsonpath_candidate,
38 jsonpath_exists, jsonpath_match, parse_json, strip_nulls, typed_json_value, value_to_json,
39};
40pub use json_strip::argument_positions as json_strip_nulls_argument_positions;
41use json_strip::strip_json_nulls_text;
42pub use range::{
43 multirange_from_ranges, parse_multirange, parse_range, CanonicalMultirange, CanonicalRange,
44};
45use time::{
46 age_between, coerce_temporal, date_trunc_value, extract_from_value, format_pg_number,
47 format_temporal, hex_encode, make_timestamp, parse_timestamp, pg_to_chrono_fmt,
48};
49pub use uuid::parse_uuid_bytes;
50use uuid::{extract_uuid_timestamp, extract_uuid_version, generate_random_uuid, generate_uuid_v7};
51mod binary;
52mod casting;
53mod conversion;
54mod scalar_array;
55mod scalar_core;
56mod scalar_dispatch;
57mod scalar_geospatial;
58mod scalar_helpers;
59mod scalar_json;
60mod scalar_math;
61mod scalar_postgres;
62mod scalar_range;
63mod scalar_temporal;
64
65use binary::{
66 compare, compare_nullable, eval_binary, eval_comparison_op, values_equal, values_equal_nullable,
67};
68pub(crate) use binary::{division_by_zero, out_of_range};
69pub use binary::{
70 eval_binary_values, eval_binary_values_with_integer_width, eval_comparison_truth,
71 integer_width_for_literal, integer_width_for_type, truthy, IntegerWidth,
72};
73pub use casting::{
74 array_dimensions, cast_value, cast_value_from, negate_value, parse_pg_array_literal,
75};
76pub(crate) use conversion::to_f64;
77use conversion::{
78 allocation_error, coerce_i64, expect_str, float1, float_to_i64_rounded, float_to_i64_trunc,
79 gcd_i64, initcap_str, nonnegative_usize, string1, to_decimal, to_i64,
80};
81pub use conversion::{array_value_to_string, value_to_string, vector_value_to_string};
82pub use conversion::{value_to_tensor, value_to_vector};
83use scalar_dispatch::{eval_scalar_function, eval_sequence_function};
84use scalar_helpers::{
85 compile_pg_regex, point_xy, quote_literal, similar_to_regex, trim_chars, typeof_value,
86};
87pub use scalar_helpers::{quote_ident, CompiledLikePattern};
88
89#[must_use]
90pub fn coercion_type_name(ty: &ColumnType) -> String {
91 match ty {
92 ColumnType::Domain { base, .. } => coercion_type_name(base),
93 ColumnType::Array(element) => format!("{}[]", coercion_type_name(element)),
94 _ => ty.sql_name(),
95 }
96}
97
98pub trait EngineHook {
103 fn nextval(&self, name: &str) -> Result<i64>;
104 fn currval(&self, name: &str) -> Result<i64>;
105 fn setval(&self, name: &str, value: i64) -> Result<i64>;
106
107 fn call_scalar_function(&self, _name: &str, _args: &[Value]) -> Option<Result<Value>> {
108 None
109 }
110
111 fn call_bound_builtin_function(
116 &self,
117 _binding: &crate::ast::FunctionBinding,
118 _args: &[(Option<String>, Value)],
119 ) -> Option<Result<Value>> {
120 None
121 }
122
123 fn has_scalar_functions(&self) -> bool {
124 true
125 }
126
127 fn resolve_type_name(&self, _name: &str) -> std::result::Result<Option<ColumnType>, String> {
129 Ok(None)
130 }
131
132 fn resolve_regclass(&self, _name: &str) -> std::result::Result<Option<i64>, String> {
134 Ok(None)
135 }
136
137 fn resolve_regtype_output(
139 &self,
140 _ty: &ColumnType,
141 _oid: i64,
142 ) -> std::result::Result<Option<String>, String> {
143 Ok(None)
144 }
145
146 fn current_schema(&self) -> std::result::Result<Option<String>, String> {
150 Ok(None)
151 }
152
153 fn current_user(&self) -> std::result::Result<Option<String>, String> {
154 Ok(None)
155 }
156
157 fn session_user(&self) -> std::result::Result<Option<String>, String> {
158 Ok(None)
159 }
160
161 fn current_schemas(
163 &self,
164 _include_implicit: bool,
165 ) -> std::result::Result<Option<Vec<String>>, String> {
166 Ok(None)
167 }
168
169 fn random_value(&self) -> std::result::Result<Option<f64>, String> {
172 Ok(None)
173 }
174
175 fn random_u64(&self) -> std::result::Result<Option<u64>, String> {
179 Ok(None)
180 }
181
182 fn set_random_seed(&self, _seed: f64) -> std::result::Result<bool, String> {
185 Ok(false)
186 }
187
188 fn call_user_function(
193 &self,
194 _name: &str,
195 _args: &[(Option<String>, Value)],
196 ) -> Option<Result<Value>> {
197 None
198 }
199
200 fn call_bound_user_function(
201 &self,
202 _binding: &crate::ast::FunctionBinding,
203 _args: &[(Option<String>, Value)],
204 ) -> Option<Result<Value>> {
205 None
206 }
207}
208
209pub fn format_regtype_value(
211 value: &Value,
212 ty: &ColumnType,
213 engine: Option<&dyn EngineHook>,
214) -> Result<Option<String>> {
215 if matches!(value, Value::Null) {
216 return Ok(None);
217 }
218 if let ColumnType::Array(element) = ty {
219 if !matches!(
220 element.as_ref(),
221 ColumnType::Regproc
222 | ColumnType::Regclass
223 | ColumnType::Regnamespace
224 | ColumnType::Regtype
225 ) {
226 return Ok(None);
227 }
228 let Value::Array(array) = value else {
229 return Ok(Some(value_to_string(value)));
230 };
231 let elements = format_regtype_array_elements(array.elements(), element, engine)?;
232 let formatted = array.with_elements(elements).ok_or_else(|| {
233 SQLError::Internal("regtype array output changed the array dimensions".into())
234 })?;
235 return Ok(Some(array_value_to_string(&formatted)));
236 }
237 if !matches!(
238 ty,
239 ColumnType::Regproc | ColumnType::Regclass | ColumnType::Regnamespace | ColumnType::Regtype
240 ) {
241 return Ok(None);
242 }
243 let Value::Int(oid) = value else {
244 return Ok(Some(value_to_string(value)));
245 };
246 if *oid == 0 {
247 return Ok(Some("-".into()));
248 }
249 let resolved = engine
250 .map(|engine| engine.resolve_regtype_output(ty, *oid))
251 .transpose()
252 .map_err(SQLError::Internal)?
253 .flatten();
254 Ok(Some(resolved.unwrap_or_else(|| oid.to_string())))
255}
256
257fn format_regtype_array_elements(
258 values: &[Value],
259 element: &ColumnType,
260 engine: Option<&dyn EngineHook>,
261) -> Result<Vec<Value>> {
262 values
263 .iter()
264 .map(|value| match value {
265 Value::Null => Ok(Value::Null),
266 Value::List(nested) => {
267 format_regtype_array_elements(nested, element, engine).map(Value::List)
268 }
269 other => format_regtype_value(other, element, engine)
270 .map(|text| text.map_or_else(|| other.clone(), Value::Str)),
271 })
272 .collect()
273}
274
275pub fn cast_value_with_type_resolution(
277 value: &Value,
278 source_ty: Option<&str>,
279 target_ty: &str,
280 engine: Option<&dyn EngineHook>,
281) -> Result<Value> {
282 let resolved_source = match (engine, source_ty) {
283 (Some(engine), Some(source_ty)) => engine
284 .resolve_type_name(source_ty)
285 .map_err(SQLError::Internal)?
286 .map(|ty| coercion_type_name(&ty)),
287 _ => None,
288 };
289 let source_ty = resolved_source.as_deref().or(source_ty);
290 let resolved_target = engine
291 .map(|engine| engine.resolve_type_name(target_ty))
292 .transpose()
293 .map_err(SQLError::Internal)?
294 .flatten();
295 let target_ty = resolved_target.as_ref().map_or_else(
296 || Cow::Borrowed(target_ty),
297 |ty| Cow::Owned(coercion_type_name(ty)),
298 );
299 if target_ty.eq_ignore_ascii_case("text") {
300 if let Some(source_ty) = source_ty.and_then(|source| ColumnType::from_sql_name(source).ok())
301 {
302 if let Some(text) = format_regtype_value(value, &source_ty, engine)? {
303 return Ok(Value::Str(text));
304 }
305 }
306 }
307 if target_ty.eq_ignore_ascii_case("regclass") {
308 if let (Some(engine), Value::Str(name) | Value::FixedChar(name)) = (engine, value) {
309 return engine
310 .resolve_regclass(name)
311 .map_err(SQLError::Internal)?
312 .map(Value::Int)
313 .ok_or_else(|| SQLError::Routine {
314 sqlstate: "42P01".into(),
315 message: format!("relation \"{name}\" does not exist"),
316 });
317 }
318 }
319 cast_value_from(value, &target_ty, source_ty)
320}
321
322pub trait RowLookup {
326 fn column(&self, name: &str) -> Option<&Value>;
327
328 fn column_is_ambiguous(&self, _name: &str) -> bool {
332 false
333 }
334
335 fn qualified_column(&self, qualifier: &str, column: &str) -> Option<&Value>;
336
337 fn qualified_column_is_ambiguous(&self, _qualifier: &str, _column: &str) -> bool {
339 false
340 }
341
342 fn positional_column(&self, _index: usize) -> Option<&Value> {
347 None
348 }
349
350 fn internal_column(&self, _column: InternalColumnRef) -> Option<&Value> {
353 None
354 }
355
356 fn score_source(&self, _qualifier: Option<&str>) -> Option<&Value> {
358 None
359 }
360
361 fn score_source_is_ambiguous(&self, _qualifier: Option<&str>) -> bool {
363 false
364 }
365
366 fn visit_columns(&self, _visitor: &mut dyn FnMut(&str, &Value)) {}
371}
372
373impl RowLookup for ResultRow {
374 fn column(&self, name: &str) -> Option<&Value> {
375 self.get(name)
376 }
377
378 fn qualified_column(&self, _qualifier: &str, _column: &str) -> Option<&Value> {
379 None
380 }
381
382 fn visit_columns(&self, visitor: &mut dyn FnMut(&str, &Value)) {
383 for (column, value) in self {
384 visitor(column, value);
385 }
386 }
387}
388
389pub struct EvalContext<'a> {
390 pub row: Option<&'a ResultRow>,
391 row_lookup: Option<&'a dyn RowLookup>,
392 pub params: &'a [SQLParam],
393 pub engine: Option<&'a dyn EngineHook>,
394}
395
396impl<'a> EvalContext<'a> {
397 pub fn new(row: Option<&'a ResultRow>, params: &'a [SQLParam]) -> Self {
398 Self {
399 row,
400 row_lookup: row.map(|row| row as &dyn RowLookup),
401 params,
402 engine: None,
403 }
404 }
405
406 pub fn from_row_lookup(row: &'a dyn RowLookup, params: &'a [SQLParam]) -> Self {
407 Self {
408 row: None,
412 row_lookup: Some(row),
413 params,
414 engine: None,
415 }
416 }
417
418 pub fn with_engine(mut self, engine: &'a dyn EngineHook) -> Self {
419 self.engine = Some(engine);
420 self
421 }
422
423 fn row_lookup(&self) -> Result<&'a dyn RowLookup> {
424 self.row_lookup
425 .ok_or_else(|| SQLError::Internal("column reference without row context".into()))
426 }
427
428 pub fn column_value(&self, name: &str) -> Result<Value> {
432 if self.row_lookup()?.column_is_ambiguous(name) {
433 return Err(SQLError::AmbiguousColumn(name.to_string()));
434 }
435 Ok(self
436 .row_lookup()?
437 .column(name)
438 .cloned()
439 .unwrap_or(Value::Null))
440 }
441
442 pub fn qualified_column_value(&self, qualifier: &str, column: &str) -> Result<Value> {
444 if self
445 .row_lookup()?
446 .qualified_column_is_ambiguous(qualifier, column)
447 {
448 return Err(SQLError::AmbiguousColumn(format!("{qualifier}.{column}")));
449 }
450 Ok(self
451 .row_lookup()?
452 .qualified_column(qualifier, column)
453 .cloned()
454 .unwrap_or(Value::Null))
455 }
456}
457
458pub fn eval(expr: &Expr, ctx: &EvalContext<'_>) -> Result<Value> {
463 match expr {
464 Expr::Default => Err(SQLError::Internal(
465 "DEFAULT reached scalar expression evaluation without a mutation target".into(),
466 )),
467 Expr::Literal(v) => Ok(v.clone()),
468 Expr::Param(i) => match i.checked_sub(1).and_then(|index| ctx.params.get(index)) {
469 Some(SQLParam::Scalar(v) | SQLParam::TypedScalar { value: v, .. }) => Ok(v.clone()),
470 Some(SQLParam::Vector(v)) => Ok(Value::List(
471 v.iter().map(|x| Value::Float(f64::from(*x))).collect(),
472 )),
473 Some(SQLParam::Tensor(vectors)) => Ok(Value::List(
474 vectors
475 .iter()
476 .map(|vector| {
477 Value::List(vector.iter().map(|x| Value::Float(f64::from(*x))).collect())
478 })
479 .collect(),
480 )),
481 None => Err(SQLError::MissingParam(*i)),
482 },
483 Expr::Column(name) => {
484 if ctx.row_lookup()?.column_is_ambiguous(name) {
488 return Err(SQLError::AmbiguousColumn(name.clone()));
489 }
490 Ok(ctx
491 .row_lookup()?
492 .column(name)
493 .cloned()
494 .unwrap_or(Value::Null))
495 }
496 Expr::QualifiedColumn { qualifier, column } => {
497 if ctx
498 .row_lookup()?
499 .qualified_column_is_ambiguous(qualifier, column)
500 {
501 return Err(SQLError::AmbiguousColumn(format!("{qualifier}.{column}")));
502 }
503 Ok(ctx
504 .row_lookup()?
505 .qualified_column(qualifier, column)
506 .cloned()
507 .unwrap_or(Value::Null))
508 }
509 Expr::InternalColumn(column) => ctx
510 .row_lookup()?
511 .internal_column(*column)
512 .cloned()
513 .ok_or_else(|| {
514 SQLError::Internal(format!(
515 "internal relation attribute {column:?} is unavailable"
516 ))
517 }),
518 Expr::Array(elements) => {
519 let mut out = Vec::with_capacity(elements.len());
520 for e in elements {
521 out.push(eval(e, ctx)?);
522 }
523 ArrayValue::try_new(out).map(Value::Array).ok_or_else(|| {
524 SQLError::TypeMismatch(
525 "multidimensional arrays must have matching dimensions".into(),
526 )
527 })
528 }
529 Expr::Row(elements) => {
530 let mut out = Vec::with_capacity(elements.len());
531 for element in elements {
532 out.push(eval(element, ctx)?);
533 }
534 Ok(Value::Row(out))
535 }
536 Expr::Star | Expr::QualifiedStar(_) => {
537 Err(SQLError::Internal("`*` cannot be evaluated".into()))
538 }
539 Expr::Func {
540 name,
541 binding,
542 args,
543 ..
544 } => {
545 let call_args = evaluate_call_args(args, ctx)?;
546 if let Some(binding) = binding {
547 if let Some(FunctionResolutionError::UndefinedFunction { signature }) =
548 binding.resolution_error.as_ref()
549 {
550 return Err(SQLError::Routine {
551 sqlstate: "42883".into(),
552 message: format!("function {signature} does not exist"),
553 });
554 }
555 if binding.builtin {
556 return eval_bound_builtin_function_call(binding, call_args, ctx);
557 }
558 let engine = ctx.engine.ok_or_else(|| {
559 SQLError::Unsupported(
560 "bound user function requires a logical engine session".into(),
561 )
562 })?;
563 engine
564 .call_bound_user_function(binding, &call_args)
565 .unwrap_or_else(|| Err(SQLError::UnknownFunction(binding.name.clone())))
566 } else {
567 eval_function_call(name, call_args, ctx)
568 }
569 }
570 Expr::WindowCall { name, .. } => Err(SQLError::Unsupported(format!(
571 "window function `{name}` must be evaluated by the window-aware executor"
572 ))),
573 Expr::Case {
574 base,
575 when,
576 else_branch,
577 } => {
578 let base_value = match base {
579 Some(b) => Some(eval(b, ctx)?),
580 None => None,
581 };
582 for (cond, result) in when {
583 let matched = match &base_value {
584 Some(bv) => values_equal(bv, &eval(cond, ctx)?),
585 None => truthy(&eval(cond, ctx)?),
586 };
587 if matched {
588 return eval(result, ctx);
589 }
590 }
591 match else_branch {
592 Some(e) => eval(e, ctx),
593 None => Ok(Value::Null),
594 }
595 }
596 Expr::Cast { expr, ty } => {
597 let source_ty = explicit_expr_type(expr);
598 let v = eval(expr, ctx)?;
599 cast_value_with_type_resolution(&v, source_ty, ty, ctx.engine)
600 }
601 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {
602 Err(SQLError::Unsupported(
603 "query-valued expressions must be lowered to physical ScalarExpr/QueryPlan slots"
604 .into(),
605 ))
606 }
607 Expr::Binary { op, lhs, rhs } => eval_binary(*op, lhs, rhs, ctx),
608 Expr::UnaryMinus(inner) => {
609 let source_ty = explicit_expr_type(inner);
610 let value = eval(inner, ctx)?;
611 negate_value(&value, source_ty)
612 }
613 Expr::Not(inner) => {
614 let v = eval(inner, ctx)?;
616 if matches!(v, Value::Null) {
617 return Ok(Value::Null);
618 }
619 Ok(Value::Bool(!truthy(&v)))
620 }
621 Expr::And(items) => {
622 let mut saw_null = false;
624 for item in items {
625 let v = eval(item, ctx)?;
626 if matches!(v, Value::Null) {
627 saw_null = true;
628 } else if !truthy(&v) {
629 return Ok(Value::Bool(false));
630 }
631 }
632 if saw_null {
633 return Ok(Value::Null);
634 }
635 Ok(Value::Bool(true))
636 }
637 Expr::Or(items) => {
638 let mut saw_null = false;
640 for item in items {
641 let v = eval(item, ctx)?;
642 if matches!(v, Value::Null) {
643 saw_null = true;
644 } else if truthy(&v) {
645 return Ok(Value::Bool(true));
646 }
647 }
648 if saw_null {
649 return Ok(Value::Null);
650 }
651 Ok(Value::Bool(false))
652 }
653 Expr::IsNull { expr, negated } => {
654 let v = eval(expr, ctx)?;
655 let is_null = matches!(v, Value::Null);
656 Ok(Value::Bool(if *negated { !is_null } else { is_null }))
657 }
658 Expr::Between { expr, low, high } => {
659 let v = eval(expr, ctx)?;
660 let lo = eval(low, ctx)?;
661 let hi = eval(high, ctx)?;
662 eval_between(&v, &lo, &hi)
663 }
664 Expr::InList {
665 expr,
666 list,
667 negated,
668 } => {
669 let v = eval(expr, ctx)?;
672 let mut saw_null = matches!(v, Value::Null);
673 for item in list {
674 let candidate = eval(item, ctx)?;
675 match values_equal_nullable(&v, &candidate) {
676 Some(true) => return Ok(Value::Bool(!*negated)),
677 Some(false) => {}
678 None => saw_null = true,
679 }
680 }
681 if saw_null {
682 return Ok(Value::Null);
683 }
684 Ok(Value::Bool(*negated))
685 }
686 }
687}
688
689fn explicit_expr_type(expr: &Expr) -> Option<&str> {
690 match expr {
691 Expr::Cast { ty, .. } => Some(ty),
692 Expr::Literal(Value::Int(value)) if i32::try_from(*value).is_ok() => Some("integer"),
693 Expr::Literal(Value::Int(_)) => Some("bigint"),
694 Expr::Literal(Value::Bytes(_)) => Some("bytea"),
695 _ => None,
696 }
697}
698
699fn eval_between(v: &Value, lo: &Value, hi: &Value) -> Result<Value> {
702 let ge = compare_nullable(v, lo)?.map(|ord| ord.is_ge());
703 let le = compare_nullable(v, hi)?.map(|ord| ord.is_le());
704 Ok(match (ge, le) {
705 (Some(false), _) | (_, Some(false)) => Value::Bool(false),
706 (Some(true), Some(true)) => Value::Bool(true),
707 _ => Value::Null,
708 })
709}
710
711fn normalized_function_name(name: &str) -> Cow<'_, str> {
712 let stripped = name.strip_prefix("pg_catalog.").unwrap_or(name);
713 if stripped.bytes().any(|byte| byte.is_ascii_uppercase()) {
714 Cow::Owned(stripped.to_ascii_lowercase())
715 } else {
716 Cow::Borrowed(stripped)
717 }
718}
719
720fn binding_dispatch(binding: Option<&FunctionBinding>) -> Option<FunctionDispatch> {
721 binding.and_then(|binding| binding.dispatch)
722}
723
724fn direct_variadic_argument_value(argument: &Expr) -> Option<&Expr> {
725 let Expr::Func { binding, args, .. } = argument else {
726 return None;
727 };
728 if binding_dispatch(binding.as_ref()) != Some(FunctionDispatch::VariadicArgument) {
729 return None;
730 }
731 let [value] = args.as_slice() else {
732 return None;
733 };
734 Some(value)
735}
736
737fn named_argument_value(argument: &Expr) -> Option<&Expr> {
738 let Expr::Func { binding, args, .. } = argument else {
739 return None;
740 };
741 if binding_dispatch(binding.as_ref()) == Some(FunctionDispatch::NamedArgument) {
742 args.get(1)
743 } else {
744 None
745 }
746}
747
748#[must_use]
750pub fn wrap_variadic_argument(mut argument: Expr) -> Expr {
751 if variadic_argument_value(&argument).is_some() {
752 return argument;
753 }
754 if let Expr::Func { binding, args, .. } = &mut argument {
755 if binding_dispatch(binding.as_ref()) == Some(FunctionDispatch::NamedArgument)
756 && args.len() == 2
757 {
758 let value = args.remove(1);
759 args.push(variadic_argument_marker(value));
760 return argument;
761 }
762 }
763 variadic_argument_marker(argument)
764}
765
766fn variadic_argument_marker(value: Expr) -> Expr {
767 let binding = FunctionBinding::dispatched(FunctionDispatch::VariadicArgument);
768 Expr::Func {
769 name: binding.name.clone(),
770 binding: Some(binding),
771 args: vec![value],
772 distinct: false,
773 order_by: Vec::new(),
774 filter: None,
775 }
776}
777
778#[must_use]
780pub fn variadic_argument_value(argument: &Expr) -> Option<&Expr> {
781 let value = named_argument_value(argument).unwrap_or(argument);
782 direct_variadic_argument_value(value)
783}
784
785#[must_use]
787pub fn call_argument_value(argument: &Expr) -> &Expr {
788 let value = named_argument_value(argument).unwrap_or(argument);
789 direct_variadic_argument_value(value).unwrap_or(value)
790}
791
792pub fn validate_named_argument_order<'a>(
796 argument_names: impl IntoIterator<Item = Option<&'a str>>,
797) -> Result<()> {
798 let mut saw_named = false;
799 let mut named = Vec::new();
800 for argument_name in argument_names {
801 let Some(argument_name) = argument_name else {
802 if saw_named {
803 return Err(SQLError::Routine {
804 sqlstate: "42601".into(),
805 message: "positional argument cannot follow named argument".into(),
806 });
807 }
808 continue;
809 };
810 saw_named = true;
811 if named.contains(&argument_name) {
812 return Err(SQLError::Routine {
813 sqlstate: "42601".into(),
814 message: format!("argument name \"{argument_name}\" used more than once"),
815 });
816 }
817 named.push(argument_name);
818 }
819 Ok(())
820}
821
822#[must_use]
824pub fn builtin_scalar_function_strictness(name: &str, argument_count: usize) -> Option<bool> {
825 let normalized = normalized_function_name(name);
826 match normalized.as_ref() {
827 "int4range" | "int8range" | "numrange" | "daterange" | "tsrange" | "tstzrange"
828 if matches!(argument_count, 2 | 3) =>
829 {
830 Some(false)
831 }
832 "int4multirange" | "int8multirange" | "nummultirange" | "datemultirange"
833 | "tsmultirange" | "tstzmultirange"
834 if argument_count <= 1 =>
835 {
836 Some(true)
837 }
838 "multirange" if argument_count == 1 => Some(true),
839 "coalesce" | "greatest" | "least" if argument_count >= 1 => Some(false),
840 "nullif" | "concat_op" if argument_count == 2 => Some(false),
841 "concat" | "format" | "json_build_array" | "jsonb_build_array" | "json_build_object"
842 | "jsonb_build_object" | "num_nulls" | "num_nonnulls" => Some(false),
843 "concat_ws" if argument_count >= 1 => Some(false),
844 "quote_nullable" | "pg_typeof" | "typeof" if argument_count == 1 => Some(false),
845 "array_cat" | "array_append" | "array_prepend" | "array_remove" | "array_positions"
846 if argument_count == 2 =>
847 {
848 Some(false)
849 }
850 "array_position" if matches!(argument_count, 2 | 3) => Some(false),
851 "array_replace" if argument_count == 3 => Some(false),
852 "array_fill" if matches!(argument_count, 2 | 3) => Some(false),
853 "array_to_string" if argument_count == 3 => Some(false),
854 "string_to_array" | "string_to_table" if matches!(argument_count, 2 | 3) => Some(false),
855 "overlaps" if argument_count == 4 => Some(false),
856 "abs"
857 | "acos"
858 | "array_dims"
859 | "array_ndims"
860 | "array_reverse"
861 | "ascii"
862 | "asin"
863 | "atan"
864 | "bit_length"
865 | "cardinality"
866 | "casefold"
867 | "cbrt"
868 | "ceil"
869 | "ceiling"
870 | "char_length"
871 | "character_length"
872 | "chr"
873 | "cos"
874 | "cosh"
875 | "current_schemas"
876 | "degrees"
877 | "exp"
878 | "factorial"
879 | "floor"
880 | "gamma"
881 | "initcap"
882 | "isfinite"
883 | "json_array_length"
884 | "jsonb_array_length"
885 | "json_typeof"
886 | "jsonb_typeof"
887 | "jsonb_pretty"
888 | "justify_hours"
889 | "length"
890 | "lgamma"
891 | "ln"
892 | "log10"
893 | "log2"
894 | "lower"
895 | "md5"
896 | "octet_length"
897 | "quote_ident"
898 | "quote_literal"
899 | "radians"
900 | "reverse"
901 | "row_to_json"
902 | "sign"
903 | "sin"
904 | "sinh"
905 | "sqrt"
906 | "tan"
907 | "tanh"
908 | "to_bin"
909 | "to_hex"
910 | "to_oct"
911 | "to_json"
912 | "to_jsonb"
913 | "to_regclass"
914 | "to_timestamp"
915 | "upper"
916 | "uuid_extract_timestamp"
917 | "uuid_extract_version"
918 if argument_count == 1 =>
919 {
920 Some(true)
921 }
922 "random" if argument_count == 2 => Some(true),
923 "age" | "btrim" | "ltrim" | "rtrim" | "trim" | "log" | "round" | "trunc"
924 | "json_strip_nulls" | "jsonb_strip_nulls"
925 if matches!(argument_count, 1 | 2) =>
926 {
927 Some(true)
928 }
929 "array_sort" if matches!(argument_count, 1..=3) => Some(true),
930 "array_length" | "array_lower" | "array_upper" | "atan2" | "date_part" | "date_trunc"
931 | "decode" | "encode" | "extract" | "gcd" | "lcm" | "left" | "mod" | "power" | "pow"
932 | "repeat" | "right" | "starts_with" | "position" | "strpos" | "to_char" | "to_date"
933 | "to_number" | "trim_array" | "point" | "st_distance" | "st_within"
934 if argument_count == 2 =>
935 {
936 Some(true)
937 }
938 "like" | "ilike" | "similar_to" if argument_count == 2 => Some(true),
939 "like" | "ilike" | "similar_to" if argument_count == 3 => Some(false),
940 "array_to_string" if argument_count == 2 => Some(true),
941 "substring" | "substr" | "lpad" | "rpad" if matches!(argument_count, 2 | 3) => Some(true),
942 "regexp_count" if matches!(argument_count, 2..=4) => Some(true),
943 "regexp_instr" if matches!(argument_count, 2..=7) => Some(true),
944 "regexp_like" | "regexp_match" | "regexp_matches" if matches!(argument_count, 2 | 3) => {
945 Some(true)
946 }
947 "regexp_replace" if matches!(argument_count, 3..=6) => Some(true),
948 "regexp_substr" if matches!(argument_count, 2..=6) => Some(true),
949 "replace" | "split_part" | "translate" | "make_date" if argument_count == 3 => Some(true),
950 "overlay" | "jsonb_set" | "jsonb_insert" if matches!(argument_count, 3 | 4) => Some(true),
951 "json_extract_path"
952 | "jsonb_extract_path"
953 | "json_extract_path_text"
954 | "jsonb_extract_path_text"
955 if argument_count >= 2 =>
956 {
957 Some(true)
958 }
959 "json_contains" | "json_contained_by" | "json_delete_path" | "json_has_key"
960 | "json_has_any_key" | "json_has_all_keys" | "jsonb_path_exists" | "jsonpath_exists"
961 | "jsonb_path_match" | "jsonpath_match"
962 if argument_count == 2 =>
963 {
964 Some(true)
965 }
966 "make_timestamp" if matches!(argument_count, 6 | 7) => Some(true),
967 "make_interval" if argument_count <= 7 => Some(true),
968 "width_bucket" if argument_count == 4 => Some(true),
969 "st_dwithin" if matches!(argument_count, 2 | 3) => Some(true),
970 _ => None,
971 }
972}
973
974#[must_use]
976pub fn bound_scalar_function_strictness(
977 name: &str,
978 binding: Option<&FunctionBinding>,
979 argument_count: usize,
980) -> Option<bool> {
981 let Some(binding) = binding else {
982 return builtin_scalar_function_strictness(name, argument_count);
983 };
984 if let Some(dispatch) = binding.dispatch {
985 return match dispatch {
986 FunctionDispatch::ArraySubscripts
987 | FunctionDispatch::Subscript
988 | FunctionDispatch::BetweenSymmetric
989 | FunctionDispatch::ToBinInt4
990 | FunctionDispatch::ToBinInt8
991 | FunctionDispatch::ToHexInt4
992 | FunctionDispatch::ToHexInt8
993 | FunctionDispatch::ToOctInt4
994 | FunctionDispatch::ToOctInt8
995 | FunctionDispatch::RandomInt4Range
996 | FunctionDispatch::RandomInt8Range
997 | FunctionDispatch::RandomNumericRange
998 | FunctionDispatch::ArraySortJson
999 | FunctionDispatch::Range { .. } => Some(true),
1000 FunctionDispatch::ArraySlices
1001 | FunctionDispatch::Slice
1002 | FunctionDispatch::AnyOperator
1003 | FunctionDispatch::AllOperator
1004 | FunctionDispatch::IsDistinct => Some(false),
1005 FunctionDispatch::NamedArgument | FunctionDispatch::VariadicArgument => None,
1006 };
1007 }
1008 binding
1009 .builtin
1010 .then(|| builtin_scalar_function_strictness(&binding.name, argument_count))
1011 .flatten()
1012}
1013
1014pub fn evaluate_call_args(
1017 args: &[Expr],
1018 ctx: &EvalContext<'_>,
1019) -> Result<Vec<(Option<String>, Value)>> {
1020 args.iter()
1021 .map(|arg| match arg {
1022 Expr::Func {
1023 binding,
1024 args: inner,
1025 ..
1026 } if binding_dispatch(binding.as_ref()) == Some(FunctionDispatch::NamedArgument) => {
1027 let Some(Expr::Literal(Value::Str(arg_name))) = inner.first() else {
1028 return Err(SQLError::Internal("named argument without a name".into()));
1029 };
1030 let value_expr = inner
1031 .get(1)
1032 .ok_or_else(|| SQLError::Internal("named argument without a value".into()))?;
1033 Ok((
1034 Some(arg_name.clone()),
1035 evaluate_call_argument_value(value_expr, ctx)?,
1036 ))
1037 }
1038 other => Ok((None, evaluate_call_argument_value(other, ctx)?)),
1039 })
1040 .collect()
1041}
1042
1043fn evaluate_call_argument_value(argument: &Expr, ctx: &EvalContext<'_>) -> Result<Value> {
1044 if let Expr::Func { binding, args, .. } = argument {
1045 if binding_dispatch(binding.as_ref()) == Some(FunctionDispatch::VariadicArgument) {
1046 let [value] = args.as_slice() else {
1047 return Err(SQLError::Internal(
1048 "VARIADIC argument marker must contain one value".into(),
1049 ));
1050 };
1051 return eval(value, ctx);
1052 }
1053 }
1054 eval(argument, ctx)
1055}
1056
1057pub fn eval_function_call(
1065 name: &str,
1066 call_args: Vec<(Option<String>, Value)>,
1067 ctx: &EvalContext<'_>,
1068) -> Result<Value> {
1069 eval_function_call_inner(name, call_args, ctx, true)
1070}
1071
1072pub fn eval_builtin_function_call(
1075 name: &str,
1076 call_args: Vec<(Option<String>, Value)>,
1077 ctx: &EvalContext<'_>,
1078) -> Result<Value> {
1079 eval_function_call_inner(name, call_args, ctx, false)
1080}
1081
1082pub fn eval_bound_builtin_function_call(
1084 binding: &FunctionBinding,
1085 call_args: Vec<(Option<String>, Value)>,
1086 ctx: &EvalContext<'_>,
1087) -> Result<Value> {
1088 let Some(dispatch) = binding.dispatch else {
1089 return eval_builtin_function_call(&binding.name, call_args, ctx);
1090 };
1091 if let Some(result) = random::eval_dispatched_random_function(dispatch, &call_args, ctx) {
1092 return result;
1093 }
1094 if call_args.iter().any(|(name, _)| name.is_some()) {
1095 return Err(SQLError::Internal(format!(
1096 "bound {} expression retained a named argument",
1097 dispatch.label()
1098 )));
1099 }
1100 let evaluated = call_args
1101 .into_iter()
1102 .map(|(_, value)| value)
1103 .collect::<Vec<_>>();
1104 if let Some(result) = scalar_postgres::eval_dispatched_postgres_function(dispatch, &evaluated) {
1105 return result;
1106 }
1107 match dispatch {
1108 FunctionDispatch::ArraySortJson => {
1109 scalar_array::eval_dispatched_json_array_sort(&evaluated)
1110 }
1111 FunctionDispatch::Range {
1112 operation,
1113 subtype,
1114 multirange,
1115 } => {
1116 scalar_range::eval_dispatched_range_function(operation, subtype, multirange, &evaluated)
1117 }
1118 FunctionDispatch::NamedArgument | FunctionDispatch::VariadicArgument => Err(
1119 SQLError::Internal("call-argument syntax marker reached scalar execution".into()),
1120 ),
1121 _ => Err(SQLError::Internal(format!(
1122 "{} has no scalar executor",
1123 dispatch.label()
1124 ))),
1125 }
1126}
1127
1128fn eval_function_call_inner(
1129 name: &str,
1130 call_args: Vec<(Option<String>, Value)>,
1131 ctx: &EvalContext<'_>,
1132 allow_dynamic_dispatch: bool,
1133) -> Result<Value> {
1134 let lower = normalized_function_name(name);
1135 let lower = lower.as_ref();
1136 let evaluated: Vec<Value> = call_args.iter().map(|(_, value)| value.clone()).collect();
1137
1138 if let Some(result) = random::eval_random_function(lower, &call_args, ctx) {
1139 return result;
1140 }
1141 if lower == "random" && !evaluated.is_empty() {
1142 return Err(SQLError::TypeMismatch("random takes no arguments".into()));
1143 }
1144 if lower == "setseed" {
1145 let [value] = evaluated.as_slice() else {
1146 return Err(SQLError::TypeMismatch("setseed takes 1 arg".into()));
1147 };
1148 let seed = to_f64(value)?;
1149 if !seed.is_finite() || !(-1.0..=1.0).contains(&seed) {
1150 return Err(SQLError::Routine {
1151 sqlstate: "22023".into(),
1152 message: format!("setseed parameter {seed} is out of allowed range [-1,1]"),
1153 });
1154 }
1155 let engine = ctx.engine.ok_or_else(|| {
1156 SQLError::Unsupported("setseed requires a logical engine session".into())
1157 })?;
1158 if !engine.set_random_seed(seed).map_err(SQLError::Internal)? {
1159 return Err(SQLError::Unsupported(
1160 "engine hook does not provide a session random stream".into(),
1161 ));
1162 }
1163 return Ok(Value::Str(String::new()));
1164 }
1165
1166 if lower == "current_schema" {
1167 if !evaluated.is_empty() {
1168 return Err(SQLError::TypeMismatch(
1169 "current_schema takes no arguments".into(),
1170 ));
1171 }
1172 let schema = ctx
1173 .engine
1174 .map(|engine| engine.current_schema())
1175 .transpose()
1176 .map_err(SQLError::Internal)?
1177 .flatten()
1178 .unwrap_or_else(|| "public".to_string());
1179 return Ok(Value::Str(schema));
1180 }
1181 if lower == "current_schemas" {
1182 let [Value::Bool(include_implicit)] = evaluated.as_slice() else {
1183 return Err(SQLError::TypeMismatch(
1184 "current_schemas takes one boolean argument".into(),
1185 ));
1186 };
1187 let schemas = ctx
1188 .engine
1189 .map(|engine| engine.current_schemas(*include_implicit))
1190 .transpose()
1191 .map_err(SQLError::Internal)?
1192 .flatten()
1193 .unwrap_or_else(|| {
1194 let mut schemas = Vec::new();
1195 if *include_implicit {
1196 schemas.push("pg_catalog".to_string());
1197 }
1198 schemas.push("public".to_string());
1199 schemas
1200 });
1201 return ArrayValue::try_new(schemas.into_iter().map(Value::Str).collect())
1202 .map(Value::Array)
1203 .ok_or_else(|| SQLError::TypeMismatch("invalid current_schemas result".into()));
1204 }
1205 if matches!(lower, "current_user" | "session_user") {
1206 if !evaluated.is_empty() {
1207 return Err(SQLError::TypeMismatch(format!(
1208 "{lower} takes no arguments"
1209 )));
1210 }
1211 let user = ctx
1212 .engine
1213 .map(|engine| {
1214 if lower == "current_user" {
1215 engine.current_user()
1216 } else {
1217 engine.session_user()
1218 }
1219 })
1220 .transpose()
1221 .map_err(SQLError::Internal)?
1222 .flatten()
1223 .unwrap_or_else(|| "uqa".to_string());
1224 return Ok(Value::Str(user));
1225 }
1226 if lower == "to_regclass" {
1227 let [value] = evaluated.as_slice() else {
1228 return Err(SQLError::BadArity {
1229 name: "to_regclass".into(),
1230 expected: "1".into(),
1231 actual: evaluated.len(),
1232 });
1233 };
1234 let name = match value {
1235 Value::Null => return Ok(Value::Null),
1236 Value::Str(name) | Value::FixedChar(name) => name,
1237 value => {
1238 return Err(SQLError::TypeMismatch(format!(
1239 "to_regclass requires text, got {}",
1240 value_type_name(value)
1241 )));
1242 }
1243 };
1244 let oid = ctx
1245 .engine
1246 .map(|engine| engine.resolve_regclass(name))
1247 .transpose()
1248 .map_err(SQLError::Internal)?
1249 .flatten();
1250 return Ok(oid.map_or(Value::Null, Value::Int));
1251 }
1252
1253 if crate::registry::is_registered(lower) {
1257 if lower == "fts_match" && jsonpath_candidate(&evaluated) {
1258 return jsonpath_match(&evaluated);
1259 }
1260 return Err(SQLError::Unsupported(format!(
1261 "scalar evaluation of `{name}` is not supported (use the function registry)"
1262 )));
1263 }
1264
1265 if call_args.iter().any(|(name, _)| name.is_some()) {
1266 if let Some(positional) = builtin_named_args(lower, &call_args) {
1267 return eval_scalar_function(lower, &positional);
1268 }
1269 if let Some(engine) = ctx.engine.filter(|_| allow_dynamic_dispatch) {
1270 if let Some(result) = engine.call_user_function(lower, &call_args) {
1271 return result;
1272 }
1273 }
1274 return Err(unknown_function_error(lower, &call_args));
1275 }
1276
1277 if matches!(lower, "nextval" | "currval" | "setval") {
1280 return eval_sequence_function(lower, &evaluated, ctx);
1281 }
1282 if let Some(engine) = ctx
1283 .engine
1284 .filter(|engine| allow_dynamic_dispatch && engine.has_scalar_functions())
1285 {
1286 if let Some(result) = engine.call_scalar_function(lower, &evaluated) {
1287 return result;
1288 }
1289 }
1290 match eval_scalar_function(lower, &evaluated) {
1291 Err(SQLError::UnknownFunction(_)) => {
1294 if let Some(engine) = ctx.engine.filter(|_| allow_dynamic_dispatch) {
1295 if let Some(result) = engine.call_user_function(lower, &call_args) {
1296 return result;
1297 }
1298 }
1299 Err(unknown_function_error(lower, &call_args))
1300 }
1301 other => other,
1302 }
1303}
1304
1305fn builtin_named_args(function: &str, call_args: &[(Option<String>, Value)]) -> Option<Vec<Value>> {
1306 if matches!(function, "array_sort" | "array_reverse") {
1307 return array_transform::reorder_named_values(function, call_args);
1308 }
1309 if matches!(function, "json_strip_nulls" | "jsonb_strip_nulls") {
1310 return json_strip::reorder_named_values(function, call_args);
1311 }
1312 let names: &[&str] = match function {
1313 "regexp_count" => match call_args.len() {
1314 2 => &["string", "pattern"],
1315 3 => &["string", "pattern", "start"],
1316 4 => &["string", "pattern", "start", "flags"],
1317 _ => return None,
1318 },
1319 "regexp_like" => match call_args.len() {
1320 2 => &["string", "pattern"],
1321 3 => &["string", "pattern", "flags"],
1322 _ => return None,
1323 },
1324 "regexp_substr" => match call_args.len() {
1325 2 => &["string", "pattern"],
1326 3 => &["string", "pattern", "start"],
1327 4 => &["string", "pattern", "start", "N"],
1328 5 => &["string", "pattern", "start", "N", "flags"],
1329 6 => &["string", "pattern", "start", "N", "flags", "subexpr"],
1330 _ => return None,
1331 },
1332 "regexp_instr" => match call_args.len() {
1333 2 => &["string", "pattern"],
1334 3 => &["string", "pattern", "start"],
1335 4 => &["string", "pattern", "start", "N"],
1336 5 => &["string", "pattern", "start", "N", "endoption"],
1337 6 => &["string", "pattern", "start", "N", "endoption", "flags"],
1338 7 => &[
1339 "string",
1340 "pattern",
1341 "start",
1342 "N",
1343 "endoption",
1344 "flags",
1345 "subexpr",
1346 ],
1347 _ => return None,
1348 },
1349 "regexp_replace" => match call_args.len() {
1350 3 => &["string", "pattern", "replacement"],
1351 4 if call_args
1352 .iter()
1353 .any(|(name, _)| name.as_deref() == Some("flags")) =>
1354 {
1355 &["string", "pattern", "replacement", "flags"]
1356 }
1357 4 => &["string", "pattern", "replacement", "start"],
1358 5 => &["string", "pattern", "replacement", "start", "N"],
1359 6 => &["string", "pattern", "replacement", "start", "N", "flags"],
1360 _ => return None,
1361 },
1362 "make_interval" => return make_interval_named_args(call_args),
1363 _ => return None,
1364 };
1365 reorder_named_args(call_args, names)
1366}
1367
1368fn reorder_named_args(
1369 call_args: &[(Option<String>, Value)],
1370 parameter_names: &[&str],
1371) -> Option<Vec<Value>> {
1372 if call_args.len() != parameter_names.len() {
1373 return None;
1374 }
1375 let mut slots = vec![None; parameter_names.len()];
1376 let mut positional_index = 0;
1377 let mut saw_named = false;
1378 for (name, value) in call_args {
1379 let slot = if let Some(name) = name {
1380 saw_named = true;
1381 parameter_names
1382 .iter()
1383 .position(|candidate| candidate == name)?
1384 } else {
1385 if saw_named {
1386 return None;
1387 }
1388 let slot = positional_index;
1389 positional_index += 1;
1390 slot
1391 };
1392 if slots.get(slot)?.is_some() {
1393 return None;
1394 }
1395 slots[slot] = Some(value.clone());
1396 }
1397 slots.into_iter().collect()
1398}
1399
1400fn make_interval_named_args(call_args: &[(Option<String>, Value)]) -> Option<Vec<Value>> {
1404 const NAMES: [&str; 7] = ["years", "months", "weeks", "days", "hours", "mins", "secs"];
1405 let mut positional = vec![Value::Int(0); NAMES.len()];
1406 let mut positional_index = 0;
1407 let mut saw_named = false;
1408 let mut assigned = [false; NAMES.len()];
1409 for (name, value) in call_args {
1410 let slot = if let Some(name) = name {
1411 saw_named = true;
1412 NAMES.iter().position(|candidate| candidate == name)?
1413 } else {
1414 if saw_named {
1415 return None;
1416 }
1417 let slot = positional_index;
1418 positional_index += 1;
1419 slot
1420 };
1421 if slot >= NAMES.len() || assigned[slot] {
1422 return None;
1423 }
1424 assigned[slot] = true;
1425 positional[slot] = value.clone();
1426 }
1427 Some(positional)
1428}
1429
1430pub fn value_type_name(v: &Value) -> &'static str {
1432 match v {
1433 Value::Null => "unknown",
1434 Value::Bool(_) => "boolean",
1435 Value::Int(_) => "integer",
1436 Value::Float(_) => "double precision",
1437 Value::Str(_) => "text",
1438 Value::FixedChar(_) => "character",
1439 Value::Bytes(_) => "bytea",
1440 Value::Temporal(TemporalValue::Interval { .. }) => "interval",
1441 Value::Temporal(_) => "timestamp",
1442 Value::Decimal(_) => "numeric",
1443 Value::Json(_) => "json",
1444 Value::JsonB(_) => "jsonb",
1445 Value::Array(_) => "anyarray",
1446 Value::List(_) => "anyarray",
1447 Value::Row(_) | Value::Record(_) => "record",
1448 Value::Map(_) => "jsonb",
1449 }
1450}
1451
1452pub fn unknown_function_error(name: &str, args: &[(Option<String>, Value)]) -> SQLError {
1455 let types = args
1456 .iter()
1457 .map(|(arg_name, value)| match arg_name {
1458 Some(arg_name) => format!("{arg_name} => {}", value_type_name(value)),
1459 None => value_type_name(value).to_string(),
1460 })
1461 .collect::<Vec<_>>()
1462 .join(", ");
1463 SQLError::Routine {
1464 sqlstate: "42883".into(),
1465 message: format!("function {name}({types}) does not exist"),
1466 }
1467}
1468
1469#[cfg(test)]
1470mod tests;