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