1use uqa_core::Value;
10use uqa_sql::ast::{BinaryOp, ColumnType, FunctionBinding};
11use uqa_sql::expr::{TO_HEX_INT4_FUNCTION, TO_HEX_INT8_FUNCTION};
12use uqa_sql::{SQLError, SQLParam};
13
14use crate::{RowSchema, ScalarExpr};
15
16mod containment;
17mod to_hex;
18
19pub trait FunctionTypeResolver: Send + Sync {
20 fn resolve_function_type(
21 &self,
22 name: &str,
23 binding: Option<&FunctionBinding>,
24 argument_names: &[Option<String>],
25 argument_types: &[Option<ColumnType>],
26 ) -> Result<Option<ColumnType>, SQLError>;
27}
28
29pub fn scalar_type(
30 expression: &ScalarExpr,
31 schema: &RowSchema,
32 params: &[SQLParam],
33) -> Result<Option<ColumnType>, SQLError> {
34 scalar_type_inner(expression, schema, params, None)
35}
36
37pub fn scalar_type_with_resolver(
38 expression: &ScalarExpr,
39 schema: &RowSchema,
40 params: &[SQLParam],
41 resolver: &dyn FunctionTypeResolver,
42) -> Result<Option<ColumnType>, SQLError> {
43 scalar_type_inner(expression, schema, params, Some(resolver))
44}
45
46fn scalar_type_inner(
47 expression: &ScalarExpr,
48 schema: &RowSchema,
49 params: &[SQLParam],
50 resolver: Option<&dyn FunctionTypeResolver>,
51) -> Result<Option<ColumnType>, SQLError> {
52 match expression {
53 ScalarExpr::Column(column) => Ok(schema.type_of(column).cloned()),
54 ScalarExpr::Position(position) => Ok(schema.column_type(*position).cloned()),
55 ScalarExpr::QualifiedColumn { qualifier, column } => {
56 Ok(schema.qualified_type(qualifier, column).cloned())
57 }
58 ScalarExpr::Literal(value) => Ok(value_type(value)),
59 ScalarExpr::Param(index) => Ok(index
60 .checked_sub(1)
61 .and_then(|index| params.get(index))
62 .and_then(parameter_type)),
63 ScalarExpr::Cast { expr, ty } => {
64 scalar_type_inner(expr, schema, params, resolver)?;
65 ColumnType::from_sql_name(ty).map(Some)
66 }
67 ScalarExpr::Array(items) => {
68 let mut element = None;
69 for item in items {
70 element = merge_optional_types(
71 element,
72 common_context_expression_type(item, schema, params, resolver)?,
73 )?;
74 }
75 Ok(element.map(|element| ColumnType::Array(Box::new(element))))
76 }
77 ScalarExpr::Row(items) => {
78 for item in items {
79 scalar_type_inner(item, schema, params, resolver)?;
80 }
81 Ok(Some(ColumnType::Record))
82 }
83 ScalarExpr::Binary { op, lhs, rhs } => {
84 let left = scalar_type_inner(lhs, schema, params, resolver)?;
85 let right = scalar_type_inner(rhs, schema, params, resolver)?;
86 binary_result_type(*op, left.as_ref(), right.as_ref())
87 }
88 ScalarExpr::UnaryMinus(inner) => scalar_type_inner(inner, schema, params, resolver)?
89 .map_or(Ok(None), |ty| unary_minus_result_type(&ty).map(Some)),
90 ScalarExpr::Not(inner) | ScalarExpr::IsNull { expr: inner, .. } => {
91 scalar_type_inner(inner, schema, params, resolver)?;
92 Ok(Some(ColumnType::Boolean))
93 }
94 ScalarExpr::And(items) | ScalarExpr::Or(items) => {
95 for item in items {
96 scalar_type_inner(item, schema, params, resolver)?;
97 }
98 Ok(Some(ColumnType::Boolean))
99 }
100 ScalarExpr::Between { expr, low, high } => {
101 scalar_type_inner(expr, schema, params, resolver)?;
102 scalar_type_inner(low, schema, params, resolver)?;
103 scalar_type_inner(high, schema, params, resolver)?;
104 Ok(Some(ColumnType::Boolean))
105 }
106 ScalarExpr::InList { expr, list, .. } => {
107 scalar_type_inner(expr, schema, params, resolver)?;
108 for item in list {
109 scalar_type_inner(item, schema, params, resolver)?;
110 }
111 Ok(Some(ColumnType::Boolean))
112 }
113 ScalarExpr::InSubquery { expr, .. } => {
114 scalar_type_inner(expr, schema, params, resolver)?;
115 Ok(Some(ColumnType::Boolean))
116 }
117 ScalarExpr::Exists { .. } => Ok(Some(ColumnType::Boolean)),
118 ScalarExpr::Case {
119 base,
120 when,
121 else_branch,
122 } => {
123 if let Some(base) = base {
124 scalar_type_inner(base, schema, params, resolver)?;
125 }
126 let mut result = None;
127 for (condition, value) in when {
128 scalar_type_inner(condition, schema, params, resolver)?;
129 result = merge_optional_types(
130 result,
131 common_context_expression_type(value, schema, params, resolver)?,
132 )?;
133 }
134 if let Some(value) = else_branch {
135 result = merge_optional_types(
136 result,
137 common_context_expression_type(value, schema, params, resolver)?,
138 )?;
139 }
140 Ok(result)
141 }
142 ScalarExpr::Func {
143 name,
144 binding,
145 args,
146 order_by,
147 filter,
148 ..
149 } => {
150 if let Some(filter) = filter {
151 scalar_type_inner(filter, schema, params, resolver)?;
152 }
153 builtin_function_type_inner(
154 name,
155 binding.as_ref(),
156 args,
157 order_by,
158 schema,
159 params,
160 resolver,
161 )
162 }
163 ScalarExpr::WindowCall { name, args, spec } => {
164 for expression in &spec.partition_by {
165 scalar_type_inner(expression, schema, params, resolver)?;
166 }
167 for order in &spec.order_by {
168 scalar_type_inner(&order.expr, schema, params, resolver)?;
169 }
170 if let Some(frame) = &spec.frame {
171 for bound in [&frame.start, &frame.end] {
172 match bound {
173 crate::ScalarFrameBound::Preceding(expression)
174 | crate::ScalarFrameBound::Following(expression) => {
175 scalar_type_inner(expression, schema, params, resolver)?;
176 }
177 crate::ScalarFrameBound::UnboundedPreceding
178 | crate::ScalarFrameBound::UnboundedFollowing
179 | crate::ScalarFrameBound::CurrentRow => {}
180 }
181 }
182 }
183 builtin_function_type_inner(name, None, args, &[], schema, params, resolver)
184 }
185 ScalarExpr::ScalarSubquery(_)
186 | ScalarExpr::Star
187 | ScalarExpr::QualifiedStar(_)
188 | ScalarExpr::Default => Ok(None),
189 }
190}
191
192pub fn builtin_function_type(
193 name: &str,
194 args: &[ScalarExpr],
195 order_by: &[crate::ScalarOrder],
196 schema: &RowSchema,
197 params: &[SQLParam],
198) -> Result<Option<ColumnType>, SQLError> {
199 builtin_function_type_inner(name, None, args, order_by, schema, params, None)
200}
201
202fn builtin_function_type_inner(
203 name: &str,
204 binding: Option<&FunctionBinding>,
205 args: &[ScalarExpr],
206 order_by: &[crate::ScalarOrder],
207 schema: &RowSchema,
208 params: &[SQLParam],
209 resolver: Option<&dyn FunctionTypeResolver>,
210) -> Result<Option<ColumnType>, SQLError> {
211 let original_name = name;
212 let lower = name.to_ascii_lowercase();
213 let name = lower.strip_prefix("pg_catalog.").unwrap_or(&lower);
214 if name.contains('.') {
215 return resolve_extension_function_type(
216 resolver,
217 original_name,
218 binding,
219 args,
220 schema,
221 params,
222 );
223 }
224 if name == uqa_sql::expr::NAMED_ARG_FUNCTION {
225 return args.get(1).map_or(Ok(None), |expression| {
226 scalar_type_inner(expression, schema, params, resolver)
227 });
228 }
229 let argument = |position: usize| -> Result<Option<ColumnType>, SQLError> {
230 args.get(position).map_or(Ok(None), |expression| {
231 scalar_type_inner(named_argument_value(expression), schema, params, resolver)
232 })
233 };
234 let ordered_argument = || -> Result<Option<ColumnType>, SQLError> {
235 order_by.first().map_or(Ok(None), |order| {
236 scalar_type_inner(&order.expr, schema, params, resolver)
237 })
238 };
239 let first = || argument(0);
240 for argument in args {
241 scalar_type_inner(named_argument_value(argument), schema, params, resolver)?;
242 }
243 for order in order_by {
244 scalar_type_inner(&order.expr, schema, params, resolver)?;
245 }
246 match name {
247 "pg_typeof" => Ok(Some(ColumnType::Regtype)),
248 "typeof"
249 | "upper"
250 | "lower"
251 | "casefold"
252 | "initcap"
253 | "trim"
254 | "btrim"
255 | "ltrim"
256 | "rtrim"
257 | "concat"
258 | "concat_ws"
259 | "replace"
260 | "substring"
261 | "substr"
262 | "left"
263 | "right"
264 | "chr"
265 | "regexp_replace"
266 | "lpad"
267 | "rpad"
268 | "repeat"
269 | "translate"
270 | "overlay"
271 | "format"
272 | "md5"
273 | "encode"
274 | "split_part"
275 | TO_HEX_INT4_FUNCTION
276 | TO_HEX_INT8_FUNCTION
277 | "quote_ident"
278 | "quote_literal"
279 | "quote_nullable"
280 | "regexp_substr"
281 | "array_to_string"
282 | "array_dims"
283 | "json_typeof"
284 | "jsonb_typeof"
285 | "jsonb_pretty"
286 | "to_char"
287 | "timeofday"
288 | "current_setting"
289 | "merge_action"
290 | "string_to_table"
291 | "regexp_split_to_table"
292 | "json_object_keys"
293 | "jsonb_object_keys"
294 | "json_array_elements_text"
295 | "jsonb_array_elements_text"
296 | "json_extract_path_text"
297 | "jsonb_extract_path_text" => Ok(Some(ColumnType::Text)),
298 "to_hex" => to_hex::resolve_type(original_name, args, schema, params, resolver),
299 "count" | "row_number" | "rank" | "dense_rank" | "crc32" | "crc32c" | "nextval"
300 | "currval" | "setval" => Ok(Some(ColumnType::BigInteger)),
301 "sum" => Ok(first()?.and_then(|ty| aggregate_sum_type(&ty))),
302 "avg" => Ok(first()?.and_then(|ty| aggregate_average_type(&ty))),
303 "stddev" | "stddev_samp" | "stddev_pop" | "variance" | "var_samp" | "var_pop" => {
304 Ok(first()?.and_then(|ty| aggregate_average_type(&ty)))
305 }
306 "min" | "max" | "lag" | "lead" | "first_value" | "last_value" | "nth_value" | "nullif"
307 | "array_cat" | "array_remove" | "array_replace" | "trim_array" | "array_sample"
308 | "array_reverse" | "array_sort" | "__slice" | "__array_slices" | "array_append"
309 | "generate_series" => first(),
310 "mode" | "percentile_disc" => ordered_argument(),
311 "percentile_cont" => Ok(ordered_argument()?.map(|ty| match base_type(&ty) {
312 ColumnType::Interval => ColumnType::Interval,
313 _ => ColumnType::DoublePrecision,
314 })),
315 "array_agg" => Ok(first()?.map(|ty| ColumnType::Array(Box::new(ty)))),
316 "string_agg" => Ok(first()?.map(|ty| {
317 if matches!(ty, ColumnType::Bytea) {
318 ColumnType::Bytea
319 } else {
320 ColumnType::Text
321 }
322 })),
323 "json_agg"
324 | "json_object_agg"
325 | "json_array_elements"
326 | "json_extract_path"
327 | "json_strip_nulls"
328 | "to_json"
329 | "row_to_json"
330 | "json_build_object"
331 | "json_build_array" => Ok(Some(ColumnType::Json)),
332 "jsonb_agg"
333 | "jsonb_object_agg"
334 | "jsonb_array_elements"
335 | "jsonb_extract_path"
336 | "json_delete_path"
337 | "jsonb_set"
338 | "jsonb_insert"
339 | "jsonb_strip_nulls"
340 | "to_jsonb"
341 | "jsonb_build_object"
342 | "jsonb_build_array" => Ok(Some(ColumnType::JsonB)),
343 "json_each" | "jsonb_each" | "json_each_text" | "jsonb_each_text" => {
344 Ok(Some(ColumnType::Record))
345 }
346 "bool_and"
347 | "bool_or"
348 | "every"
349 | "starts_with"
350 | "like"
351 | "ilike"
352 | "similar_to"
353 | "regexp_like"
354 | "isfinite"
355 | "contains_op"
356 | "contained_by_op"
357 | "json_contains"
358 | "json_contained_by"
359 | "json_has_key"
360 | "json_has_any_key"
361 | "json_has_all_keys"
362 | "jsonb_path_exists"
363 | "jsonpath_exists"
364 | "jsonb_path_match"
365 | "jsonpath_match"
366 | "array_overlap"
367 | "__any_op"
368 | "__all_op"
369 | "__is_distinct"
370 | "__between_symmetric"
371 | "st_within"
372 | "st_dwithin"
373 | "overlaps" => {
374 if matches!(name, "contains_op" | "contained_by_op") {
375 return containment::resolve_operator_type(name, args, schema, params, resolver);
376 }
377 Ok(Some(ColumnType::Boolean))
378 }
379 "coalesce" | "greatest" | "least" => common_argument_type(args, schema, params, resolver),
380 "reverse" => Ok(first()?.map(|ty| {
381 if matches!(base_type(&ty), ColumnType::Bytea) {
382 ColumnType::Bytea
383 } else {
384 ColumnType::Text
385 }
386 })),
387 "concat_op" => concat_type(argument(0)?, argument(1)?),
388 "ntile" | "length" | "char_length" | "character_length" | "octet_length" | "position"
389 | "strpos" | "ascii" | "width_bucket" | "bit_length" | "regexp_count" | "regexp_instr"
390 | "num_nulls" | "num_nonnulls" | "array_length" | "array_upper" | "array_lower"
391 | "array_ndims" | "cardinality" | "array_position" | "json_array_length"
392 | "jsonb_array_length" => Ok(Some(ColumnType::Integer)),
393 "abs" => Ok(first()?.map(|ty| base_type(&ty).clone())),
394 "round" | "trunc" | "ceil" | "ceiling" | "floor" | "sign" => {
395 Ok(first()?.map(|ty| numeric_unary_result_type(&ty)))
396 }
397 "mod" | "gcd" | "lcm" => numeric_binary_function_type(argument(0)?, argument(1)?),
398 "div" | "factorial" | "extract" | "to_number" => Ok(Some(numeric_type())),
399 "power" | "pow" => numeric_power_type(args, schema, params, resolver),
400 "sqrt" | "ln" | "log" | "log10" => {
401 numeric_transcendental_type(args, schema, params, resolver)
402 }
403 "sin" | "cos" | "tan" | "asin" | "acos" | "atan" | "atan2" | "sinh" | "cosh" | "tanh"
404 | "exp" | "log2" | "cbrt" | "gamma" | "lgamma" | "degrees" | "radians" | "pi"
405 | "random" | "st_distance" | "date_part" => Ok(Some(ColumnType::DoublePrecision)),
406 "regexp_match" | "regexp_matches" | "string_to_array" => {
407 Ok(Some(ColumnType::Array(Box::new(ColumnType::Text))))
408 }
409 "array_positions" => Ok(Some(ColumnType::Array(Box::new(ColumnType::Integer)))),
410 "decode" => Ok(Some(ColumnType::Bytea)),
411 "array_prepend" => argument(1),
412 "array_fill" => Ok(first()?.map(|ty| ColumnType::Array(Box::new(ty)))),
413 "__subscript" | "__array_subscripts" | "unnest" => {
414 Ok(first()?.and_then(array_element_type))
415 }
416 "now" | "current_timestamp" | "clock_timestamp" | "statement_timestamp" => {
417 Ok(Some(ColumnType::TimestampTz))
418 }
419 "current_date" | "make_date" | "to_date" => Ok(Some(ColumnType::Date)),
420 "to_timestamp" => Ok(Some(ColumnType::TimestampTz)),
421 "age" | "make_interval" | "justify_hours" => Ok(Some(ColumnType::Interval)),
422 "date_trunc" => Ok(argument(1)?.map(|ty| match base_type(&ty) {
423 ColumnType::Interval => ColumnType::Interval,
424 ColumnType::Timestamp => ColumnType::Timestamp,
425 _ => ColumnType::TimestampTz,
426 })),
427 "make_timestamp" => Ok(Some(ColumnType::Timestamp)),
428 "gen_random_uuid" | "uuidv4" | "uuidv7" => Ok(Some(ColumnType::Uuid)),
429 "current_database" | "current_catalog" | "current_schema" | "current_user"
430 | "session_user" => Ok(Some(ColumnType::Name)),
431 "current_schemas" => Ok(Some(ColumnType::Array(Box::new(ColumnType::Name)))),
432 _ => {
433 resolve_extension_function_type(resolver, original_name, binding, args, schema, params)
434 }
435 }
436}
437
438fn resolve_extension_function_type(
439 resolver: Option<&dyn FunctionTypeResolver>,
440 name: &str,
441 binding: Option<&FunctionBinding>,
442 args: &[ScalarExpr],
443 schema: &RowSchema,
444 params: &[SQLParam],
445) -> Result<Option<ColumnType>, SQLError> {
446 let Some(resolver) = resolver else {
447 return Ok(None);
448 };
449 let mut argument_names = Vec::with_capacity(args.len());
450 let mut argument_types = Vec::with_capacity(args.len());
451 for argument in args {
452 let (name, value) = named_argument(argument);
453 argument_names.push(name);
454 argument_types.push(
455 if matches!(value, ScalarExpr::Literal(Value::Str(_) | Value::Null)) {
456 None
457 } else {
458 scalar_type_inner(value, schema, params, Some(resolver))?
459 },
460 );
461 }
462 resolver.resolve_function_type(name, binding, &argument_names, &argument_types)
463}
464
465fn named_argument(expression: &ScalarExpr) -> (Option<String>, &ScalarExpr) {
466 let ScalarExpr::Func { name, args, .. } = expression else {
467 return (None, expression);
468 };
469 if name != uqa_sql::expr::NAMED_ARG_FUNCTION {
470 return (None, expression);
471 }
472 let argument_name = args.first().and_then(|name| match name {
473 ScalarExpr::Literal(Value::Str(name)) => Some(name.clone()),
474 _ => None,
475 });
476 (argument_name, args.get(1).unwrap_or(expression))
477}
478
479fn named_argument_value(expression: &ScalarExpr) -> &ScalarExpr {
480 let ScalarExpr::Func { name, args, .. } = expression else {
481 return expression;
482 };
483 if name != uqa_sql::expr::NAMED_ARG_FUNCTION {
484 return expression;
485 }
486 args.get(1).unwrap_or(expression)
487}
488
489fn numeric_type() -> ColumnType {
490 ColumnType::Numeric {
491 precision: None,
492 scale: None,
493 }
494}
495
496fn base_type(mut ty: &ColumnType) -> &ColumnType {
497 while let ColumnType::Domain { base, .. } = ty {
498 ty = base;
499 }
500 ty
501}
502
503fn aggregate_sum_type(ty: &ColumnType) -> Option<ColumnType> {
504 Some(match base_type(ty) {
505 ColumnType::SmallInteger | ColumnType::Integer => ColumnType::BigInteger,
506 ColumnType::BigInteger | ColumnType::Numeric { .. } => numeric_type(),
507 ColumnType::Real => ColumnType::Real,
508 ColumnType::DoublePrecision => ColumnType::DoublePrecision,
509 _ => return None,
510 })
511}
512
513fn aggregate_average_type(ty: &ColumnType) -> Option<ColumnType> {
514 Some(match base_type(ty) {
515 ColumnType::SmallInteger
516 | ColumnType::Integer
517 | ColumnType::BigInteger
518 | ColumnType::Numeric { .. } => numeric_type(),
519 ColumnType::Real | ColumnType::DoublePrecision => ColumnType::DoublePrecision,
520 _ => return None,
521 })
522}
523
524fn common_argument_type(
525 args: &[ScalarExpr],
526 schema: &RowSchema,
527 params: &[SQLParam],
528 resolver: Option<&dyn FunctionTypeResolver>,
529) -> Result<Option<ColumnType>, SQLError> {
530 let mut result = None;
531 for argument in args {
532 result = merge_optional_types(
533 result,
534 common_context_expression_type(
535 named_argument_value(argument),
536 schema,
537 params,
538 resolver,
539 )?,
540 )?;
541 }
542 Ok(result.or(Some(ColumnType::Text)))
543}
544
545fn concat_type(
546 left: Option<ColumnType>,
547 right: Option<ColumnType>,
548) -> Result<Option<ColumnType>, SQLError> {
549 match (left, right) {
550 (Some(ColumnType::Array(left)), Some(ColumnType::Array(right))) => {
551 common_type(&left, &right).map(|element| Some(ColumnType::Array(Box::new(element))))
552 }
553 (Some(array @ ColumnType::Array(_)), _) | (_, Some(array @ ColumnType::Array(_))) => {
554 Ok(Some(array))
555 }
556 (Some(ColumnType::JsonB), Some(ColumnType::JsonB)) => Ok(Some(ColumnType::JsonB)),
557 _ => Ok(Some(ColumnType::Text)),
558 }
559}
560
561fn numeric_unary_result_type(ty: &ColumnType) -> ColumnType {
562 if matches!(base_type(ty), ColumnType::Numeric { .. }) {
563 numeric_type()
564 } else {
565 ColumnType::DoublePrecision
566 }
567}
568
569fn unary_minus_result_type(ty: &ColumnType) -> Result<ColumnType, SQLError> {
570 match base_type(ty) {
571 ty @ (ColumnType::SmallInteger
572 | ColumnType::Integer
573 | ColumnType::BigInteger
574 | ColumnType::Real
575 | ColumnType::DoublePrecision
576 | ColumnType::Numeric { .. }
577 | ColumnType::Interval) => Ok(ty.clone()),
578 other => Err(SQLError::TypeMismatch(format!(
579 "operator does not exist: - {}",
580 other.sql_name()
581 ))),
582 }
583}
584
585fn numeric_binary_function_type(
586 left: Option<ColumnType>,
587 right: Option<ColumnType>,
588) -> Result<Option<ColumnType>, SQLError> {
589 match (left, right) {
590 (Some(left), Some(right)) => common_numeric_type(base_type(&left), base_type(&right))
591 .map(Some)
592 .ok_or_else(|| {
593 SQLError::TypeMismatch(format!(
594 "types {} and {} are not numeric",
595 left.sql_name(),
596 right.sql_name()
597 ))
598 }),
599 (Some(ty), None) | (None, Some(ty)) => Ok(Some(base_type(&ty).clone())),
600 (None, None) => Ok(None),
601 }
602}
603
604fn numeric_transcendental_type(
605 args: &[ScalarExpr],
606 schema: &RowSchema,
607 params: &[SQLParam],
608 resolver: Option<&dyn FunctionTypeResolver>,
609) -> Result<Option<ColumnType>, SQLError> {
610 let mut saw_argument = false;
611 for argument in args {
612 let Some(ty) = scalar_type_inner(named_argument_value(argument), schema, params, resolver)?
613 else {
614 continue;
615 };
616 saw_argument = true;
617 if !matches!(base_type(&ty), ColumnType::Numeric { .. }) {
618 return Ok(Some(ColumnType::DoublePrecision));
619 }
620 }
621 Ok(saw_argument.then(numeric_type))
622}
623
624fn numeric_power_type(
625 args: &[ScalarExpr],
626 schema: &RowSchema,
627 params: &[SQLParam],
628 resolver: Option<&dyn FunctionTypeResolver>,
629) -> Result<Option<ColumnType>, SQLError> {
630 let mut saw_numeric = false;
631 let mut saw_floating = false;
632 for argument in args {
633 let argument = named_argument_value(argument);
634 if matches!(argument, ScalarExpr::Literal(Value::Str(_) | Value::Null)) {
635 continue;
636 }
637 let Some(ty) = scalar_type_inner(argument, schema, params, resolver)? else {
638 continue;
639 };
640 match base_type(&ty) {
641 ColumnType::Numeric { .. } => saw_numeric = true,
642 ColumnType::SmallInteger | ColumnType::Integer | ColumnType::BigInteger => {}
643 ColumnType::Real | ColumnType::DoublePrecision => saw_floating = true,
644 _ => {
645 return Err(SQLError::Routine {
646 sqlstate: "42883".into(),
647 message: "function power with these argument types does not exist".into(),
648 })
649 }
650 }
651 }
652 Ok(if saw_floating {
653 Some(ColumnType::DoublePrecision)
654 } else if saw_numeric {
655 Some(numeric_type())
656 } else if !args.is_empty() {
657 Some(ColumnType::DoublePrecision)
658 } else {
659 None
660 })
661}
662
663fn array_element_type(ty: ColumnType) -> Option<ColumnType> {
664 match ty {
665 ColumnType::Array(element) => Some(*element),
666 ColumnType::Int2Vector => Some(ColumnType::SmallInteger),
667 ColumnType::OidVector => Some(ColumnType::Oid),
668 _ => None,
669 }
670}
671
672fn binary_result_type(
673 op: BinaryOp,
674 left: Option<&ColumnType>,
675 right: Option<&ColumnType>,
676) -> Result<Option<ColumnType>, SQLError> {
677 if matches!(
678 op,
679 BinaryOp::Equal
680 | BinaryOp::NotEqual
681 | BinaryOp::Less
682 | BinaryOp::LessEqual
683 | BinaryOp::Greater
684 | BinaryOp::GreaterEqual
685 ) {
686 return Ok(Some(ColumnType::Boolean));
687 }
688 let (Some(left), Some(right)) = (left, right) else {
689 return merge_optional_types(left.cloned(), right.cloned());
690 };
691 let left = base_type(left);
692 let right = base_type(right);
693 if let Some(ty) = temporal_binary_result_type(op, left, right) {
694 return Ok(Some(ty));
695 }
696 if let Some(ty) = common_numeric_type(left, right) {
697 return Ok(Some(ty));
698 }
699 if matches!(left, ColumnType::JsonB)
700 && matches!(op, BinaryOp::Subtract)
701 && (right.is_character_string()
702 || matches!(right, ColumnType::SmallInteger | ColumnType::Integer)
703 || matches!(right, ColumnType::Array(element) if element.is_character_string()))
704 {
705 return Ok(Some(ColumnType::JsonB));
706 }
707 Err(SQLError::Routine {
708 sqlstate: "42883".into(),
709 message: format!(
710 "operator does not exist: {} {} {}",
711 left.sql_name(),
712 binary_operator_name(op),
713 right.sql_name()
714 ),
715 })
716}
717
718fn temporal_binary_result_type(
719 op: BinaryOp,
720 left: &ColumnType,
721 right: &ColumnType,
722) -> Option<ColumnType> {
723 use ColumnType as T;
724 match (left, right, op) {
725 (T::Date, T::Date, BinaryOp::Subtract) => Some(T::Integer),
726 (T::Date, T::SmallInteger | T::Integer, BinaryOp::Add | BinaryOp::Subtract)
727 | (T::SmallInteger | T::Integer, T::Date, BinaryOp::Add) => Some(T::Date),
728 (T::Date | T::Timestamp, T::Interval, BinaryOp::Add | BinaryOp::Subtract)
729 | (T::Interval, T::Date | T::Timestamp, BinaryOp::Add) => Some(T::Timestamp),
730 (T::TimestampTz, T::Interval, BinaryOp::Add | BinaryOp::Subtract)
731 | (T::Interval, T::TimestampTz, BinaryOp::Add) => Some(T::TimestampTz),
732 (T::Time, T::Interval, BinaryOp::Add | BinaryOp::Subtract)
733 | (T::Interval, T::Time, BinaryOp::Add) => Some(T::Time),
734 (T::TimeTz, T::Interval, BinaryOp::Add | BinaryOp::Subtract)
735 | (T::Interval, T::TimeTz, BinaryOp::Add) => Some(T::TimeTz),
736 (T::Interval, T::Interval, BinaryOp::Add | BinaryOp::Subtract)
737 | (T::Time, T::Time, BinaryOp::Subtract)
738 | (T::TimeTz, T::TimeTz, BinaryOp::Subtract)
739 | (
740 T::Date | T::Timestamp | T::TimestampTz,
741 T::Date | T::Timestamp | T::TimestampTz,
742 BinaryOp::Subtract,
743 ) => Some(T::Interval),
744 (T::Interval, ty, BinaryOp::Multiply | BinaryOp::Divide) if numeric_rank(ty).is_some() => {
745 Some(T::Interval)
746 }
747 (ty, T::Interval, BinaryOp::Multiply) if numeric_rank(ty).is_some() => Some(T::Interval),
748 _ => None,
749 }
750}
751
752fn binary_operator_name(op: BinaryOp) -> &'static str {
753 match op {
754 BinaryOp::Equal => "=",
755 BinaryOp::NotEqual => "<>",
756 BinaryOp::Less => "<",
757 BinaryOp::LessEqual => "<=",
758 BinaryOp::Greater => ">",
759 BinaryOp::GreaterEqual => ">=",
760 BinaryOp::Add => "+",
761 BinaryOp::Subtract => "-",
762 BinaryOp::Multiply => "*",
763 BinaryOp::Divide => "/",
764 }
765}
766
767pub fn bind_type_introspection(
773 expression: ScalarExpr,
774 schema: &RowSchema,
775 params: &[SQLParam],
776) -> ScalarExpr {
777 bind_type_introspection_inner(expression, schema, params, None)
778}
779
780pub fn bind_type_introspection_with_resolver(
782 expression: ScalarExpr,
783 schema: &RowSchema,
784 params: &[SQLParam],
785 resolver: &dyn FunctionTypeResolver,
786) -> ScalarExpr {
787 bind_type_introspection_inner(expression, schema, params, Some(resolver))
788}
789
790fn bind_type_introspection_inner(
791 expression: ScalarExpr,
792 schema: &RowSchema,
793 params: &[SQLParam],
794 resolver: Option<&dyn FunctionTypeResolver>,
795) -> ScalarExpr {
796 if !requires_type_introspection_binding(&expression) {
797 return expression;
798 }
799 match expression {
800 ScalarExpr::Func {
801 name,
802 binding,
803 mut args,
804 distinct,
805 mut order_by,
806 mut filter,
807 } => {
808 for argument in &mut args {
809 bind_type_introspection_in_place(argument, schema, params, resolver);
810 }
811 for order in &mut order_by {
812 bind_type_introspection_in_place(&mut order.expr, schema, params, resolver);
813 }
814 if let Some(filter) = filter.as_deref_mut() {
815 bind_type_introspection_in_place(filter, schema, params, resolver);
816 }
817 if containment::is_operator(&name) {
818 containment::bind_unknown_arguments(&mut args, schema, params, resolver);
819 }
820 if is_common_type_function(&name) {
821 bind_common_type_expressions(&mut args, schema, params, resolver);
822 }
823 let name =
824 to_hex::bind_overload(name, binding.as_ref(), &args, schema, params, resolver);
825 if is_pg_typeof(&name) && args.len() == 1 {
826 let name = scalar_type_inner(&args[0], schema, params, resolver)
827 .ok()
828 .flatten()
829 .map_or_else(|| "unknown".to_string(), |ty| ty.regtype_name());
830 return ScalarExpr::Cast {
831 expr: Box::new(ScalarExpr::Literal(Value::Str(name))),
832 ty: "regtype".into(),
833 };
834 }
835 ScalarExpr::Func {
836 name,
837 binding,
838 args,
839 distinct,
840 order_by,
841 filter,
842 }
843 }
844 ScalarExpr::Array(mut items) => {
845 bind_type_introspection_items(&mut items, schema, params, resolver);
846 bind_common_type_expressions(&mut items, schema, params, resolver);
847 ScalarExpr::Array(items)
848 }
849 ScalarExpr::Row(mut items) => {
850 bind_type_introspection_items(&mut items, schema, params, resolver);
851 ScalarExpr::Row(items)
852 }
853 ScalarExpr::Binary {
854 op,
855 mut lhs,
856 mut rhs,
857 } => {
858 bind_type_introspection_in_place(lhs.as_mut(), schema, params, resolver);
859 bind_type_introspection_in_place(rhs.as_mut(), schema, params, resolver);
860 ScalarExpr::Binary { op, lhs, rhs }
861 }
862 ScalarExpr::UnaryMinus(mut expr) => {
863 let source_type = scalar_type_inner(&expr, schema, params, resolver)
864 .ok()
865 .flatten()
866 .and_then(|ty| unary_minus_result_type(&ty).ok());
867 bind_type_introspection_in_place(expr.as_mut(), schema, params, resolver);
868 if let Some(source_type) = source_type {
869 let source_name = source_type.sql_name();
870 if !matches!(expr.as_ref(), ScalarExpr::Cast { ty, .. } if ty.eq_ignore_ascii_case(&source_name))
871 {
872 let inner = std::mem::replace(expr.as_mut(), ScalarExpr::Literal(Value::Null));
873 *expr = ScalarExpr::Cast {
874 expr: Box::new(inner),
875 ty: source_name,
876 };
877 }
878 }
879 ScalarExpr::UnaryMinus(expr)
880 }
881 ScalarExpr::Not(mut inner) => {
882 bind_type_introspection_in_place(inner.as_mut(), schema, params, resolver);
883 ScalarExpr::Not(inner)
884 }
885 ScalarExpr::And(mut items) => {
886 bind_type_introspection_items(&mut items, schema, params, resolver);
887 ScalarExpr::And(items)
888 }
889 ScalarExpr::Or(mut items) => {
890 bind_type_introspection_items(&mut items, schema, params, resolver);
891 ScalarExpr::Or(items)
892 }
893 ScalarExpr::IsNull { mut expr, negated } => {
894 bind_type_introspection_in_place(expr.as_mut(), schema, params, resolver);
895 ScalarExpr::IsNull { expr, negated }
896 }
897 ScalarExpr::Between {
898 mut expr,
899 mut low,
900 mut high,
901 } => {
902 bind_type_introspection_in_place(expr.as_mut(), schema, params, resolver);
903 bind_type_introspection_in_place(low.as_mut(), schema, params, resolver);
904 bind_type_introspection_in_place(high.as_mut(), schema, params, resolver);
905 ScalarExpr::Between { expr, low, high }
906 }
907 ScalarExpr::InList {
908 mut expr,
909 mut list,
910 negated,
911 } => {
912 bind_type_introspection_in_place(expr.as_mut(), schema, params, resolver);
913 bind_type_introspection_items(&mut list, schema, params, resolver);
914 ScalarExpr::InList {
915 expr,
916 list,
917 negated,
918 }
919 }
920 ScalarExpr::WindowCall {
921 name,
922 mut args,
923 mut spec,
924 } => {
925 bind_type_introspection_items(&mut args, schema, params, resolver);
926 bind_type_introspection_items(&mut spec.partition_by, schema, params, resolver);
927 for order in &mut spec.order_by {
928 bind_type_introspection_in_place(&mut order.expr, schema, params, resolver);
929 }
930 if let Some(frame) = spec.frame.as_mut() {
931 bind_frame_bound(&mut frame.start, schema, params, resolver);
932 bind_frame_bound(&mut frame.end, schema, params, resolver);
933 }
934 ScalarExpr::WindowCall { name, args, spec }
935 }
936 ScalarExpr::Case {
937 mut base,
938 mut when,
939 mut else_branch,
940 } => {
941 if let Some(base) = base.as_deref_mut() {
942 bind_type_introspection_in_place(base, schema, params, resolver);
943 }
944 for (condition, result) in &mut when {
945 bind_type_introspection_in_place(condition, schema, params, resolver);
946 bind_type_introspection_in_place(result, schema, params, resolver);
947 }
948 if let Some(else_branch) = else_branch.as_deref_mut() {
949 bind_type_introspection_in_place(else_branch, schema, params, resolver);
950 }
951 if base.is_some() {
952 let comparison_type = common_expression_type(
953 base.iter()
954 .map(Box::as_ref)
955 .chain(when.iter().map(|(condition, _)| condition)),
956 schema,
957 params,
958 resolver,
959 );
960 if let Some(comparison_type) = comparison_type {
961 if let Some(base) = base.as_deref_mut() {
962 bind_common_type_cast(base, &comparison_type, schema, params, resolver);
963 }
964 for (condition, _) in &mut when {
965 bind_common_type_cast(
966 condition,
967 &comparison_type,
968 schema,
969 params,
970 resolver,
971 );
972 }
973 }
974 }
975 let result_type = common_expression_type(
976 when.iter()
977 .map(|(_, result)| result)
978 .chain(else_branch.iter().map(Box::as_ref)),
979 schema,
980 params,
981 resolver,
982 );
983 if let Some(result_type) = result_type {
984 for (_, result) in &mut when {
985 bind_common_type_cast(result, &result_type, schema, params, resolver);
986 }
987 if let Some(else_branch) = else_branch.as_deref_mut() {
988 bind_common_type_cast(else_branch, &result_type, schema, params, resolver);
989 }
990 }
991 ScalarExpr::Case {
992 base,
993 when,
994 else_branch,
995 }
996 }
997 ScalarExpr::Cast { mut expr, ty } => {
998 let source_type = cast_requires_declared_source(&ty)
999 .then(|| {
1000 scalar_type_inner(&expr, schema, params, resolver)
1001 .ok()
1002 .flatten()
1003 })
1004 .flatten();
1005 bind_type_introspection_in_place(expr.as_mut(), schema, params, resolver);
1006 if let Some(source_type) = source_type {
1007 let source_name = source_type.sql_name();
1008 if !matches!(expr.as_ref(), ScalarExpr::Cast { ty, .. } if ty.eq_ignore_ascii_case(&source_name))
1009 {
1010 let inner = std::mem::replace(expr.as_mut(), ScalarExpr::Literal(Value::Null));
1011 *expr = ScalarExpr::Cast {
1012 expr: Box::new(inner),
1013 ty: source_name,
1014 };
1015 }
1016 }
1017 ScalarExpr::Cast { expr, ty }
1018 }
1019 ScalarExpr::InSubquery {
1020 mut expr,
1021 subquery,
1022 negated,
1023 } => {
1024 bind_type_introspection_in_place(expr.as_mut(), schema, params, resolver);
1025 ScalarExpr::InSubquery {
1026 expr,
1027 subquery,
1028 negated,
1029 }
1030 }
1031 other => other,
1032 }
1033}
1034
1035fn requires_type_introspection_binding(expression: &ScalarExpr) -> bool {
1036 match expression {
1037 ScalarExpr::Func {
1038 name,
1039 args,
1040 order_by,
1041 filter,
1042 ..
1043 } => {
1044 is_pg_typeof(name)
1045 || is_common_type_function(name)
1046 || to_hex::is_function(name)
1047 || containment::is_operator(name)
1048 || args.iter().any(requires_type_introspection_binding)
1049 || order_by
1050 .iter()
1051 .any(|order| requires_type_introspection_binding(&order.expr))
1052 || filter
1053 .as_deref()
1054 .is_some_and(requires_type_introspection_binding)
1055 }
1056 ScalarExpr::Array(_) | ScalarExpr::Case { .. } | ScalarExpr::UnaryMinus(_) => true,
1057 ScalarExpr::Row(items) | ScalarExpr::And(items) | ScalarExpr::Or(items) => {
1058 items.iter().any(requires_type_introspection_binding)
1059 }
1060 ScalarExpr::Binary { lhs, rhs, .. } => {
1061 requires_type_introspection_binding(lhs) || requires_type_introspection_binding(rhs)
1062 }
1063 ScalarExpr::Not(expression)
1064 | ScalarExpr::IsNull {
1065 expr: expression, ..
1066 } => requires_type_introspection_binding(expression),
1067 ScalarExpr::Between { expr, low, high } => {
1068 requires_type_introspection_binding(expr)
1069 || requires_type_introspection_binding(low)
1070 || requires_type_introspection_binding(high)
1071 }
1072 ScalarExpr::InList { expr, list, .. } => {
1073 requires_type_introspection_binding(expr)
1074 || list.iter().any(requires_type_introspection_binding)
1075 }
1076 ScalarExpr::WindowCall { args, spec, .. } => {
1077 args.iter().any(requires_type_introspection_binding)
1078 || spec
1079 .partition_by
1080 .iter()
1081 .any(requires_type_introspection_binding)
1082 || spec
1083 .order_by
1084 .iter()
1085 .any(|order| requires_type_introspection_binding(&order.expr))
1086 || spec.frame.as_ref().is_some_and(|frame| {
1087 frame_bound_requires_type_introspection_binding(&frame.start)
1088 || frame_bound_requires_type_introspection_binding(&frame.end)
1089 })
1090 }
1091 ScalarExpr::Cast { expr, ty } => {
1092 cast_requires_declared_source(ty) || requires_type_introspection_binding(expr)
1093 }
1094 ScalarExpr::InSubquery { expr, .. } => requires_type_introspection_binding(expr),
1095 ScalarExpr::Star
1096 | ScalarExpr::QualifiedStar(_)
1097 | ScalarExpr::Default
1098 | ScalarExpr::Column(_)
1099 | ScalarExpr::Position(_)
1100 | ScalarExpr::QualifiedColumn { .. }
1101 | ScalarExpr::Literal(_)
1102 | ScalarExpr::Param(_)
1103 | ScalarExpr::ScalarSubquery(_)
1104 | ScalarExpr::Exists { .. } => false,
1105 }
1106}
1107
1108fn frame_bound_requires_type_introspection_binding(bound: &crate::ScalarFrameBound) -> bool {
1109 match bound {
1110 crate::ScalarFrameBound::Preceding(expression)
1111 | crate::ScalarFrameBound::Following(expression) => {
1112 requires_type_introspection_binding(expression)
1113 }
1114 crate::ScalarFrameBound::UnboundedPreceding
1115 | crate::ScalarFrameBound::UnboundedFollowing
1116 | crate::ScalarFrameBound::CurrentRow => false,
1117 }
1118}
1119
1120fn is_pg_typeof(name: &str) -> bool {
1121 name.eq_ignore_ascii_case("pg_typeof") || name.eq_ignore_ascii_case("pg_catalog.pg_typeof")
1122}
1123
1124fn is_common_type_function(name: &str) -> bool {
1125 matches!(
1126 name.to_ascii_lowercase().as_str(),
1127 "coalesce" | "greatest" | "least"
1128 )
1129}
1130
1131fn bind_common_type_expressions(
1132 expressions: &mut [ScalarExpr],
1133 schema: &RowSchema,
1134 params: &[SQLParam],
1135 resolver: Option<&dyn FunctionTypeResolver>,
1136) {
1137 let Some(target) = common_expression_type(expressions.iter(), schema, params, resolver) else {
1138 return;
1139 };
1140 for expression in expressions {
1141 bind_common_type_cast(expression, &target, schema, params, resolver);
1142 }
1143}
1144
1145fn common_expression_type<'a>(
1146 expressions: impl IntoIterator<Item = &'a ScalarExpr>,
1147 schema: &RowSchema,
1148 params: &[SQLParam],
1149 resolver: Option<&dyn FunctionTypeResolver>,
1150) -> Option<ColumnType> {
1151 let mut common = None;
1152 let mut saw_expression = false;
1153 for expression in expressions {
1154 saw_expression = true;
1155 let expression_type =
1156 common_context_expression_type(expression, schema, params, resolver).ok()?;
1157 common = merge_optional_types(common, expression_type).ok()?;
1158 }
1159 saw_expression.then(|| common.unwrap_or(ColumnType::Text))
1160}
1161
1162fn bind_common_type_cast(
1163 expression: &mut ScalarExpr,
1164 target: &ColumnType,
1165 schema: &RowSchema,
1166 params: &[SQLParam],
1167 resolver: Option<&dyn FunctionTypeResolver>,
1168) {
1169 let target = base_type(target);
1170 let source = common_context_expression_type(expression, schema, params, resolver)
1171 .ok()
1172 .flatten();
1173 if source
1174 .as_ref()
1175 .is_some_and(|source| base_type(source) == target)
1176 {
1177 return;
1178 }
1179 let inner = std::mem::replace(expression, ScalarExpr::Literal(Value::Null));
1180 *expression = ScalarExpr::Cast {
1181 expr: Box::new(inner),
1182 ty: target.sql_name(),
1183 };
1184}
1185
1186fn bind_type_introspection_items(
1187 expressions: &mut [ScalarExpr],
1188 schema: &RowSchema,
1189 params: &[SQLParam],
1190 resolver: Option<&dyn FunctionTypeResolver>,
1191) {
1192 for expression in expressions {
1193 bind_type_introspection_in_place(expression, schema, params, resolver);
1194 }
1195}
1196
1197fn bind_type_introspection_in_place(
1198 expression: &mut ScalarExpr,
1199 schema: &RowSchema,
1200 params: &[SQLParam],
1201 resolver: Option<&dyn FunctionTypeResolver>,
1202) {
1203 let owned = std::mem::replace(expression, ScalarExpr::Literal(Value::Null));
1204 *expression = bind_type_introspection_inner(owned, schema, params, resolver);
1205}
1206
1207fn cast_requires_declared_source(target: &str) -> bool {
1208 let mut target = target.trim().to_ascii_lowercase();
1209 while let Some(element) = target.strip_suffix("[]") {
1210 target = element.trim_end().to_string();
1211 }
1212 matches!(
1213 target.as_str(),
1214 "bytea" | "pg_catalog.bytea" | "oid" | "pg_catalog.oid" | "xid" | "pg_catalog.xid"
1215 )
1216}
1217
1218fn bind_frame_bound(
1219 bound: &mut crate::ScalarFrameBound,
1220 schema: &RowSchema,
1221 params: &[SQLParam],
1222 resolver: Option<&dyn FunctionTypeResolver>,
1223) {
1224 match bound {
1225 crate::ScalarFrameBound::Preceding(expression)
1226 | crate::ScalarFrameBound::Following(expression) => {
1227 bind_type_introspection_in_place(expression.as_mut(), schema, params, resolver);
1228 }
1229 crate::ScalarFrameBound::UnboundedPreceding
1230 | crate::ScalarFrameBound::UnboundedFollowing
1231 | crate::ScalarFrameBound::CurrentRow => {}
1232 }
1233}
1234
1235pub fn values_column_types(
1236 rows: &[Vec<ScalarExpr>],
1237 params: &[SQLParam],
1238) -> Result<Vec<Option<ColumnType>>, SQLError> {
1239 let width = rows.first().map_or(0, Vec::len);
1240 let empty = RowSchema::default();
1241 let mut types = vec![None; width];
1242 for row in rows {
1243 if row.len() != width {
1244 return Err(SQLError::TypeMismatch(
1245 "VALUES lists must all be the same length".into(),
1246 ));
1247 }
1248 for (position, expression) in row.iter().enumerate() {
1249 types[position] = merge_optional_types(
1250 types[position].take(),
1251 common_context_expression_type(expression, &empty, params, None)?,
1252 )?;
1253 }
1254 }
1255 Ok(types
1256 .into_iter()
1257 .map(|ty| ty.or(Some(ColumnType::Text)))
1258 .collect())
1259}
1260
1261pub fn common_context_expression_type(
1263 expression: &ScalarExpr,
1264 schema: &RowSchema,
1265 params: &[SQLParam],
1266 resolver: Option<&dyn FunctionTypeResolver>,
1267) -> Result<Option<ColumnType>, SQLError> {
1268 if matches!(expression, ScalarExpr::Literal(Value::Str(_) | Value::Null)) {
1269 return Ok(None);
1270 }
1271 scalar_type_inner(expression, schema, params, resolver)
1272}
1273
1274fn parameter_type(parameter: &SQLParam) -> Option<ColumnType> {
1275 match parameter {
1276 SQLParam::Scalar(value) => value_type(value),
1277 SQLParam::Vector(values) => u32::try_from(values.len()).ok().map(ColumnType::Vector),
1278 SQLParam::Tensor(values) => values
1279 .first()
1280 .and_then(|values| u32::try_from(values.len()).ok())
1281 .map(ColumnType::Tensor),
1282 }
1283}
1284
1285fn value_type(value: &Value) -> Option<ColumnType> {
1286 match value {
1287 Value::Null | Value::Map(_) => None,
1288 Value::Row(_) | Value::Record(_) => Some(ColumnType::Record),
1289 Value::Bool(_) => Some(ColumnType::Boolean),
1290 Value::Int(value) if i32::try_from(*value).is_ok() => Some(ColumnType::Integer),
1291 Value::Int(_) => Some(ColumnType::BigInteger),
1292 Value::Float(_) => Some(ColumnType::DoublePrecision),
1293 Value::Decimal(_) => Some(ColumnType::Numeric {
1294 precision: None,
1295 scale: None,
1296 }),
1297 Value::Str(_) => Some(ColumnType::Text),
1298 Value::FixedChar(value) => u32::try_from(value.chars().count())
1299 .ok()
1300 .map(ColumnType::Character),
1301 Value::Bytes(_) => Some(ColumnType::Bytea),
1302 Value::Temporal(value) => Some(match value {
1303 uqa_core::TemporalValue::Date { .. } => ColumnType::Date,
1304 uqa_core::TemporalValue::Time { .. } => ColumnType::Time,
1305 uqa_core::TemporalValue::TimeTz { .. } => ColumnType::TimeTz,
1306 uqa_core::TemporalValue::Timestamp { .. } => ColumnType::Timestamp,
1307 uqa_core::TemporalValue::TimestampTz { .. } => ColumnType::TimestampTz,
1308 uqa_core::TemporalValue::Interval { .. } => ColumnType::Interval,
1309 }),
1310 Value::Json(_) => Some(ColumnType::Json),
1311 Value::JsonB(_) => Some(ColumnType::JsonB),
1312 Value::Array(array) => {
1313 let mut element = None;
1314 merge_array_element_types(array.elements(), &mut element)?;
1315 element.map(|element| ColumnType::Array(Box::new(element)))
1316 }
1317 Value::List(values) => {
1318 let mut element = None;
1319 for value in values {
1320 element = merge_optional_types(element, value_type(value)).ok()?;
1321 }
1322 element.map(|element| ColumnType::Array(Box::new(element)))
1323 }
1324 }
1325}
1326
1327fn merge_array_element_types(values: &[Value], element: &mut Option<ColumnType>) -> Option<()> {
1328 for value in values {
1329 if let Value::List(nested) = value {
1330 merge_array_element_types(nested, element)?;
1331 } else {
1332 *element = merge_optional_types(element.take(), value_type(value)).ok()?;
1333 }
1334 }
1335 Some(())
1336}
1337
1338fn merge_optional_types(
1339 left: Option<ColumnType>,
1340 right: Option<ColumnType>,
1341) -> Result<Option<ColumnType>, SQLError> {
1342 match (left, right) {
1343 (None, other) | (other, None) => Ok(other),
1344 (Some(left), Some(right)) => common_type(&left, &right).map(Some),
1345 }
1346}
1347
1348pub fn common_type(left: &ColumnType, right: &ColumnType) -> Result<ColumnType, SQLError> {
1349 if left == right {
1350 return Ok(left.clone());
1351 }
1352 if matches!(left, ColumnType::Domain { .. }) || matches!(right, ColumnType::Domain { .. }) {
1353 return common_type(base_type(left), base_type(right));
1354 }
1355 if let Some(numeric) = common_numeric_type(left, right) {
1356 return Ok(numeric);
1357 }
1358 if matches!(left, ColumnType::Oid) && is_integral_type(right)
1359 || matches!(right, ColumnType::Oid) && is_integral_type(left)
1360 {
1361 return Ok(ColumnType::Oid);
1362 }
1363 if left.is_character_string() && right.is_character_string() {
1364 return Ok(match left {
1365 ColumnType::Bpchar | ColumnType::Character(_) => ColumnType::Bpchar,
1366 ColumnType::Varchar(_) => ColumnType::Varchar(None),
1367 ColumnType::Name => ColumnType::Name,
1368 _ => ColumnType::Text,
1369 });
1370 }
1371 match (left, right) {
1372 (ColumnType::Date, ColumnType::Timestamp) | (ColumnType::Timestamp, ColumnType::Date) => {
1373 Ok(ColumnType::Timestamp)
1374 }
1375 (ColumnType::Date | ColumnType::Timestamp, ColumnType::TimestampTz)
1376 | (ColumnType::TimestampTz, ColumnType::Date | ColumnType::Timestamp) => {
1377 Ok(ColumnType::TimestampTz)
1378 }
1379 (ColumnType::Array(left), ColumnType::Array(right)) => {
1380 common_type(left, right).map(|element| ColumnType::Array(Box::new(element)))
1381 }
1382 _ => Err(SQLError::TypeMismatch(format!(
1383 "types {} and {} cannot be matched",
1384 left.sql_name(),
1385 right.sql_name()
1386 ))),
1387 }
1388}
1389
1390fn is_integral_type(ty: &ColumnType) -> bool {
1391 matches!(
1392 base_type(ty),
1393 ColumnType::SmallInteger | ColumnType::Integer | ColumnType::BigInteger
1394 )
1395}
1396
1397fn common_numeric_type(left: &ColumnType, right: &ColumnType) -> Option<ColumnType> {
1398 let rank = numeric_rank(left)?.max(numeric_rank(right)?);
1399 Some(match rank {
1400 0 => ColumnType::SmallInteger,
1401 1 => ColumnType::Integer,
1402 2 => ColumnType::BigInteger,
1403 3 => ColumnType::Numeric {
1404 precision: None,
1405 scale: None,
1406 },
1407 4 => ColumnType::Real,
1408 _ => ColumnType::DoublePrecision,
1409 })
1410}
1411
1412fn numeric_rank(ty: &ColumnType) -> Option<u8> {
1413 match ty {
1414 ColumnType::SmallInteger => Some(0),
1415 ColumnType::Integer => Some(1),
1416 ColumnType::BigInteger => Some(2),
1417 ColumnType::Numeric { .. } => Some(3),
1418 ColumnType::Real => Some(4),
1419 ColumnType::DoublePrecision => Some(5),
1420 _ => None,
1421 }
1422}
1423
1424pub fn equality_operand_type(
1425 left: &ColumnType,
1426 right: &ColumnType,
1427) -> Result<ColumnType, SQLError> {
1428 if matches!(left, ColumnType::Json) || matches!(right, ColumnType::Json) {
1429 return Err(undefined_equality_operator(left, right));
1430 }
1431 if matches!(left, ColumnType::Array(_)) || matches!(right, ColumnType::Array(_)) {
1432 if left == right {
1433 return Ok(left.clone());
1434 }
1435 return Err(undefined_equality_operator(left, right));
1436 }
1437 if left.is_character_string() && right.is_character_string() {
1438 if matches!(left, ColumnType::Bpchar | ColumnType::Character(_))
1439 || matches!(right, ColumnType::Bpchar | ColumnType::Character(_))
1440 {
1441 return Ok(ColumnType::Bpchar);
1442 }
1443 return Ok(ColumnType::Text);
1444 }
1445 common_type(left, right).map_err(|_| undefined_equality_operator(left, right))
1446}
1447
1448fn undefined_equality_operator(left: &ColumnType, right: &ColumnType) -> SQLError {
1449 SQLError::Routine {
1450 sqlstate: "42883".into(),
1451 message: format!(
1452 "operator does not exist: {} = {}",
1453 left.sql_name(),
1454 right.sql_name()
1455 ),
1456 }
1457}
1458
1459#[cfg(test)]
1460mod tests;