1use uqa_sql::ast::{ColumnType, FunctionBinding};
10use uqa_sql::{SQLError, SQLParam};
11
12use crate::{RowSchema, ScalarExpr};
13#[cfg(test)]
14use uqa_core::Value;
15
16mod array_transform;
17mod cast_compatibility;
18mod checksum;
19mod common;
20mod containment;
21mod equality;
22mod fixed_builtin;
23mod functions;
24mod gamma;
25mod introspection;
26mod json_strip;
27mod length;
28mod md5;
29mod operators;
30mod overload_resolution;
31mod qualified_column;
32mod range;
33mod reverse;
34mod routine_signature;
35mod string_binary;
36
37#[doc(hidden)]
38pub use checksum::{resolve_checksum_overload, ResolvedChecksumOverload};
39pub use common::{
40 common_context_expression_type, common_type, effective_overload_argument_type,
41 effective_overload_argument_type_with_params, function_call_argument_signature,
42 values_column_types, FunctionCallArgumentSignature,
43};
44pub use equality::{equality_operand_type, foreign_key_operand_type};
45#[doc(hidden)]
46pub use fixed_builtin::{
47 fixed_builtin_return_type, is_function as is_fixed_builtin, resolve_fixed_builtin_call,
48 ResolvedFixedBuiltinCall,
49};
50pub use functions::{builtin_function_argument_targets, builtin_function_type};
51#[doc(hidden)]
52pub use gamma::{resolve_gamma_overload, ResolvedGammaOverload};
53pub use introspection::{bind_type_introspection, bind_type_introspection_with_resolver};
54#[doc(hidden)]
55pub use json_strip::{resolve_json_strip_overload, ResolvedJsonStripOverload};
56#[doc(hidden)]
57pub use length::{resolve_length_overload, ResolvedLengthOverload};
58#[doc(hidden)]
59pub use md5::{resolve_md5_overload, ResolvedMd5Overload};
60#[doc(hidden)]
61pub use operators::{require_equality_operator, require_ordering_operator};
62#[doc(hidden)]
63pub use overload_resolution::{
64 builtin_binding_matches, builtin_name_matches, canonical_column_type_name,
65 canonical_routine_type_name, function_resolution_error, match_builtin_function_overload,
66 match_function_signature, rank_function_matches, resolve_local_builtin_overload,
67 routine_type_accepts_implicit_cast, routine_type_category, routine_type_is_preferred,
68 FunctionParameterDescriptor, MatchedBuiltinFunction, MatchedFunctionSignature,
69 RankedFunctionMatch,
70};
71#[doc(hidden)]
72pub use reverse::{resolve_reverse_overload, ResolvedReverseOverload};
73#[doc(hidden)]
74pub use routine_signature::{
75 match_routine_signature, routine_polymorphic_type, MatchedRoutineSignature,
76 RoutineCallDescriptor, RoutineCoercionTarget, RoutineParameterDescriptor,
77 RoutinePolymorphicFamily, RoutinePolymorphicType, RoutineSignatureMatchError,
78 RoutineTypeSubstitutions, RoutineVariadicMode, RoutineVariadicPlan,
79};
80#[doc(hidden)]
81pub use string_binary::{ResolvedStringBinaryOverload, ResolvedTextByteaOverload};
82
83pub trait FunctionTypeResolver: Send + Sync {
84 fn has_untyped_function(&self, _name: &str) -> bool {
89 false
90 }
91
92 fn resolve_type_name(&self, _name: &str) -> Result<Option<ColumnType>, SQLError> {
95 Ok(None)
96 }
97
98 fn resolve_function_type(
99 &self,
100 name: &str,
101 binding: Option<&FunctionBinding>,
102 argument_names: &[Option<String>],
103 argument_types: &[Option<ColumnType>],
104 explicit_variadic: bool,
105 ) -> Result<Option<ColumnType>, SQLError>;
106
107 fn resolve_function_overload(
109 &self,
110 _name: &str,
111 _binding: Option<&FunctionBinding>,
112 _argument_names: &[Option<String>],
113 _argument_types: &[Option<ColumnType>],
114 _explicit_variadic: bool,
115 ) -> Result<Option<ResolvedFunctionOverload>, SQLError> {
116 Ok(None)
117 }
118
119 fn is_scalar_function_binding(&self, _binding: &FunctionBinding) -> Result<bool, SQLError> {
121 Ok(false)
122 }
123
124 fn resolve_function_overload_with_builtins(
129 &self,
130 name: &str,
131 binding: Option<&FunctionBinding>,
132 argument_names: &[Option<String>],
133 argument_types: &[Option<ColumnType>],
134 explicit_variadic: bool,
135 _builtins: &[BuiltinFunctionOverload],
136 ) -> Result<Option<ResolvedFunctionOverload>, SQLError> {
137 self.resolve_function_overload(
138 name,
139 binding,
140 argument_names,
141 argument_types,
142 explicit_variadic,
143 )
144 }
145
146 fn resolve_scalar_subquery_type(
148 &self,
149 _subquery: crate::SubqueryId,
150 _outer_schema: &RowSchema,
151 _params: &[SQLParam],
152 ) -> Result<Option<ColumnType>, SQLError> {
153 Ok(None)
154 }
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub struct BuiltinFunctionOverload {
159 pub name: String,
160 pub argument_names: Vec<Option<String>>,
161 pub argument_types: Vec<ColumnType>,
162 pub default_arguments: usize,
163 pub return_type: ColumnType,
164}
165
166#[derive(Debug, Clone, PartialEq, Eq)]
167pub struct ResolvedFunctionOverload {
168 pub binding: FunctionBinding,
169 pub return_type: ColumnType,
170 pub exact_matches: usize,
171 pub known_arguments: usize,
172 pub preferred_matches: usize,
173 pub precedes_pg_catalog: bool,
174}
175
176impl ResolvedFunctionOverload {
177 #[must_use]
178 pub fn is_exact_for_known_arguments(&self) -> bool {
179 self.known_arguments > 0 && self.exact_matches == self.known_arguments
180 }
181}
182
183pub fn scalar_type(
184 expression: &ScalarExpr,
185 schema: &RowSchema,
186 params: &[SQLParam],
187) -> Result<Option<ColumnType>, SQLError> {
188 scalar_type_inner(expression, schema, params, None)
189}
190
191pub fn scalar_type_with_resolver(
192 expression: &ScalarExpr,
193 schema: &RowSchema,
194 params: &[SQLParam],
195 resolver: &dyn FunctionTypeResolver,
196) -> Result<Option<ColumnType>, SQLError> {
197 scalar_type_inner(expression, schema, params, Some(resolver))
198}
199
200#[expect(
201 clippy::too_many_lines,
202 reason = "type resolution preserves candidate order and ambiguity diagnostics atomically"
203)]
204pub(super) fn scalar_type_inner(
205 expression: &ScalarExpr,
206 schema: &RowSchema,
207 params: &[SQLParam],
208 resolver: Option<&dyn FunctionTypeResolver>,
209) -> Result<Option<ColumnType>, SQLError> {
210 if matches!(
211 expression,
212 ScalarExpr::Func { binding, .. }
213 if binding.as_ref().and_then(|binding| binding.dispatch).is_some_and(
214 uqa_sql::ast::FunctionDispatch::is_call_argument_marker
215 )
216 ) {
217 let argument = crate::scalar_call_argument(expression)?;
218 return scalar_type_inner(argument.value, schema, params, resolver);
219 }
220 match expression {
221 ScalarExpr::Column(column) => {
222 if schema.has_unqualified_column(column) || schema.column_is_ambiguous(column) {
223 Ok(schema.type_of(column).cloned())
224 } else if schema.has_qualifier(column) {
225 Ok(Some(ColumnType::Record))
226 } else {
227 Ok(None)
228 }
229 }
230 ScalarExpr::Position(position) => Ok(schema.column_type(*position).cloned()),
231 ScalarExpr::InternalColumn(column) => Ok(schema.internal_type(*column).cloned()),
232 ScalarExpr::QualifiedColumn { qualifier, column } => {
233 qualified_column::resolve(schema, qualifier, column)
234 }
235 ScalarExpr::Literal(value) => Ok(common::value_type(value)),
236 ScalarExpr::Param(index) => Ok(index
237 .checked_sub(1)
238 .and_then(|index| params.get(index))
239 .and_then(common::parameter_type)),
240 ScalarExpr::Cast { expr, ty } => {
241 let source = scalar_type_inner(expr, schema, params, resolver)?;
242 let target = match ColumnType::from_sql_name(ty) {
243 Ok(ty) => Ok(Some(ty)),
244 Err(error @ SQLError::Unsupported(_)) => match resolver {
245 Some(resolver) => resolver
246 .resolve_type_name(ty)?
247 .map_or(Err(error), |ty| Ok(Some(ty))),
248 None => Err(error),
249 },
250 Err(error) => Err(error),
251 }?;
252 if let Some(target) = target.as_ref() {
253 cast_compatibility::validate_void_cast(source.as_ref(), target)?;
254 }
255 Ok(target)
256 }
257 ScalarExpr::Array(items) => {
258 let mut element = None;
259 for item in items {
260 element = common::merge_optional_types(
261 element,
262 common::common_context_expression_type(item, schema, params, resolver)?,
263 )?;
264 }
265 Ok(element.map(|element| ColumnType::Array(Box::new(element))))
266 }
267 ScalarExpr::Row(items) => {
268 for item in items {
269 scalar_type_inner(item, schema, params, resolver)?;
270 }
271 Ok(Some(ColumnType::Record))
272 }
273 ScalarExpr::Binary { op, lhs, rhs } => {
274 let left = scalar_type_inner(lhs, schema, params, resolver)?;
275 let right = scalar_type_inner(rhs, schema, params, resolver)?;
276 operators::binary_result_type(*op, left.as_ref(), right.as_ref())
277 }
278 ScalarExpr::UnaryMinus(inner) => scalar_type_inner(inner, schema, params, resolver)?
279 .map_or(Ok(None), |ty| {
280 operators::unary_minus_result_type(&ty).map(Some)
281 }),
282 ScalarExpr::Not(inner) | ScalarExpr::IsNull { expr: inner, .. } => {
283 scalar_type_inner(inner, schema, params, resolver)?;
284 Ok(Some(ColumnType::Boolean))
285 }
286 ScalarExpr::And(items) | ScalarExpr::Or(items) => {
287 for item in items {
288 scalar_type_inner(item, schema, params, resolver)?;
289 }
290 Ok(Some(ColumnType::Boolean))
291 }
292 ScalarExpr::Between { expr, low, high } => {
293 let value = scalar_type_inner(expr, schema, params, resolver)?;
294 let low = scalar_type_inner(low, schema, params, resolver)?;
295 let high = scalar_type_inner(high, schema, params, resolver)?;
296 operators::binary_result_type(
297 uqa_sql::ast::BinaryOp::GreaterEqual,
298 value.as_ref(),
299 low.as_ref(),
300 )?;
301 operators::binary_result_type(
302 uqa_sql::ast::BinaryOp::LessEqual,
303 value.as_ref(),
304 high.as_ref(),
305 )?;
306 Ok(Some(ColumnType::Boolean))
307 }
308 ScalarExpr::InList { expr, list, .. } => {
309 let needle = scalar_type_inner(expr, schema, params, resolver)?;
310 for item in list {
311 let candidate = scalar_type_inner(item, schema, params, resolver)?;
312 operators::binary_result_type(
313 uqa_sql::ast::BinaryOp::Equal,
314 needle.as_ref(),
315 candidate.as_ref(),
316 )?;
317 }
318 Ok(Some(ColumnType::Boolean))
319 }
320 ScalarExpr::InSubquery { expr, subquery, .. } => {
321 let needle = scalar_type_inner(expr, schema, params, resolver)?;
322 let candidate = resolver
323 .map(|resolver| resolver.resolve_scalar_subquery_type(*subquery, schema, params))
324 .transpose()?
325 .flatten();
326 operators::binary_result_type(
327 uqa_sql::ast::BinaryOp::Equal,
328 needle.as_ref(),
329 candidate.as_ref(),
330 )?;
331 Ok(Some(ColumnType::Boolean))
332 }
333 ScalarExpr::Exists { .. } => Ok(Some(ColumnType::Boolean)),
334 ScalarExpr::Case {
335 base,
336 when,
337 else_branch,
338 } => {
339 let simple = base.is_some();
340 let base_type = base
341 .as_deref()
342 .map(|base| scalar_type_inner(base, schema, params, resolver))
343 .transpose()?
344 .flatten();
345 let mut result = None;
346 for (condition, value) in when {
347 let condition_type = scalar_type_inner(condition, schema, params, resolver)?;
348 if simple {
349 operators::binary_result_type(
350 uqa_sql::ast::BinaryOp::Equal,
351 base_type.as_ref(),
352 condition_type.as_ref(),
353 )?;
354 }
355 result = common::merge_optional_types(
356 result,
357 common::common_context_expression_type(value, schema, params, resolver)?,
358 )?;
359 }
360 if let Some(value) = else_branch {
361 result = common::merge_optional_types(
362 result,
363 common::common_context_expression_type(value, schema, params, resolver)?,
364 )?;
365 }
366 Ok(result)
367 }
368 ScalarExpr::Func {
369 name,
370 binding,
371 args,
372 distinct,
373 order_by,
374 filter,
375 } => {
376 if let Some(uqa_sql::ast::FunctionResolutionError::UndefinedFunction { signature }) =
377 binding
378 .as_ref()
379 .and_then(|binding| binding.resolution_error.as_ref())
380 {
381 return Err(SQLError::Routine {
382 sqlstate: "42883".into(),
383 message: format!("function {signature} does not exist"),
384 });
385 }
386 if let Some(filter) = filter {
387 scalar_type_inner(filter, schema, params, resolver)?;
388 }
389 if *distinct {
390 for argument in args {
391 if let Some(ty) = scalar_type_inner(argument, schema, params, resolver)? {
392 require_equality_operator(&ty)?;
393 }
394 }
395 }
396 for order in order_by {
397 if let Some(ty) = scalar_type_inner(&order.expr, schema, params, resolver)? {
398 require_ordering_operator(&ty)?;
399 }
400 }
401 functions::builtin_function_type_inner(
402 name,
403 binding.as_ref(),
404 args,
405 order_by,
406 schema,
407 params,
408 resolver,
409 )
410 }
411 ScalarExpr::WindowCall { name, args, spec } => {
412 for expression in &spec.partition_by {
413 if let Some(ty) = scalar_type_inner(expression, schema, params, resolver)? {
414 require_equality_operator(&ty)?;
415 }
416 }
417 for order in &spec.order_by {
418 if let Some(ty) = scalar_type_inner(&order.expr, schema, params, resolver)? {
419 require_ordering_operator(&ty)?;
420 }
421 }
422 if let Some(frame) = &spec.frame {
423 for bound in [&frame.start, &frame.end] {
424 match bound {
425 crate::ScalarFrameBound::Preceding(expression)
426 | crate::ScalarFrameBound::Following(expression) => {
427 scalar_type_inner(expression, schema, params, resolver)?;
428 }
429 crate::ScalarFrameBound::UnboundedPreceding
430 | crate::ScalarFrameBound::UnboundedFollowing
431 | crate::ScalarFrameBound::CurrentRow => {}
432 }
433 }
434 }
435 functions::builtin_function_type_inner(name, None, args, &[], schema, params, resolver)
436 }
437 ScalarExpr::ScalarSubquery(subquery) => resolver.map_or(Ok(None), |resolver| {
438 resolver.resolve_scalar_subquery_type(*subquery, schema, params)
439 }),
440 ScalarExpr::QualifiedStar(qualifier) if schema.has_qualifier(qualifier) => {
441 Ok(Some(ColumnType::Record))
442 }
443 ScalarExpr::Star | ScalarExpr::QualifiedStar(_) | ScalarExpr::Default => Ok(None),
444 }
445}
446
447#[cfg(test)]
448mod tests;