Skip to main content

uqa_sql/routines/
resolution.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Static routine signature matching, invocation binding, and declared return-type resolution.
8
9mod combined_overloads;
10
11use super::{
12    declaration::RoutineTypeCatalog, routine_signature_types, SQLUserFunction, StaticFunctionMatch,
13};
14use crate::type_resolution::{
15    canonical_routine_type_name, match_routine_candidate, rank_function_matches,
16    BuiltinFunctionOverload, FunctionTypeResolver, MatchedRoutineSignature,
17    ResolvedFunctionOverload, RoutineCallDescriptor, RoutineParameterDescriptor,
18    RoutineSignatureMatchError,
19};
20use crate::{
21    ast::{
22        ColumnType, CreateFunction, FunctionBinding, FunctionParamMode, FunctionReturns,
23        RoutineInvocationBinding, RoutineVariadicMode,
24    },
25    catalog::domain::StoredDomain,
26    SQLError,
27};
28use std::{collections::BTreeMap, sync::Arc};
29use uqa_core::RelationIdentity;
30
31/// The original immutable domain allocation captured for one signature-matching pass.
32pub type RoutineTypeSnapshot = Arc<BTreeMap<String, StoredDomain>>;
33pub trait RoutineOverloadCatalog: RoutineTypeCatalog + Send + Sync {
34    fn routine_type_snapshot(&self) -> RoutineTypeSnapshot;
35    fn routine_search_path(&self) -> Vec<String>;
36    fn has_registered_scalar_function(&self, name: &str) -> bool;
37    fn lookup_sql_routine_candidates(
38        &self,
39        name: &str,
40    ) -> Result<Option<Vec<Arc<SQLUserFunction>>>, SQLError>;
41    fn lookup_bound_sql_routine_candidates_by_binding(
42        &self,
43        binding: &FunctionBinding,
44    ) -> Option<Vec<Arc<SQLUserFunction>>>;
45    fn lookup_bound_sql_functions_by_binding(
46        &self,
47        binding: &FunctionBinding,
48    ) -> Option<Vec<Arc<SQLUserFunction>>>;
49}
50pub struct RoutineOverloadContext<'a> {
51    pub catalog: &'a dyn RoutineOverloadCatalog,
52}
53
54impl FunctionTypeResolver for RoutineOverloadContext<'_> {
55    fn format_type_name(&self, ty: &ColumnType) -> Result<String, SQLError> {
56        self.catalog.format_type(ty)
57    }
58
59    fn composite_types(&self) -> Option<&dyn crate::expr::composites::CompositeTypeCatalog> {
60        self.catalog.composite_types()
61    }
62
63    fn has_untyped_function(&self, name: &str) -> bool {
64        self.catalog.has_registered_scalar_function(name)
65    }
66
67    fn resolve_type_name(&self, name: &str) -> Result<Option<ColumnType>, SQLError> {
68        self.catalog
69            .resolve_catalog_column_type_name(name)
70            .map(Some)
71    }
72
73    fn require_type_usage(&self, ty: &ColumnType) -> Result<(), SQLError> {
74        self.catalog.require_type_usage(ty)
75    }
76
77    fn resolve_function_type(
78        &self,
79        name: &str,
80        binding: Option<&FunctionBinding>,
81        argument_names: &[Option<String>],
82        argument_types: &[Option<ColumnType>],
83        explicit_variadic: bool,
84    ) -> Result<Option<ColumnType>, SQLError> {
85        self.resolve_function_overload(
86            name,
87            binding,
88            argument_names,
89            argument_types,
90            explicit_variadic,
91        )
92        .map(|resolved| resolved.map(|resolved| resolved.return_type))
93    }
94
95    fn resolve_function_overload(
96        &self,
97        name: &str,
98        binding: Option<&FunctionBinding>,
99        argument_names: &[Option<String>],
100        argument_types: &[Option<ColumnType>],
101        explicit_variadic: bool,
102    ) -> Result<Option<ResolvedFunctionOverload>, SQLError> {
103        let Some(matched) = self.resolve_static_sql_routine_match(
104            name,
105            binding,
106            argument_names,
107            argument_types,
108            explicit_variadic,
109            RoutineCallKind::Function,
110        )?
111        else {
112            return Ok(None);
113        };
114        let function = &matched.function;
115        Ok(Some(ResolvedFunctionOverload {
116            binding: matched.binding(),
117            return_type: static_function_return_type(
118                self,
119                name,
120                &function.def,
121                Some(&matched.invocation),
122            )?,
123            exact_matches: matched.exact_matches,
124            known_arguments: argument_types.iter().flatten().count(),
125            preferred_matches: matched.preferred_matches,
126            precedes_pg_catalog: self.user_function_precedes_pg_catalog(&function.def.name),
127        }))
128    }
129
130    fn is_scalar_function_binding(&self, binding: &FunctionBinding) -> Result<bool, SQLError> {
131        if binding.builtin {
132            return Ok(false);
133        }
134        let function = self
135            .catalog
136            .lookup_bound_sql_functions_by_binding(binding)
137            .and_then(|overloads| {
138                overloads.into_iter().find(|function| {
139                    !function.def.is_procedure
140                        && routine_signature_types(&function.def) == binding.argument_types
141                })
142            });
143        Ok(function.is_some_and(|function| !function.def.returns_set()))
144    }
145
146    fn resolve_function_overload_with_builtins(
147        &self,
148        name: &str,
149        binding: Option<&FunctionBinding>,
150        argument_names: &[Option<String>],
151        argument_types: &[Option<ColumnType>],
152        explicit_variadic: bool,
153        builtins: &[BuiltinFunctionOverload],
154    ) -> Result<Option<ResolvedFunctionOverload>, SQLError> {
155        combined_overloads::resolve(
156            self,
157            name,
158            binding,
159            argument_names,
160            argument_types,
161            explicit_variadic,
162            builtins,
163        )
164        .map(Some)
165    }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub enum RoutineCallKind {
170    Function,
171    Procedure,
172}
173
174impl RoutineCallKind {
175    fn is_procedure(self) -> bool {
176        self == Self::Procedure
177    }
178
179    fn name(self) -> &'static str {
180        match self {
181            Self::Function => "function",
182            Self::Procedure => "procedure",
183        }
184    }
185}
186
187impl RoutineOverloadContext<'_> {
188    pub(super) fn user_function_precedes_pg_catalog(&self, name: &str) -> bool {
189        let Ok((Some(schema), _)) = RelationIdentity::parse_reference(name) else {
190            return false;
191        };
192        let search_path = self.catalog.routine_search_path();
193        let Some(user_position) = search_path.iter().position(|entry| entry == &schema) else {
194            return false;
195        };
196        search_path
197            .iter()
198            .position(|entry| entry == "pg_catalog")
199            .is_some_and(|catalog_position| user_position < catalog_position)
200    }
201
202    pub fn resolve_static_sql_function(
203        &self,
204        name: &str,
205        binding: Option<&FunctionBinding>,
206        argument_names: &[Option<String>],
207        argument_types: &[Option<ColumnType>],
208        explicit_variadic: bool,
209    ) -> Result<Option<Arc<SQLUserFunction>>, SQLError> {
210        self.resolve_static_sql_function_match(
211            name,
212            binding,
213            argument_names,
214            argument_types,
215            explicit_variadic,
216        )
217        .map(|matched| matched.map(|matched| matched.function))
218    }
219
220    pub fn resolve_static_sql_function_match(
221        &self,
222        name: &str,
223        binding: Option<&FunctionBinding>,
224        argument_names: &[Option<String>],
225        argument_types: &[Option<ColumnType>],
226        explicit_variadic: bool,
227    ) -> Result<Option<StaticFunctionMatch>, SQLError> {
228        self.resolve_static_sql_routine_match(
229            name,
230            binding,
231            argument_names,
232            argument_types,
233            explicit_variadic,
234            RoutineCallKind::Function,
235        )
236    }
237
238    pub fn resolve_table_function_overload_with_builtins(
239        &self,
240        name: &str,
241        binding: Option<&FunctionBinding>,
242        argument_names: &[Option<String>],
243        argument_types: &[Option<ColumnType>],
244        explicit_variadic: bool,
245        builtins: &[BuiltinFunctionOverload],
246    ) -> Result<Option<ResolvedFunctionOverload>, SQLError> {
247        combined_overloads::resolve_table(
248            self,
249            name,
250            binding,
251            argument_names,
252            argument_types,
253            explicit_variadic,
254            builtins,
255        )
256        .map(Some)
257    }
258
259    pub fn resolve_static_sql_routine_match(
260        &self,
261        name: &str,
262        binding: Option<&FunctionBinding>,
263        argument_names: &[Option<String>],
264        argument_types: &[Option<ColumnType>],
265        explicit_variadic: bool,
266        kind: RoutineCallKind,
267    ) -> Result<Option<StaticFunctionMatch>, SQLError> {
268        if let Some(binding) = binding {
269            if binding.builtin {
270                return Ok(None);
271            }
272            let function = self
273                .catalog
274                .lookup_bound_sql_routine_candidates_by_binding(binding)
275                .and_then(|overloads| {
276                    overloads.into_iter().find(|function| {
277                        function.def.is_procedure == kind.is_procedure()
278                            && routine_signature_types(&function.def) == binding.argument_types
279                    })
280                })
281                .ok_or_else(|| static_bound_routine_error(kind, binding))?;
282            let matched = if let Some(invocation) = &binding.invocation {
283                let invocation_is_explicit = matches!(
284                    invocation.variadic_mode,
285                    RoutineVariadicMode::Explicit { .. }
286                );
287                if invocation.argument_positions.len() != argument_types.len()
288                    || invocation_is_explicit != explicit_variadic
289                {
290                    return Err(static_bound_routine_error(kind, binding));
291                }
292                StaticFunctionMatch {
293                    function,
294                    default_error: None,
295                    invocation: invocation.clone(),
296                    argument_types: invocation.argument_targets.clone(),
297                    raw_exact_matches: 0,
298                    exact_matches: 0,
299                    preferred_matches: 0,
300                    variadic_expansion: matches!(
301                        invocation.variadic_mode,
302                        RoutineVariadicMode::Expanded { .. }
303                    ),
304                }
305            } else {
306                static_routine_match(
307                    &self.catalog.routine_type_snapshot(),
308                    function,
309                    argument_names,
310                    argument_types,
311                    explicit_variadic,
312                    kind,
313                )
314                .map_err(|error| static_signature_error(kind, name, error))?
315                .ok_or_else(|| static_bound_routine_error(kind, binding))?
316            };
317            ensure_routine_kind(
318                name,
319                argument_names,
320                argument_types,
321                kind,
322                &matched.function.def,
323            )?;
324            check_selected_default_types(self.catalog, kind, name, &matched)?;
325            return Ok(Some(matched));
326        }
327        let Some(overloads) = self.catalog.lookup_sql_routine_candidates(name)? else {
328            return Ok(None);
329        };
330        resolve_static_routine_overload(
331            self.catalog,
332            name,
333            overloads,
334            argument_names,
335            argument_types,
336            explicit_variadic,
337            kind,
338        )
339        .map(Some)
340    }
341}
342
343fn resolve_static_routine_overload(
344    catalog: &dyn RoutineOverloadCatalog,
345    name: &str,
346    overloads: Vec<Arc<SQLUserFunction>>,
347    argument_names: &[Option<String>],
348    argument_types: &[Option<ColumnType>],
349    explicit_variadic: bool,
350    kind: RoutineCallKind,
351) -> Result<StaticFunctionMatch, SQLError> {
352    let snapshot = catalog.routine_type_snapshot();
353    let mut candidates = Vec::new();
354    let mut match_error = None;
355    for function in overloads {
356        match static_routine_match(
357            &snapshot,
358            function,
359            argument_names,
360            argument_types,
361            explicit_variadic,
362            kind,
363        ) {
364            Ok(Some(candidate)) => candidates.push(candidate),
365            Ok(None) => {}
366            Err(error) => {
367                match_error.get_or_insert(error);
368            }
369        }
370    }
371    retain_earliest_effective_signatures(&mut candidates);
372    if candidates.is_empty() {
373        if let Some(error) = match_error {
374            return Err(static_signature_error(kind, name, error));
375        }
376        return Err(static_routine_resolution_error(
377            kind,
378            "42883",
379            name,
380            argument_types,
381            "does not exist",
382        ));
383    }
384
385    if !rank_function_matches(&mut candidates, argument_types) || candidates.len() != 1 {
386        return Err(static_routine_resolution_error(
387            kind,
388            "42725",
389            name,
390            argument_types,
391            "is not unique",
392        ));
393    }
394    let matched = candidates
395        .pop()
396        .ok_or_else(|| SQLError::Internal("resolved routine candidate disappeared".into()))?;
397    ensure_routine_kind(
398        name,
399        argument_names,
400        argument_types,
401        kind,
402        &matched.function.def,
403    )?;
404    check_selected_default_types(catalog, kind, name, &matched)?;
405    Ok(matched)
406}
407
408pub(super) fn retain_earliest_effective_signatures(candidates: &mut Vec<StaticFunctionMatch>) {
409    let mut visible = Vec::<(Vec<String>, String)>::new();
410    candidates.retain(|candidate| {
411        let schema = RelationIdentity::parse_reference(&candidate.function.def.name)
412            .ok()
413            .and_then(|(schema, _)| schema)
414            .unwrap_or_default();
415        if let Some((_, first_schema)) = visible
416            .iter()
417            .find(|(signature, _)| signature == &candidate.argument_types)
418        {
419            return first_schema == &schema;
420        }
421        visible.push((candidate.argument_types.clone(), schema));
422        true
423    });
424}
425
426fn static_routine_match(
427    catalog: &RoutineTypeSnapshot,
428    function: Arc<SQLUserFunction>,
429    argument_names: &[Option<String>],
430    argument_types: &[Option<ColumnType>],
431    explicit_variadic: bool,
432    kind: RoutineCallKind,
433) -> Result<Option<StaticFunctionMatch>, RoutineSignatureMatchError> {
434    let parameter_indices = routine_call_parameter_indices(&function.def, kind);
435    let parameters = parameter_indices
436        .iter()
437        .map(|index| &function.def.params[*index])
438        .collect::<Vec<_>>();
439    let Some(matched) = match_static_function_signature(
440        catalog,
441        &parameters,
442        argument_names,
443        argument_types,
444        explicit_variadic,
445    )?
446    else {
447        return Ok(None);
448    };
449    let invocation = routine_invocation_binding(&function.def, &parameter_indices, &matched);
450    Ok(Some(StaticFunctionMatch {
451        function,
452        argument_types: matched.argument_signature,
453        default_error: matched.default_error,
454        raw_exact_matches: matched.raw_exact_matches,
455        exact_matches: matched.exact_matches,
456        preferred_matches: matched.preferred_matches,
457        variadic_expansion: matches!(
458            matched.variadic_mode,
459            crate::type_resolution::RoutineVariadicMode::Pack
460        ),
461        invocation: Box::new(invocation),
462    }))
463}
464
465pub(super) fn static_function_match(
466    catalog: &RoutineTypeSnapshot,
467    function: Arc<SQLUserFunction>,
468    argument_names: &[Option<String>],
469    argument_types: &[Option<ColumnType>],
470    explicit_variadic: bool,
471) -> Result<Option<StaticFunctionMatch>, RoutineSignatureMatchError> {
472    static_routine_match(
473        catalog,
474        function,
475        argument_names,
476        argument_types,
477        explicit_variadic,
478        RoutineCallKind::Function,
479    )
480}
481
482fn match_static_function_signature(
483    catalog: &RoutineTypeSnapshot,
484    signature: &[&crate::ast::FunctionParam],
485    argument_names: &[Option<String>],
486    argument_types: &[Option<ColumnType>],
487    explicit_variadic: bool,
488) -> Result<Option<MatchedRoutineSignature>, RoutineSignatureMatchError> {
489    let parameters = signature
490        .iter()
491        .map(|parameter| RoutineParameterDescriptor {
492            name: Some(parameter.name.clone()),
493            type_name: canonical_routine_type_name(&parameter.type_name),
494            column_type: declared_parameter_type(catalog, &parameter.type_name),
495            has_default: parameter.default.is_some(),
496            default_type: parameter.default_type.clone(),
497            variadic: parameter.mode == FunctionParamMode::Variadic,
498        })
499        .collect::<Vec<_>>();
500    match_routine_candidate(
501        &parameters,
502        RoutineCallDescriptor {
503            argument_names,
504            argument_types,
505            explicit_variadic,
506        },
507    )
508}
509
510fn declared_parameter_type(catalog: &RoutineTypeSnapshot, type_name: &str) -> Option<ColumnType> {
511    if let Some(element) = type_name.strip_suffix("[]") {
512        return declared_parameter_type(catalog, element).map(|ty| ColumnType::Array(Box::new(ty)));
513    }
514    // Enum parameters match by identity alone; a domain parameter also needs its base type.
515    if let Some(identity) = crate::ast::UserTypeIdentity::parse(type_name) {
516        return (identity.kind == crate::ast::UserTypeKind::Domain)
517            .then(|| {
518                catalog
519                    .values()
520                    .find(|domain| domain.oid == identity.oid)
521                    .map(StoredDomain::column_type)
522                    .or_else(|| crate::catalog::system_catalog_domain(identity.oid))
523            })
524            .flatten();
525    }
526    ColumnType::from_sql_name(type_name).ok().or_else(|| {
527        catalog.values().map(StoredDomain::column_type).find(|ty| {
528            canonical_routine_type_name(&ty.sql_name()) == canonical_routine_type_name(type_name)
529        })
530    })
531}
532
533fn routine_call_parameter_indices(def: &CreateFunction, kind: RoutineCallKind) -> Vec<usize> {
534    def.params
535        .iter()
536        .enumerate()
537        .filter_map(|(index, parameter)| {
538            let participates = if kind == RoutineCallKind::Procedure && !def.is_procedure {
539                true
540            } else {
541                match parameter.mode {
542                    FunctionParamMode::In
543                    | FunctionParamMode::InOut
544                    | FunctionParamMode::Variadic => true,
545                    FunctionParamMode::Out => def.is_procedure,
546                    FunctionParamMode::Table => false,
547                }
548            };
549            participates.then_some(index)
550        })
551        .collect()
552}
553
554fn routine_invocation_binding(
555    def: &CreateFunction,
556    parameter_indices: &[usize],
557    matched: &MatchedRoutineSignature,
558) -> RoutineInvocationBinding {
559    // Matching keys identify an overload; execution receives the selected catalog types instead of resolving those keys through the caller's search_path.
560    let concrete = matched.invocation_binding(None);
561    let output_type = |type_name: &str| {
562        if crate::type_resolution::routine_polymorphic_type(type_name).is_some() {
563            matched
564                .substitute_type(type_name)
565                .map(|ty| ty.catalog_name())
566        } else {
567            Some(type_name.to_owned())
568        }
569    };
570    let parameter_types = def
571        .params
572        .iter()
573        .enumerate()
574        .map(|(definition_index, parameter)| {
575            parameter_indices
576                .iter()
577                .position(|index| *index == definition_index)
578                .and_then(|call_index| concrete.parameter_types.get(call_index).cloned())
579                .or_else(|| output_type(&parameter.type_name))
580                .unwrap_or_else(|| parameter.type_name.clone())
581        })
582        .collect::<Vec<_>>();
583    let argument_positions = matched
584        .argument_positions
585        .iter()
586        .map(|call_index| parameter_indices[*call_index])
587        .collect();
588    let variadic_mode = match &matched.variadic_plan {
589        crate::type_resolution::RoutineVariadicPlan::Pack {
590            parameter_index, ..
591        } => RoutineVariadicMode::Expanded {
592            parameter_index: parameter_indices[*parameter_index],
593        },
594        crate::type_resolution::RoutineVariadicPlan::PassThrough {
595            parameter_index, ..
596        } => RoutineVariadicMode::Explicit {
597            parameter_index: parameter_indices[*parameter_index],
598        },
599        crate::type_resolution::RoutineVariadicPlan::None
600        | crate::type_resolution::RoutineVariadicPlan::Default { .. } => RoutineVariadicMode::None,
601    };
602    let output_indices = def
603        .params
604        .iter()
605        .enumerate()
606        .filter_map(|(index, parameter)| {
607            matches!(
608                parameter.mode,
609                FunctionParamMode::Out | FunctionParamMode::InOut | FunctionParamMode::Table
610            )
611            .then_some(index)
612        })
613        .collect::<Vec<_>>();
614    let return_type = match &def.returns {
615        FunctionReturns::Scalar { type_name } | FunctionReturns::SetOf { type_name } => {
616            output_type(type_name)
617        }
618        FunctionReturns::Table => Some(ColumnType::Record.catalog_name()),
619        FunctionReturns::None => match output_indices.as_slice() {
620            [] => None,
621            [index] => parameter_types.get(*index).cloned(),
622            _ => Some(ColumnType::Record.catalog_name()),
623        },
624    };
625    RoutineInvocationBinding {
626        argument_positions,
627        argument_targets: concrete.argument_targets,
628        argument_sources: concrete.argument_sources,
629        parameter_types,
630        return_type,
631        variadic_mode,
632    }
633}
634
635pub(crate) fn static_signature_error(
636    _kind: RoutineCallKind,
637    _name: &str,
638    error: RoutineSignatureMatchError,
639) -> SQLError {
640    let sqlstate = error.sqlstate().to_string();
641    let message = match error {
642        RoutineSignatureMatchError::InvalidVariadicSignature { reason } => reason,
643        RoutineSignatureMatchError::IndeterminatePolymorphicType { .. } => {
644            "could not determine polymorphic type because input has type unknown".into()
645        }
646        _ => unreachable!("default consistency is checked after candidate selection"),
647    };
648    SQLError::Routine { sqlstate, message }
649}
650
651pub(super) fn static_function_return_type(
652    resolver: &RoutineOverloadContext<'_>,
653    name: &str,
654    def: &CreateFunction,
655    invocation: Option<&RoutineInvocationBinding>,
656) -> Result<ColumnType, SQLError> {
657    let invocation_return = invocation.and_then(|invocation| invocation.return_type.as_deref());
658    let declared_return = match &def.returns {
659        FunctionReturns::Scalar { type_name } | FunctionReturns::SetOf { type_name } => {
660            Some(type_name.as_str())
661        }
662        FunctionReturns::Table | FunctionReturns::None => None,
663    };
664    if invocation_return
665        .or(declared_return)
666        .is_some_and(|type_name| canonical_routine_type_name(type_name) == "trigger")
667    {
668        return Err(SQLError::Routine {
669            sqlstate: "0A000".into(),
670            message: "trigger functions can only be called as triggers".into(),
671        });
672    }
673    if matches!(def.returns, FunctionReturns::Table) || def.output_params().len() > 1 {
674        return Ok(ColumnType::Record);
675    }
676    if let Some(type_name) = invocation_return {
677        return resolver
678            .catalog
679            .resolve_catalog_column_type(type_name)
680            .or_else(|| ColumnType::from_sql_name(type_name).ok())
681            .ok_or_else(|| {
682                SQLError::TypeMismatch(format!(
683                    "function `{name}` has unresolved return type `{type_name}`"
684                ))
685            });
686    }
687    let type_name = match &def.returns {
688        FunctionReturns::Scalar { type_name } | FunctionReturns::SetOf { type_name } => type_name,
689        FunctionReturns::None => {
690            let outputs = def.output_params();
691            if outputs.len() > 1 {
692                return Ok(ColumnType::Record);
693            }
694            &outputs
695                .first()
696                .ok_or_else(|| {
697                    SQLError::TypeMismatch(format!("function `{name}` does not return a value"))
698                })?
699                .type_name
700        }
701        FunctionReturns::Table => unreachable!("table result handled above"),
702    };
703    resolver
704        .catalog
705        .resolve_catalog_column_type(type_name)
706        .or_else(|| ColumnType::from_sql_name(type_name).ok())
707        .ok_or_else(|| SQLError::TypeMismatch(format!("unknown type `{type_name}`")))
708}
709
710fn ensure_routine_kind(
711    name: &str,
712    argument_names: &[Option<String>],
713    argument_types: &[Option<ColumnType>],
714    expected: RoutineCallKind,
715    definition: &CreateFunction,
716) -> Result<(), SQLError> {
717    if definition.is_procedure == expected.is_procedure() {
718        return Ok(());
719    }
720    let arguments = static_routine_argument_types(argument_names, argument_types);
721    let suffix = if definition.is_procedure {
722        "is a procedure"
723    } else {
724        "is not a procedure"
725    };
726    Err(SQLError::Diagnostic {
727        sqlstate: "42809".into(),
728        message: format!("{name}({arguments}) {suffix}"),
729        detail: None,
730        hint: Some(if definition.is_procedure {
731            "To call a procedure, use CALL.".into()
732        } else {
733            "To call a function, use SELECT.".into()
734        }),
735    })
736}
737
738fn static_bound_routine_error(kind: RoutineCallKind, binding: &FunctionBinding) -> SQLError {
739    SQLError::Routine {
740        sqlstate: "42883".into(),
741        message: format!(
742            "bound {} {}({}) does not exist",
743            kind.name(),
744            binding.name,
745            binding.argument_types.join(", ")
746        ),
747    }
748}
749
750fn static_routine_resolution_error(
751    kind: RoutineCallKind,
752    sqlstate: &str,
753    name: &str,
754    argument_types: &[Option<ColumnType>],
755    suffix: &str,
756) -> SQLError {
757    let arguments = static_routine_argument_types(&[], argument_types);
758    let hint = if sqlstate == "42725" {
759        format!(
760            "Could not choose a best candidate {}. You might need to add explicit type casts.",
761            kind.name()
762        )
763    } else {
764        format!(
765            "No {} matches the given name and argument types. You might need to add explicit type casts.",
766            kind.name()
767        )
768    };
769    SQLError::Diagnostic {
770        sqlstate: sqlstate.into(),
771        message: format!("{} {name}({arguments}) {suffix}", kind.name()),
772        detail: None,
773        hint: Some(hint),
774    }
775}
776
777fn static_routine_argument_types(
778    argument_names: &[Option<String>],
779    argument_types: &[Option<ColumnType>],
780) -> String {
781    argument_types
782        .iter()
783        .enumerate()
784        .map(|(index, ty)| {
785            let ty = ty
786                .as_ref()
787                .map_or_else(|| "unknown".into(), ColumnType::regtype_name);
788            argument_names
789                .get(index)
790                .and_then(Option::as_ref)
791                .map_or_else(|| ty.clone(), |name| format!("{name} => {ty}"))
792        })
793        .collect::<Vec<_>>()
794        .join(", ")
795}
796
797/// Defaults do not exclude or rank an overload. Their polymorphic consistency is
798/// checked only after the ordinary supplied-argument rules choose one candidate.
799pub(super) fn check_selected_default_types(
800    catalog: &dyn RoutineTypeCatalog,
801    _kind: RoutineCallKind,
802    _name: &str,
803    matched: &StaticFunctionMatch,
804) -> Result<(), SQLError> {
805    if let Some(error) = &matched.default_error {
806        return Err(default_type_error(catalog, error)?);
807    }
808    // An actual anyarray supplies no element identity. Its sole-input exception
809    // permits an anyarray result, but cannot specialize an anyelement result.
810    if matched
811        .invocation
812        .parameter_types
813        .iter()
814        .any(|ty| ty == "anyarray")
815        && matched.invocation.return_type.as_deref().is_some_and(|ty| {
816            matches!(
817                canonical_routine_type_name(ty).as_str(),
818                "anyelement" | "anynonarray" | "anyenum"
819            )
820        })
821    {
822        return Err(default_type_error(
823            catalog,
824            &RoutineSignatureMatchError::IndeterminateArrayElement,
825        )?);
826    }
827    Ok(())
828}
829
830fn default_type_error(
831    catalog: &dyn RoutineTypeCatalog,
832    error: &RoutineSignatureMatchError,
833) -> Result<SQLError, SQLError> {
834    use RoutineSignatureMatchError as E;
835    let format = |actual: &crate::ast::RoutineDefaultType| match actual {
836        crate::ast::RoutineDefaultType::Concrete(ty) => catalog.format_type(ty),
837        crate::ast::RoutineDefaultType::Polymorphic(name) => Ok(name.clone()),
838    };
839    let mut detail = None;
840    let message = match error {
841        E::InconsistentDefault {
842            declared,
843            first,
844            second,
845        } => {
846            detail = Some(format!("{} versus {}", format(first)?, format(second)?));
847            format!("arguments declared \"{declared}\" are not all alike")
848        }
849        E::InconsistentPolymorphicTypes {
850            first_declared,
851            second_declared,
852            first,
853            second,
854        } => {
855            detail = Some(format!("{} versus {}", format(first)?, format(second)?));
856            format!("argument declared {first_declared} is not consistent with argument declared {second_declared}")
857        }
858        E::InvalidPolymorphicActual { declared, actual } => {
859            let kind = if declared.ends_with("multirange") {
860                "a multirange type"
861            } else if declared.ends_with("range") {
862                "a range type"
863            } else {
864                "an array"
865            };
866            format!(
867                "argument declared {declared} is not {kind} but type {}",
868                format(actual)?
869            )
870        }
871        E::IncompatibleDefaultTypes { first, second } => format!(
872            "argument types {} and {} cannot be matched",
873            catalog.format_type(first)?,
874            catalog.format_type(second)?
875        ),
876        E::IncompatibleRangeSubtype {
877            declared,
878            range,
879            common,
880        } => format!(
881            "{declared} type {} does not match anycompatible type {}",
882            catalog.format_type(range)?,
883            catalog.format_type(common)?
884        ),
885        E::InvalidPolymorphicElement { declared, actual } => {
886            let shape = if declared == "anyenum" {
887                "not an enum type"
888            } else {
889                "an array type"
890            };
891            format!(
892                "type matched to {declared} is {shape}: {}",
893                catalog.format_type(actual)?
894            )
895        }
896        E::IndeterminatePolymorphicArgument { declared } => format!(
897            "could not determine polymorphic type {declared} because input has type unknown"
898        ),
899        E::IndeterminateArrayElement => {
900            "cannot determine element type of \"anyarray\" argument".into()
901        }
902        E::IndeterminatePolymorphicType { .. } => {
903            "could not determine polymorphic type because input has type unknown".into()
904        }
905        E::InvalidVariadicSignature { reason } => reason.clone(),
906    };
907    Ok(SQLError::Diagnostic {
908        sqlstate: error.sqlstate().into(),
909        message,
910        detail,
911        hint: None,
912    })
913}