Skip to main content

uqa_sql/catalog/node_tree/expressions/
routines.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Bind catalog declarations through the shared routine matcher and overload ranking.
8
9use super::{ColumnType, RoutineIdentity, SQLError};
10use crate::ast::RoutineVariadicMode;
11use crate::type_resolution::{
12    function_resolution_error, match_routine_signature, rank_function_matches,
13    MatchedRoutineSignature, RankedFunctionMatch, RoutineCallDescriptor,
14    RoutineParameterDescriptor,
15};
16
17pub struct RoutineDeclaration {
18    pub oid: i64,
19    pub parameters: Vec<RoutineParameterDescriptor>,
20    pub return_type: String,
21}
22
23struct Candidate<'a> {
24    declaration: &'a RoutineDeclaration,
25    matched: MatchedRoutineSignature,
26    wildcard: Option<usize>,
27}
28
29impl RankedFunctionMatch for Candidate<'_> {
30    fn argument_types(&self) -> &[String] {
31        self.matched.argument_types()
32    }
33    fn raw_exact_matches(&self) -> usize {
34        self.matched.raw_exact_matches()
35    }
36    fn exact_matches(&self) -> usize {
37        self.matched.exact_matches()
38    }
39    fn preferred_matches(&self) -> usize {
40        self.matched.preferred_matches()
41    }
42    fn is_variadic_expansion(&self) -> bool {
43        self.matched.is_variadic_expansion()
44    }
45}
46
47/// Select a declared catalog signature while retaining supplied positions and concrete polymorphic targets.
48pub fn select_routine(
49    name: &str,
50    declarations: &[RoutineDeclaration],
51    call: RoutineCallDescriptor<'_>,
52) -> Result<RoutineIdentity, SQLError> {
53    let mut candidates = Vec::new();
54    let mut failure = None;
55    for declaration in declarations {
56        match candidate(name, declaration, call) {
57            Ok(Some(candidate)) => candidates.push(candidate),
58            Ok(None) => {}
59            Err(error) => {
60                failure.get_or_insert(error);
61            }
62        }
63    }
64
65    if candidates.is_empty() {
66        if let Some(error) = failure {
67            return Err(error);
68        }
69        return Err(function_resolution_error(
70            "42883",
71            name,
72            call.argument_names,
73            call.argument_types,
74            "does not exist",
75        ));
76    }
77    if !rank_function_matches(&mut candidates, call.argument_types) || candidates.len() != 1 {
78        return Err(function_resolution_error(
79            "42725",
80            name,
81            call.argument_names,
82            call.argument_types,
83            "is not unique",
84        ));
85    }
86    let Candidate {
87        declaration,
88        matched,
89        wildcard,
90    } = candidates.pop().expect("one selected catalog routine");
91    let result_type = matched
92        .substitute_type(&declaration.return_type)
93        .ok_or_else(|| SQLError::Internal("unresolved catalog routine result type".into()))?;
94    let mut invocation = matched.invocation_binding(Some(&declaration.return_type));
95    if let Some(position) = wildcard {
96        invocation.parameter_types.truncate(position);
97        invocation.parameter_types.push("any".into());
98        for index in position..call.argument_types.len() {
99            invocation.argument_positions[index] = position;
100            invocation.argument_targets[index] = call.argument_types[index]
101                .as_ref()
102                .map_or_else(|| "unknown".into(), ColumnType::catalog_name);
103        }
104        invocation.variadic_mode = if call.explicit_variadic {
105            RoutineVariadicMode::Explicit {
106                parameter_index: position,
107            }
108        } else {
109            RoutineVariadicMode::Expanded {
110                parameter_index: position,
111            }
112        };
113    }
114    Ok(RoutineIdentity {
115        oid: declaration.oid,
116        invocation,
117        result_type,
118    })
119}
120
121fn candidate<'a>(
122    name: &str,
123    declaration: &'a RoutineDeclaration,
124    call: RoutineCallDescriptor<'_>,
125) -> Result<Option<Candidate<'a>>, SQLError> {
126    let wildcard = declaration
127        .parameters
128        .last()
129        .filter(|parameter| parameter.variadic && parameter.type_name == "any")
130        .map(|_| declaration.parameters.len() - 1);
131    let parameters;
132    let matching = if let Some(position) = wildcard {
133        if call.argument_types.len() <= position || call.argument_names.iter().any(Option::is_some)
134        {
135            return Ok(None);
136        }
137        if call.explicit_variadic
138            && (call.argument_types.len() != position + 1
139                || !matches!(call.argument_types[position], Some(ColumnType::Array(_))))
140        {
141            return Ok(None);
142        }
143        // VARIADIC any consumes individually typed values, without selecting a common element type or forming an array.
144        parameters = declaration.parameters[..position]
145            .iter()
146            .cloned()
147            .chain(call.argument_types[position..].iter().map(|ty| {
148                let ty = ty.clone().unwrap_or(ColumnType::Text);
149                RoutineParameterDescriptor {
150                    name: None,
151                    type_name: ty.catalog_name(),
152                    column_type: Some(ty),
153                    has_default: false,
154                    default_type: None,
155                    variadic: false,
156                }
157            }))
158            .collect::<Vec<_>>();
159        &parameters
160    } else {
161        &declaration.parameters
162    };
163    let mut matching_call = call;
164    if wildcard.is_some() {
165        matching_call.explicit_variadic = false;
166    }
167    let matched = match_routine_signature(matching, matching_call).map_err(|error| {
168        crate::routines::resolution::static_signature_error(
169            crate::routines::resolution::RoutineCallKind::Function,
170            name,
171            error,
172        )
173    })?;
174    Ok(matched.map(|matched| Candidate {
175        declaration,
176        matched,
177        wildcard,
178    }))
179}