Skip to main content

uqa_sql/type_resolution/
common.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use crate::ast::ColumnType;
8use crate::{SQLError, SQLParam};
9use uqa_core::{
10    memory::{Produced, ProductionControl},
11    Value,
12};
13
14use crate::schema::ScalarTypeSchema;
15use crate::{scalar_call_arguments, RowSchema, ScalarExpr};
16
17use super::FunctionTypeResolver;
18
19/// Decoded function-call argument names, effective overload types, and whether the call used explicit `VARIADIC` syntax.
20#[doc(hidden)]
21pub type FunctionCallArgumentSignature = (Vec<Option<String>>, Vec<Option<ColumnType>>, bool);
22
23/// Build the PostgreSQL-compatible overload signature for one physical function call using the shared common-context typing rule.
24#[doc(hidden)]
25pub fn function_call_argument_signature(
26    arguments: &[ScalarExpr],
27    schema: &dyn ScalarTypeSchema,
28    params: &[SQLParam],
29    resolver: Option<&dyn FunctionTypeResolver>,
30) -> Result<FunctionCallArgumentSignature, SQLError> {
31    let call_arguments = scalar_call_arguments(arguments)?;
32    let explicit_variadic = call_arguments
33        .iter()
34        .any(|argument| argument.explicit_variadic);
35    let mut argument_names = Vec::with_capacity(call_arguments.len());
36    let mut argument_types = Vec::with_capacity(call_arguments.len());
37    for argument in call_arguments {
38        argument_names.push(argument.name.map(str::to_string));
39        let argument_type =
40            common_context_expression_type(argument.value, schema, params, resolver)?;
41        argument_types.push(effective_overload_argument_type_with_params(
42            argument.value,
43            argument_type,
44            params,
45        ));
46    }
47    Ok((argument_names, argument_types, explicit_variadic))
48}
49
50pub(super) fn local_routine_name(name: &str) -> String {
51    let lower = name.to_ascii_lowercase();
52    lower
53        .strip_prefix("pg_catalog.")
54        .unwrap_or(&lower)
55        .to_string()
56}
57
58pub(super) fn numeric_type() -> ColumnType {
59    ColumnType::Numeric {
60        precision: None,
61        scale: None,
62    }
63}
64
65pub(super) fn base_type(mut ty: &ColumnType) -> &ColumnType {
66    while let ColumnType::Domain { base, .. } = ty {
67        ty = base;
68    }
69    ty.without_temporal_modifiers()
70}
71
72/// `coerce_type` reads a scalar domain through its base input, but array input keeps each element's domain checks. Input functions receive interval modifiers; other modifiers remain on the outer coercion.
73pub(crate) fn literal_input_type_with_control(
74    mut ty: &ColumnType,
75    control: &ProductionControl<'_>,
76) -> Result<Produced<ColumnType>, SQLError> {
77    fn input_type(
78        ty: &ColumnType,
79        control: &ProductionControl<'_>,
80    ) -> Result<Produced<ColumnType>, SQLError> {
81        Ok(match ty {
82            ColumnType::IntervalWithFields { .. } | ColumnType::Domain { .. } => {
83                ty.clone_with_control(control)?
84            }
85            ColumnType::Array(element) => {
86                ColumnType::array_with_control(input_type(element, control)?, control)?
87            }
88            _ => ty.without_type_modifiers_with_control(control)?,
89        })
90    }
91    while let ColumnType::Domain { base, .. } = ty {
92        ty = base;
93    }
94    input_type(ty, control)
95}
96
97pub(crate) fn array_element_type(ty: &ColumnType) -> Option<&ColumnType> {
98    match base_type(ty) {
99        ColumnType::Array(element) => {
100            let mut element = element.as_ref();
101            while let ColumnType::Array(inner) = element {
102                element = inner;
103            }
104            Some(element)
105        }
106        ColumnType::Int2Vector => Some(&ColumnType::SmallInteger),
107        ColumnType::OidVector => Some(&ColumnType::Oid),
108        _ => None,
109    }
110}
111
112pub fn values_column_types(
113    rows: &[Vec<ScalarExpr>],
114    params: &[SQLParam],
115) -> Result<Vec<Option<ColumnType>>, SQLError> {
116    let width = rows.first().map_or(0, Vec::len);
117    let empty = RowSchema::default();
118    let mut types = vec![None; width];
119    for row in rows {
120        if row.len() != width {
121            return Err(SQLError::TypeMismatch(
122                "VALUES lists must all be the same length".into(),
123            ));
124        }
125        for (position, expression) in row.iter().enumerate() {
126            types[position] = merge_optional_types_in(
127                CommonTypeContext::Values,
128                types[position].take(),
129                common_context_expression_type(expression, &empty, params, None)?,
130            )?;
131        }
132    }
133    Ok(types
134        .into_iter()
135        .map(|ty| ty.or(Some(ColumnType::Text)))
136        .collect())
137}
138
139/// Resolve an expression participating in `PostgreSQL`'s common-type selection. Bare string and NULL literals retain the parser's `unknown` type until the surrounding VALUES, set operation, CASE, or array context selects a concrete type.
140pub fn common_context_expression_type(
141    expression: &ScalarExpr,
142    schema: &dyn ScalarTypeSchema,
143    params: &[SQLParam],
144    resolver: Option<&dyn FunctionTypeResolver>,
145) -> Result<Option<ColumnType>, SQLError> {
146    common_context_expression_type_with_control(
147        expression,
148        schema,
149        params,
150        resolver,
151        &ProductionControl::uncontrolled(),
152    )
153    .map(|ty| {
154        ty.map(|ty| {
155            ty.into_uncontrolled()
156                .expect("ordinary common-context inference has no reservation")
157        })
158    })
159}
160
161pub(super) fn common_context_expression_type_with_control(
162    expression: &ScalarExpr,
163    schema: &dyn ScalarTypeSchema,
164    params: &[SQLParam],
165    resolver: Option<&dyn FunctionTypeResolver>,
166    control: &ProductionControl<'_>,
167) -> Result<Option<Produced<ColumnType>>, SQLError> {
168    control.check()?;
169    if matches!(expression, ScalarExpr::Literal(Value::Str(_) | Value::Null)) {
170        return Ok(None);
171    }
172    super::scalar_type_inner_with_control(expression, schema, params, resolver, control)
173}
174
175/// Preserve parser-level `unknown` identity for fixed built-in overload selection.
176#[doc(hidden)]
177pub fn effective_overload_argument_type(
178    expression: &ScalarExpr,
179    resolved: Option<ColumnType>,
180) -> Option<ColumnType> {
181    if effective_overload_argument_type_ref_with_params(expression, resolved.as_ref(), &[])
182        .is_some()
183    {
184        resolved
185    } else {
186        None
187    }
188}
189
190/// Preserve an explicitly typed scalar parameter while retaining the legacy `unknown` treatment of untyped text-valued [`SQLParam::Scalar`] parameters.
191#[doc(hidden)]
192pub fn effective_overload_argument_type_with_params(
193    expression: &ScalarExpr,
194    resolved: Option<ColumnType>,
195    params: &[SQLParam],
196) -> Option<ColumnType> {
197    if effective_overload_argument_type_ref_with_params(expression, resolved.as_ref(), params)
198        .is_some()
199    {
200        resolved
201    } else {
202        None
203    }
204}
205
206/// Borrow the same effective argument type before a controlled caller decides whether a payload copy is needed.
207pub(super) fn effective_overload_argument_type_ref_with_params<'a>(
208    expression: &ScalarExpr,
209    resolved: Option<&'a ColumnType>,
210    params: &[SQLParam],
211) -> Option<&'a ColumnType> {
212    if let ScalarExpr::Param(index) = expression {
213        if index
214            .checked_sub(1)
215            .and_then(|index| params.get(index))
216            .is_some_and(|parameter| parameter.declared_scalar_type().is_some())
217        {
218            return resolved;
219        }
220    }
221    if matches!(expression, ScalarExpr::Literal(Value::Str(_) | Value::Null))
222        || matches!(expression, ScalarExpr::Param(_)) && matches!(resolved, Some(ColumnType::Text))
223    {
224        None
225    } else {
226        resolved
227    }
228}
229
230pub(super) fn parameter_type_with_control(
231    parameter: &SQLParam,
232    control: &ProductionControl<'_>,
233) -> Result<Option<Produced<ColumnType>>, SQLError> {
234    control.check()?;
235    let scalar = match parameter {
236        SQLParam::Scalar(value) => return value_type_with_control(value, control),
237        SQLParam::TypedScalar { ty, .. } | SQLParam::DeferredError { ty, .. } => {
238            return Ok(Some(ty.clone_with_control(control)?))
239        }
240        SQLParam::Vector(values) => u32::try_from(values.len()).ok().map(ColumnType::Vector),
241        SQLParam::Tensor(values) => values
242            .first()
243            .and_then(|values| u32::try_from(values.len()).ok())
244            .map(ColumnType::Tensor),
245    };
246    scalar
247        .map(|ty| {
248            control
249                .finish(ty, control.empty_reservation())
250                .map_err(Into::into)
251        })
252        .transpose()
253}
254
255pub(crate) fn value_type(value: &Value) -> Option<ColumnType> {
256    value_type_with_control(value, &ProductionControl::uncontrolled())
257        .expect("ordinary value type inference cannot be cancelled or limited")
258        .map(|value| {
259            value
260                .into_uncontrolled()
261                .expect("ordinary value type has no reservation")
262        })
263}
264
265pub(crate) fn value_type_with_control(
266    value: &Value,
267    control: &ProductionControl<'_>,
268) -> Result<Option<Produced<ColumnType>>, SQLError> {
269    control.check()?;
270    let scalar = match value {
271        // A runtime enum carrier knows only its type OID; declared expression types supply the enum's identity.
272        Value::Null | Value::Map(_) | Value::Enum(_) => None,
273        Value::Void => Some(ColumnType::Void),
274        Value::Row(_) | Value::Record(_) => Some(ColumnType::Record),
275        Value::Bool(_) => Some(ColumnType::Boolean),
276        Value::Int(value) if i32::try_from(*value).is_ok() => Some(ColumnType::Integer),
277        Value::Int(_) => Some(ColumnType::BigInteger),
278        Value::Float(_) => Some(ColumnType::DoublePrecision),
279        Value::Decimal(_) => Some(numeric_type()),
280        Value::Str(_) => Some(ColumnType::Text),
281        Value::FixedChar(value) => {
282            let mut count = 0_usize;
283            for _ in value.chars() {
284                control.check()?;
285                count += 1;
286            }
287            u32::try_from(count).ok().map(ColumnType::Character)
288        }
289        Value::Bytes(_) => Some(ColumnType::Bytea),
290        Value::Temporal(value) => Some(match value {
291            uqa_core::TemporalValue::Date { .. } => ColumnType::Date,
292            uqa_core::TemporalValue::Time { .. } => ColumnType::Time,
293            uqa_core::TemporalValue::TimeTz { .. } => ColumnType::TimeTz,
294            uqa_core::TemporalValue::Timestamp { .. } => ColumnType::Timestamp,
295            uqa_core::TemporalValue::TimestampTz { .. } => ColumnType::TimestampTz,
296            uqa_core::TemporalValue::Interval { .. } => ColumnType::Interval,
297        }),
298        Value::Json(_) => Some(ColumnType::Json),
299        Value::JsonB(_) => Some(ColumnType::JsonB),
300        Value::LegacyVector(vector) => Some(match vector.kind() {
301            uqa_core::LegacyVectorKind::SmallInteger => ColumnType::Int2Vector,
302            uqa_core::LegacyVectorKind::Oid => ColumnType::OidVector,
303        }),
304        Value::Array(array) => {
305            let mut element = None;
306            if !merge_array_element_types(array.elements(), &mut element, control)? {
307                return Ok(None);
308            }
309            return element
310                .map(|element| ColumnType::array_with_control(element, control).map_err(Into::into))
311                .transpose();
312        }
313        Value::List(values) => {
314            let mut element = None;
315            for value in values {
316                let next = value_type_with_control(value, control)?;
317                match merge_value_types(element, next, control) {
318                    Ok(merged) => element = merged,
319                    Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => {
320                        return Err(error)
321                    }
322                    Err(_) => return Ok(None),
323                }
324            }
325            return element
326                .map(|element| ColumnType::array_with_control(element, control).map_err(Into::into))
327                .transpose();
328        }
329    };
330    scalar
331        .map(|ty| {
332            control
333                .finish(ty, control.empty_reservation())
334                .map_err(Into::into)
335        })
336        .transpose()
337}
338
339fn merge_array_element_types(
340    values: &[Value],
341    element: &mut Option<Produced<ColumnType>>,
342    control: &ProductionControl<'_>,
343) -> Result<bool, SQLError> {
344    for value in values {
345        control.check()?;
346        if let Value::List(nested) = value {
347            if !merge_array_element_types(nested, element, control)? {
348                return Ok(false);
349            }
350        } else {
351            match merge_value_types(
352                element.take(),
353                value_type_with_control(value, control)?,
354                control,
355            ) {
356                Ok(merged) => *element = merged,
357                Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => {
358                    return Err(error)
359                }
360                Err(_) => return Ok(false),
361            }
362        }
363    }
364    Ok(true)
365}
366
367pub(super) fn merge_value_types(
368    left: Option<Produced<ColumnType>>,
369    right: Option<Produced<ColumnType>>,
370    control: &ProductionControl<'_>,
371) -> Result<Option<Produced<ColumnType>>, SQLError> {
372    control.check()?;
373    match (left, right) {
374        (None, other) | (other, None) => Ok(other),
375        (Some(left), Some(right)) if *left == *right => Ok(Some(left)),
376        (Some(left), Some(right)) => common_type_with_control(&left, &right, control).map(Some),
377    }
378}
379
380/// `coerce_to_common_type` reads an `unknown` string constant with the selected type's input function, which reports what the type rejects, before the statement runs. A type whose input function consults the catalog is read when the expression is bound to it.
381pub(super) fn read_unknown_literals<'a>(
382    expressions: impl IntoIterator<Item = &'a ScalarExpr>,
383    target: &ColumnType,
384) -> Result<(), SQLError> {
385    if super::catalog_input_type(target) {
386        return Ok(());
387    }
388    // `coerce_type` hands the literal to the target type's input function, so the diagnostic is the input function's.
389    let target = base_type(target).without_type_modifiers().catalog_name();
390    for expression in expressions {
391        if let ScalarExpr::Literal(Value::Str(text)) = expression {
392            crate::expr::cast_value_from(&Value::Str(text.clone()), &target, None)?;
393        }
394    }
395    Ok(())
396}
397
398/// The common type of two optional column types for the construct `context`, an absent type being `unknown`.
399pub(super) fn merge_optional_types_in(
400    context: CommonTypeContext,
401    left: Option<ColumnType>,
402    right: Option<ColumnType>,
403) -> Result<Option<ColumnType>, SQLError> {
404    match (left, right) {
405        (None, other) | (other, None) => Ok(other),
406        (Some(left), Some(right)) => common_type_in(context, &left, &right).map(Some),
407    }
408}
409
410/// [`merge_value_types`] reporting a conflict as the construct `context` reports it.
411pub(super) fn merge_value_types_in(
412    context: CommonTypeContext,
413    left: Option<Produced<ColumnType>>,
414    right: Option<Produced<ColumnType>>,
415    control: &ProductionControl<'_>,
416) -> Result<Option<Produced<ColumnType>>, SQLError> {
417    control.check()?;
418    match (left, right) {
419        (None, other) | (other, None) => Ok(other),
420        (Some(left), Some(right)) if *left == *right => Ok(Some(left)),
421        (Some(left), Some(right)) => select_pair_with_control(&left, &right, control)
422            .map(Some)
423            .map_err(|failure| failure.in_context(context)),
424    }
425}
426
427pub fn common_type(left: &ColumnType, right: &ColumnType) -> Result<ColumnType, SQLError> {
428    common_type_with_control(left, right, &ProductionControl::uncontrolled()).map(|value| {
429        value
430            .into_uncontrolled()
431            .expect("ordinary common type has no reservation")
432    })
433}
434
435/// The common type of `left` and `right` for the construct `context`, which reports a conflict as `select_common_type` and `coerce_to_common_type` report it: `42804` when the types are of different categories and `42846` when the other type has no implicit cast to the selected one.
436pub fn common_type_in(
437    context: CommonTypeContext,
438    left: &ColumnType,
439    right: &ColumnType,
440) -> Result<ColumnType, SQLError> {
441    select_pair_with_control(left, right, &ProductionControl::uncontrolled())
442        .map(|value| {
443            value
444                .into_uncontrolled()
445                .expect("ordinary common type has no reservation")
446        })
447        .map_err(|failure| failure.in_context(context))
448}
449
450/// The construct selecting a common type, as `select_common_type` and `coerce_to_common_type` name it in their diagnostics.
451#[derive(Clone, Copy, Debug, PartialEq, Eq)]
452pub enum CommonTypeContext {
453    Union,
454    Intersect,
455    Except,
456    Values,
457    Case,
458    Coalesce,
459    Array,
460    Greatest,
461    Least,
462    In,
463    JoinUsing,
464    Cycle,
465}
466
467impl CommonTypeContext {
468    pub const fn label(self) -> &'static str {
469        match self {
470            Self::Union => "UNION",
471            Self::Intersect => "INTERSECT",
472            Self::Except => "EXCEPT",
473            Self::Values => "VALUES",
474            Self::Case => "CASE",
475            Self::Coalesce => "COALESCE",
476            Self::Array => "ARRAY",
477            Self::Greatest => "GREATEST",
478            Self::Least => "LEAST",
479            Self::In => "IN",
480            Self::JoinUsing => "JOIN/USING",
481            Self::Cycle => "CYCLE",
482        }
483    }
484
485    /// The name `coerce_to_common_type` reports: `transformCaseExpr` coerces the results in a `CASE/WHEN` context.
486    pub const fn coercion_label(self) -> &'static str {
487        match self {
488            Self::Case => "CASE/WHEN",
489            other => other.label(),
490        }
491    }
492
493    pub const fn set_operation(kind: crate::ast::SetOpKind) -> Self {
494        match kind {
495            crate::ast::SetOpKind::Union => Self::Union,
496            crate::ast::SetOpKind::Intersect => Self::Intersect,
497            crate::ast::SetOpKind::Except => Self::Except,
498        }
499    }
500
501    /// The context of a function whose arguments share a common type.
502    pub fn function(name: &str) -> Option<Self> {
503        match local_routine_name(name).as_str() {
504            "coalesce" => Some(Self::Coalesce),
505            "greatest" => Some(Self::Greatest),
506            "least" => Some(Self::Least),
507            _ => None,
508        }
509    }
510}
511
512/// Why two types have no common type: `select_common_type` finds them in different categories, or `coerce_to_common_type` finds no implicit cast from the other type to the selected one. `Failed` carries an error the selection itself met.
513#[derive(Debug)]
514pub(super) enum CommonTypeFailure {
515    /// The two types, in the order they met.
516    Unmatched(Box<(ColumnType, ColumnType)>),
517    /// The type without an implicit cast, and the selected type it would convert to.
518    Inconvertible(Box<(ColumnType, ColumnType)>),
519    Failed(Box<SQLError>),
520}
521
522impl CommonTypeFailure {
523    fn failed(error: impl Into<SQLError>) -> Self {
524        Self::Failed(Box::new(error.into()))
525    }
526
527    /// The diagnostic the construct `context` reports.
528    fn in_context(self, context: CommonTypeContext) -> SQLError {
529        match self {
530            Self::Unmatched(types) => SQLError::Routine {
531                sqlstate: "42804".into(),
532                message: format!(
533                    "{} types {} and {} cannot be matched",
534                    context.label(),
535                    types.0.sql_name(),
536                    types.1.sql_name()
537                ),
538            },
539            Self::Inconvertible(types) => SQLError::Routine {
540                sqlstate: "42846".into(),
541                message: format!(
542                    "{} could not convert type {} to {}",
543                    context.coercion_label(),
544                    types.0.sql_name(),
545                    types.1.sql_name()
546                ),
547            },
548            Self::Failed(error) => *error,
549        }
550    }
551
552    /// The diagnostic of a selection without a construct: a type mismatch naming the two types.
553    fn without_context(self, left: &ColumnType, right: &ColumnType) -> SQLError {
554        match self {
555            Self::Failed(error) => *error,
556            Self::Unmatched(_) | Self::Inconvertible(_) => SQLError::TypeMismatch(format!(
557                "types {} and {} cannot be matched",
558                left.sql_name(),
559                right.sql_name()
560            )),
561        }
562    }
563}
564
565/// `select_common_input_type_with_control` without production limits.
566pub fn select_common_input_type(
567    types: &[Option<&ColumnType>],
568) -> Result<Option<ColumnType>, SQLError> {
569    select_common_input_type_with_control(types, &ProductionControl::uncontrolled()).map(
570        |selected| {
571            selected.map(|value| {
572                value
573                    .into_uncontrolled()
574                    .expect("ordinary common type has no reservation")
575            })
576        },
577    )
578}
579
580/// `select_common_type` over typed and `unknown` (`None`) inputs: only inputs of exactly one type keep that type, which is how a domain survives; otherwise domains are reduced to their base types before the pairwise rules. `unknown` inputs alone resolve to `text`, and `None` means the known types have no common type.
581pub(super) fn select_common_input_type_with_control(
582    types: &[Option<&ColumnType>],
583    control: &ProductionControl<'_>,
584) -> Result<Option<Produced<ColumnType>>, SQLError> {
585    control.check()?;
586    if let Some(Some(first)) = types.first() {
587        if types.iter().all(|ty| ty.is_some_and(|ty| ty == *first)) {
588            return first
589                .clone_with_control(control)
590                .map(Some)
591                .map_err(Into::into);
592        }
593    }
594    let mut selected: Option<Produced<ColumnType>> = None;
595    for ty in types.iter().flatten() {
596        let ty = base_type(ty);
597        selected = Some(match selected {
598            None => ty.clone_with_control(control)?,
599            Some(current) => match common_type_with_control(&current, ty, control) {
600                Ok(common) => common,
601                Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => {
602                    return Err(error)
603                }
604                Err(_) => return Ok(None),
605            },
606        });
607    }
608    match selected {
609        Some(selected) => Ok(Some(selected)),
610        None => control
611            .finish(ColumnType::Text, control.empty_reservation())
612            .map(Some)
613            .map_err(Into::into),
614    }
615}
616
617/// Preserve the existing common-type rules while the selected type owns its copied names and array boxes; a conflict is a type mismatch naming the two types.
618pub(super) fn common_type_with_control(
619    left: &ColumnType,
620    right: &ColumnType,
621    control: &ProductionControl<'_>,
622) -> Result<Produced<ColumnType>, SQLError> {
623    select_pair_with_control(left, right, control)
624        .map_err(|failure| failure.without_context(left, right))
625}
626
627/// `select_common_type` over two types: equal types, temporal modifiers and domains reduce first, then the numeric, OID, character, temporal and array rules, and then the category rule.
628fn select_pair_with_control(
629    left: &ColumnType,
630    right: &ColumnType,
631    control: &ProductionControl<'_>,
632) -> Result<Produced<ColumnType>, CommonTypeFailure> {
633    control.check().map_err(CommonTypeFailure::failed)?;
634    if left == right {
635        return left
636            .clone_with_control(control)
637            .map_err(CommonTypeFailure::failed);
638    }
639    if left != left.without_temporal_modifiers() || right != right.without_temporal_modifiers() {
640        return select_pair_with_control(
641            left.without_temporal_modifiers(),
642            right.without_temporal_modifiers(),
643            control,
644        );
645    }
646    if matches!(left, ColumnType::Domain { .. }) || matches!(right, ColumnType::Domain { .. }) {
647        return select_pair_with_control(base_type(left), base_type(right), control);
648    }
649    let scalar = if let Some(numeric) = common_numeric_type(left, right) {
650        numeric
651    } else if matches!(left, ColumnType::Oid) && is_integral_type(right)
652        || matches!(right, ColumnType::Oid) && is_integral_type(left)
653    {
654        ColumnType::Oid
655    } else if left.is_character_string() && right.is_character_string() {
656        match left {
657            ColumnType::Bpchar | ColumnType::Character(_) => ColumnType::Bpchar,
658            ColumnType::Varchar(_) => ColumnType::Varchar(None),
659            ColumnType::Name => ColumnType::Name,
660            _ => ColumnType::Text,
661        }
662    } else {
663        match (left, right) {
664            (ColumnType::Date, ColumnType::Timestamp)
665            | (ColumnType::Timestamp, ColumnType::Date) => ColumnType::Timestamp,
666            (ColumnType::Date | ColumnType::Timestamp, ColumnType::TimestampTz)
667            | (ColumnType::TimestampTz, ColumnType::Date | ColumnType::Timestamp) => {
668                ColumnType::TimestampTz
669            }
670            (ColumnType::Array(_), ColumnType::Array(_)) => {
671                // An array type does not fix its number of dimensions: `integer[][]` is `integer[]`, so arrays meet at their element types, and each value keeps its own dimensions.
672                let element = select_pair_with_control(
673                    innermost_array_element(left),
674                    innermost_array_element(right),
675                    control,
676                )?;
677                return ColumnType::array_with_control(element, control)
678                    .map_err(CommonTypeFailure::failed);
679            }
680            _ => same_category_common_type(left, right)?,
681        }
682    };
683    control
684        .finish(scalar, control.empty_reservation())
685        .map_err(CommonTypeFailure::failed)
686}
687
688/// `select_common_type` for two types of one category that the rules above do not cover: the first type stays unless it is not its category's preferred type and coerces implicitly to the second, which does not coerce back to it. The other type must then coerce implicitly to the selected one, as `coerce_to_common_type` requires.
689fn same_category_common_type(
690    left: &ColumnType,
691    right: &ColumnType,
692) -> Result<ColumnType, CommonTypeFailure> {
693    use super::overload_resolution::{
694        routine_type_accepts_implicit_cast as implicit, routine_type_category,
695        routine_type_is_preferred,
696    };
697    let left_name = super::canonical_column_type_name(left);
698    let right_name = super::canonical_column_type_name(right);
699    if routine_type_category(&left_name) != routine_type_category(&right_name) {
700        return Err(CommonTypeFailure::Unmatched(Box::new((
701            left.clone(),
702            right.clone(),
703        ))));
704    }
705    let switch = !routine_type_is_preferred(&left_name)
706        && implicit(&left_name, &right_name)
707        && !implicit(&right_name, &left_name);
708    let (selected, other, selected_name, other_name) = if switch {
709        (right, left, &right_name, &left_name)
710    } else {
711        (left, right, &left_name, &right_name)
712    };
713    if implicit(other_name, selected_name) {
714        Ok(selected.clone())
715    } else {
716        Err(CommonTypeFailure::Inconvertible(Box::new((
717            other.clone(),
718            selected.clone(),
719        ))))
720    }
721}
722
723pub(super) mod case;
724
725/// The element type below every array level of `ty`.
726fn innermost_array_element(mut ty: &ColumnType) -> &ColumnType {
727    while let ColumnType::Array(element) = ty {
728        ty = element;
729    }
730    ty
731}
732
733fn is_integral_type(ty: &ColumnType) -> bool {
734    matches!(
735        base_type(ty),
736        ColumnType::SmallInteger | ColumnType::Integer | ColumnType::BigInteger
737    )
738}
739
740pub(super) fn common_numeric_type(left: &ColumnType, right: &ColumnType) -> Option<ColumnType> {
741    let rank = numeric_rank(left)?.max(numeric_rank(right)?);
742    Some(match rank {
743        0 => ColumnType::SmallInteger,
744        1 => ColumnType::Integer,
745        2 => ColumnType::BigInteger,
746        3 => numeric_type(),
747        4 => ColumnType::Real,
748        _ => ColumnType::DoublePrecision,
749    })
750}
751
752pub(super) fn numeric_rank(ty: &ColumnType) -> Option<u8> {
753    match ty {
754        ColumnType::SmallInteger => Some(0),
755        ColumnType::Integer => Some(1),
756        ColumnType::BigInteger => Some(2),
757        ColumnType::Numeric { .. } => Some(3),
758        ColumnType::Real => Some(4),
759        ColumnType::DoublePrecision => Some(5),
760        _ => None,
761    }
762}
763
764/// Array dimensions belong to values; `PostgreSQL` operator signatures identify an array by its scalar element type, including an element domain's identity.
765pub(super) fn same_operator_type_with_control(
766    left: &ColumnType,
767    right: &ColumnType,
768    control: &ProductionControl<'_>,
769) -> Result<bool, uqa_core::ValueRetentionError> {
770    fn element(mut ty: &ColumnType) -> &ColumnType {
771        while let ColumnType::Array(inner) = ty {
772            ty = inner;
773        }
774        ty
775    }
776    let left = base_type(left);
777    let right = base_type(right);
778    let (left, right) = match (left, right) {
779        (ColumnType::Array(left), ColumnType::Array(right)) => (element(left), element(right)),
780        _ => (left, right),
781    };
782    let left = left.without_type_modifiers_with_control(control)?;
783    let right = right.without_type_modifiers_with_control(control)?;
784    Ok(*left == *right)
785}
786
787#[cfg(test)]
788mod production_tests;