Skip to main content

uqa_sql/type_resolution/
mod.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Static SQL type propagation and PostgreSQL-compatible common-type rules.
8
9use crate::ast::{ColumnType, FunctionBinding};
10use crate::{SQLError, SQLParam};
11
12use crate::schema::ScalarTypeSchema;
13use crate::{RowSchema, ScalarExpr};
14#[cfg(test)]
15use uqa_core::Value;
16
17mod array_transform;
18mod call;
19mod cast_compatibility;
20mod checksum;
21mod common;
22pub(crate) use common::array_element_type;
23pub(crate) use common::value_type;
24pub(crate) use common::value_type_with_control;
25mod containment;
26mod equality;
27mod fixed_builtin;
28mod functions;
29mod gamma;
30mod inference;
31use inference::scalar_type_inner_with_control;
32mod introspection;
33mod json_strip;
34mod length;
35mod md5;
36mod operators;
37mod overload_resolution;
38mod qualified_column;
39mod range;
40mod reverse;
41mod routine_signature;
42mod scalar_input;
43pub use scalar_input::{
44    scalar_cast_source_type_name_with_control, scalar_integer_operation_width,
45    scalar_integer_operation_width_with_control, scalar_operand_type_name,
46    scalar_operand_type_name_with_control,
47};
48mod string_binary;
49
50pub(crate) use cast_compatibility::cast_catalog_entry_with_control;
51pub use cast_compatibility::{
52    assignment_type_compatible, cast_catalog_entry, explicit_type_compatible, CastCatalogEntry,
53    CastMethod,
54};
55#[doc(hidden)]
56pub use checksum::{resolve_checksum_overload, ResolvedChecksumOverload};
57pub use common::{
58    common_context_expression_type, common_type, effective_overload_argument_type,
59    effective_overload_argument_type_with_params, function_call_argument_signature,
60    values_column_types, FunctionCallArgumentSignature,
61};
62pub use equality::{
63    equality_operand_type, equality_operand_type_with_control, foreign_key_operand_type,
64};
65#[doc(hidden)]
66pub use fixed_builtin::{
67    fixed_builtin_return_type, fixed_builtin_return_type_with_control,
68    is_function as is_fixed_builtin, resolve_fixed_builtin_call, ResolvedFixedBuiltinCall,
69};
70pub use functions::{builtin_function_argument_targets, builtin_function_type};
71#[doc(hidden)]
72pub use gamma::{resolve_gamma_overload, ResolvedGammaOverload};
73pub use introspection::{
74    bind_type_introspection, bind_type_introspection_with_control,
75    bind_type_introspection_with_resolver,
76};
77#[doc(hidden)]
78pub use json_strip::{resolve_json_strip_overload, ResolvedJsonStripOverload};
79#[doc(hidden)]
80pub use length::{resolve_length_overload, ResolvedLengthOverload};
81#[doc(hidden)]
82pub use md5::{resolve_md5_overload, ResolvedMd5Overload};
83#[doc(hidden)]
84pub use operators::{
85    binary_operator_by_oid, binary_operator_catalog_entry, binary_operator_types,
86    binary_operator_types_with_control, binary_result_type, binary_result_type_with_control,
87    numeric_operator_types, numeric_operator_types_with_control, require_equality_operator,
88    require_ordering_operator, unary_minus_catalog_entry, unary_operator_by_oid,
89    BinaryOperatorCatalogEntry, NumericOperatorTypes, UnaryOperatorCatalogEntry,
90};
91#[doc(hidden)]
92pub use overload_resolution::{
93    builtin_binding_matches, builtin_name_matches, canonical_column_type_name,
94    canonical_routine_type_name, function_resolution_error, match_builtin_function_overload,
95    match_function_signature, rank_function_matches, resolve_local_builtin_overload,
96    routine_type_accepts_implicit_cast, routine_type_category, routine_type_is_preferred,
97    FunctionParameterDescriptor, MatchedBuiltinFunction, MatchedFunctionSignature,
98    RankedFunctionMatch,
99};
100#[doc(hidden)]
101pub use reverse::{resolve_reverse_overload, ResolvedReverseOverload};
102#[doc(hidden)]
103pub use routine_signature::{
104    match_routine_signature, routine_polymorphic_type, MatchedRoutineSignature,
105    RoutineCallDescriptor, RoutineCoercionTarget, RoutineParameterDescriptor,
106    RoutinePolymorphicFamily, RoutinePolymorphicType, RoutineSignatureMatchError,
107    RoutineTypeSubstitutions, RoutineVariadicMode, RoutineVariadicPlan,
108};
109#[doc(hidden)]
110pub use string_binary::{ResolvedStringBinaryOverload, ResolvedTextByteaOverload};
111
112pub trait FunctionTypeResolver: Send + Sync {
113    /// Return whether an external runtime callback claims this unbound function
114    /// name without exposing a declared SQL return type. Such callbacks must
115    /// retain dispatch precedence instead of being rebound to a same-named
116    /// built-in overload.
117    fn has_untyped_function(&self, _name: &str) -> bool {
118        false
119    }
120
121    /// Resolve a catalog-owned SQL type name that is not represented by the
122    /// built-in [`ColumnType::from_sql_name`] mapping, such as a domain.
123    fn resolve_type_name(&self, _name: &str) -> Result<Option<ColumnType>, SQLError> {
124        Ok(None)
125    }
126
127    fn resolve_function_type(
128        &self,
129        name: &str,
130        binding: Option<&FunctionBinding>,
131        argument_names: &[Option<String>],
132        argument_types: &[Option<ColumnType>],
133        explicit_variadic: bool,
134    ) -> Result<Option<ColumnType>, SQLError>;
135
136    /// Resolve a catalog-backed overload together with the stable binding needed to execute it after built-in and user-defined candidates have been ranked.
137    fn resolve_function_overload(
138        &self,
139        _name: &str,
140        _binding: Option<&FunctionBinding>,
141        _argument_names: &[Option<String>],
142        _argument_types: &[Option<ColumnType>],
143        _explicit_variadic: bool,
144    ) -> Result<Option<ResolvedFunctionOverload>, SQLError> {
145        Ok(None)
146    }
147
148    /// Return whether an exact catalog-selected binding can execute in a scalar expression. The conservative default prevents aggregate, procedure, and set-returning routines from being attached to [`ScalarExpr::Func`].
149    fn is_scalar_function_binding(&self, _binding: &FunctionBinding) -> Result<bool, SQLError> {
150        Ok(false)
151    }
152
153    /// Resolve catalog-backed routines and the supplied built-in overloads as
154    /// one `PostgreSQL` candidate set. Implementations with catalog visibility
155    /// should override this so search-path shadowing and unknown-category
156    /// selection happen before a winner is chosen.
157    fn resolve_function_overload_with_builtins(
158        &self,
159        name: &str,
160        binding: Option<&FunctionBinding>,
161        argument_names: &[Option<String>],
162        argument_types: &[Option<ColumnType>],
163        explicit_variadic: bool,
164        _builtins: &[BuiltinFunctionOverload],
165    ) -> Result<Option<ResolvedFunctionOverload>, SQLError> {
166        self.resolve_function_overload(
167            name,
168            binding,
169            argument_names,
170            argument_types,
171            explicit_variadic,
172        )
173    }
174
175    /// Resolve the declared first-column type of a physical scalar-subquery slot when the owning execution context carries its plan arena.
176    fn resolve_scalar_subquery_type(
177        &self,
178        _subquery: crate::SubqueryId,
179        _outer_schema: &RowSchema,
180        _params: &[SQLParam],
181    ) -> Result<Option<ColumnType>, SQLError> {
182        Ok(None)
183    }
184}
185
186#[derive(Debug, Clone, PartialEq, Eq)]
187pub struct BuiltinFunctionOverload {
188    pub name: String,
189    pub argument_names: Vec<Option<String>>,
190    pub argument_types: Vec<ColumnType>,
191    pub default_arguments: usize,
192    pub return_type: ColumnType,
193}
194
195#[derive(Debug, Clone, PartialEq, Eq)]
196pub struct ResolvedFunctionOverload {
197    pub binding: FunctionBinding,
198    pub return_type: ColumnType,
199    pub exact_matches: usize,
200    pub known_arguments: usize,
201    pub preferred_matches: usize,
202    pub precedes_pg_catalog: bool,
203}
204
205impl ResolvedFunctionOverload {
206    #[must_use]
207    pub fn is_exact_for_known_arguments(&self) -> bool {
208        self.known_arguments > 0 && self.exact_matches == self.known_arguments
209    }
210}
211
212pub fn scalar_type(
213    expression: &ScalarExpr,
214    schema: &dyn ScalarTypeSchema,
215    params: &[SQLParam],
216) -> Result<Option<ColumnType>, SQLError> {
217    scalar_type_inner(expression, schema, params, None)
218}
219
220/// Infer a scalar type from borrowed schema and parameter metadata while retaining every constructed type and temporary buffer under the supplied allowance.
221pub fn scalar_type_with_control(
222    expression: &ScalarExpr,
223    schema: &dyn ScalarTypeSchema,
224    params: &[SQLParam],
225    control: &uqa_core::memory::ProductionControl<'_>,
226) -> Result<Option<uqa_core::memory::Produced<ColumnType>>, SQLError> {
227    scalar_type_inner_with_control(expression, schema, params, None, control)
228}
229
230/// Preserve unknown-literal and domain rules when selecting an operator's common type without a catalog callback.
231pub fn common_context_type_with_control(
232    expression: &ScalarExpr,
233    schema: &dyn ScalarTypeSchema,
234    params: &[SQLParam],
235    control: &uqa_core::memory::ProductionControl<'_>,
236) -> Result<Option<uqa_core::memory::Produced<ColumnType>>, SQLError> {
237    common::common_context_expression_type_with_control(expression, schema, params, None, control)
238}
239
240pub fn scalar_type_with_resolver(
241    expression: &ScalarExpr,
242    schema: &dyn ScalarTypeSchema,
243    params: &[SQLParam],
244    resolver: &dyn FunctionTypeResolver,
245) -> Result<Option<ColumnType>, SQLError> {
246    scalar_type_inner(expression, schema, params, Some(resolver))
247}
248
249pub(super) fn scalar_type_inner(
250    expression: &ScalarExpr,
251    schema: &dyn ScalarTypeSchema,
252    params: &[SQLParam],
253    resolver: Option<&dyn FunctionTypeResolver>,
254) -> Result<Option<ColumnType>, SQLError> {
255    scalar_type_inner_with_control(
256        expression,
257        schema,
258        params,
259        resolver,
260        &uqa_core::memory::ProductionControl::uncontrolled(),
261    )
262    .map(|ty| {
263        ty.map(|ty| {
264            ty.into_uncontrolled()
265                .expect("ordinary scalar inference has no reservation")
266        })
267    })
268}
269
270#[cfg(test)]
271mod tests;
272
273mod declaration;
274pub use declaration::resolve_declared_column_type;
275
276mod coercion;
277pub use coercion::coerce_common_context_value;