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, .. } => return Ok(Some(ty.clone_with_control(control)?)),
238        SQLParam::Vector(values) => u32::try_from(values.len()).ok().map(ColumnType::Vector),
239        SQLParam::Tensor(values) => values
240            .first()
241            .and_then(|values| u32::try_from(values.len()).ok())
242            .map(ColumnType::Tensor),
243    };
244    scalar
245        .map(|ty| {
246            control
247                .finish(ty, control.empty_reservation())
248                .map_err(Into::into)
249        })
250        .transpose()
251}
252
253pub(crate) fn value_type(value: &Value) -> Option<ColumnType> {
254    value_type_with_control(value, &ProductionControl::uncontrolled())
255        .expect("ordinary value type inference cannot be cancelled or limited")
256        .map(|value| {
257            value
258                .into_uncontrolled()
259                .expect("ordinary value type has no reservation")
260        })
261}
262
263pub(crate) fn value_type_with_control(
264    value: &Value,
265    control: &ProductionControl<'_>,
266) -> Result<Option<Produced<ColumnType>>, SQLError> {
267    control.check()?;
268    let scalar = match value {
269        // A runtime enum carrier knows only its type OID; declared expression types supply the enum's identity.
270        Value::Null | Value::Map(_) | Value::Enum(_) => None,
271        Value::Void => Some(ColumnType::Void),
272        Value::Row(_) | Value::Record(_) => Some(ColumnType::Record),
273        Value::Bool(_) => Some(ColumnType::Boolean),
274        Value::Int(value) if i32::try_from(*value).is_ok() => Some(ColumnType::Integer),
275        Value::Int(_) => Some(ColumnType::BigInteger),
276        Value::Float(_) => Some(ColumnType::DoublePrecision),
277        Value::Decimal(_) => Some(numeric_type()),
278        Value::Str(_) => Some(ColumnType::Text),
279        Value::FixedChar(value) => {
280            let mut count = 0_usize;
281            for _ in value.chars() {
282                control.check()?;
283                count += 1;
284            }
285            u32::try_from(count).ok().map(ColumnType::Character)
286        }
287        Value::Bytes(_) => Some(ColumnType::Bytea),
288        Value::Temporal(value) => Some(match value {
289            uqa_core::TemporalValue::Date { .. } => ColumnType::Date,
290            uqa_core::TemporalValue::Time { .. } => ColumnType::Time,
291            uqa_core::TemporalValue::TimeTz { .. } => ColumnType::TimeTz,
292            uqa_core::TemporalValue::Timestamp { .. } => ColumnType::Timestamp,
293            uqa_core::TemporalValue::TimestampTz { .. } => ColumnType::TimestampTz,
294            uqa_core::TemporalValue::Interval { .. } => ColumnType::Interval,
295        }),
296        Value::Json(_) => Some(ColumnType::Json),
297        Value::JsonB(_) => Some(ColumnType::JsonB),
298        Value::LegacyVector(vector) => Some(match vector.kind() {
299            uqa_core::LegacyVectorKind::SmallInteger => ColumnType::Int2Vector,
300            uqa_core::LegacyVectorKind::Oid => ColumnType::OidVector,
301        }),
302        Value::Array(array) => {
303            let mut element = None;
304            if !merge_array_element_types(array.elements(), &mut element, control)? {
305                return Ok(None);
306            }
307            return element
308                .map(|element| ColumnType::array_with_control(element, control).map_err(Into::into))
309                .transpose();
310        }
311        Value::List(values) => {
312            let mut element = None;
313            for value in values {
314                let next = value_type_with_control(value, control)?;
315                match merge_value_types(element, next, control) {
316                    Ok(merged) => element = merged,
317                    Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => {
318                        return Err(error)
319                    }
320                    Err(_) => return Ok(None),
321                }
322            }
323            return element
324                .map(|element| ColumnType::array_with_control(element, control).map_err(Into::into))
325                .transpose();
326        }
327    };
328    scalar
329        .map(|ty| {
330            control
331                .finish(ty, control.empty_reservation())
332                .map_err(Into::into)
333        })
334        .transpose()
335}
336
337fn merge_array_element_types(
338    values: &[Value],
339    element: &mut Option<Produced<ColumnType>>,
340    control: &ProductionControl<'_>,
341) -> Result<bool, SQLError> {
342    for value in values {
343        control.check()?;
344        if let Value::List(nested) = value {
345            if !merge_array_element_types(nested, element, control)? {
346                return Ok(false);
347            }
348        } else {
349            match merge_value_types(
350                element.take(),
351                value_type_with_control(value, control)?,
352                control,
353            ) {
354                Ok(merged) => *element = merged,
355                Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => {
356                    return Err(error)
357                }
358                Err(_) => return Ok(false),
359            }
360        }
361    }
362    Ok(true)
363}
364
365pub(super) fn merge_value_types(
366    left: Option<Produced<ColumnType>>,
367    right: Option<Produced<ColumnType>>,
368    control: &ProductionControl<'_>,
369) -> Result<Option<Produced<ColumnType>>, SQLError> {
370    control.check()?;
371    match (left, right) {
372        (None, other) | (other, None) => Ok(other),
373        (Some(left), Some(right)) if *left == *right => Ok(Some(left)),
374        (Some(left), Some(right)) => common_type_with_control(&left, &right, control).map(Some),
375    }
376}
377
378/// `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.
379pub(super) fn read_unknown_literals<'a>(
380    expressions: impl IntoIterator<Item = &'a ScalarExpr>,
381    target: &ColumnType,
382) -> Result<(), SQLError> {
383    if super::catalog_input_type(target) {
384        return Ok(());
385    }
386    // `coerce_type` hands the literal to the target type's input function, so the diagnostic is the input function's.
387    let target = base_type(target).without_type_modifiers().catalog_name();
388    for expression in expressions {
389        if let ScalarExpr::Literal(Value::Str(text)) = expression {
390            crate::expr::cast_value_from(&Value::Str(text.clone()), &target, None)?;
391        }
392    }
393    Ok(())
394}
395
396/// The common type of two optional column types for the construct `context`, an absent type being `unknown`.
397pub(super) fn merge_optional_types_in(
398    context: CommonTypeContext,
399    left: Option<ColumnType>,
400    right: Option<ColumnType>,
401) -> Result<Option<ColumnType>, SQLError> {
402    match (left, right) {
403        (None, other) | (other, None) => Ok(other),
404        (Some(left), Some(right)) => common_type_in(context, &left, &right).map(Some),
405    }
406}
407
408/// [`merge_value_types`] reporting a conflict as the construct `context` reports it.
409pub(super) fn merge_value_types_in(
410    context: CommonTypeContext,
411    left: Option<Produced<ColumnType>>,
412    right: Option<Produced<ColumnType>>,
413    control: &ProductionControl<'_>,
414) -> Result<Option<Produced<ColumnType>>, SQLError> {
415    control.check()?;
416    match (left, right) {
417        (None, other) | (other, None) => Ok(other),
418        (Some(left), Some(right)) if *left == *right => Ok(Some(left)),
419        (Some(left), Some(right)) => select_pair_with_control(&left, &right, control)
420            .map(Some)
421            .map_err(|failure| failure.in_context(context)),
422    }
423}
424
425pub fn common_type(left: &ColumnType, right: &ColumnType) -> Result<ColumnType, SQLError> {
426    common_type_with_control(left, right, &ProductionControl::uncontrolled()).map(|value| {
427        value
428            .into_uncontrolled()
429            .expect("ordinary common type has no reservation")
430    })
431}
432
433/// 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.
434pub fn common_type_in(
435    context: CommonTypeContext,
436    left: &ColumnType,
437    right: &ColumnType,
438) -> Result<ColumnType, SQLError> {
439    select_pair_with_control(left, right, &ProductionControl::uncontrolled())
440        .map(|value| {
441            value
442                .into_uncontrolled()
443                .expect("ordinary common type has no reservation")
444        })
445        .map_err(|failure| failure.in_context(context))
446}
447
448/// The construct selecting a common type, as `select_common_type` and `coerce_to_common_type` name it in their diagnostics.
449#[derive(Clone, Copy, Debug, PartialEq, Eq)]
450pub enum CommonTypeContext {
451    Union,
452    Intersect,
453    Except,
454    Values,
455    Case,
456    Coalesce,
457    Array,
458    Greatest,
459    Least,
460    In,
461    JoinUsing,
462    Cycle,
463}
464
465impl CommonTypeContext {
466    pub const fn label(self) -> &'static str {
467        match self {
468            Self::Union => "UNION",
469            Self::Intersect => "INTERSECT",
470            Self::Except => "EXCEPT",
471            Self::Values => "VALUES",
472            Self::Case => "CASE",
473            Self::Coalesce => "COALESCE",
474            Self::Array => "ARRAY",
475            Self::Greatest => "GREATEST",
476            Self::Least => "LEAST",
477            Self::In => "IN",
478            Self::JoinUsing => "JOIN/USING",
479            Self::Cycle => "CYCLE",
480        }
481    }
482
483    /// The name `coerce_to_common_type` reports: `transformCaseExpr` coerces the results in a `CASE/WHEN` context.
484    pub const fn coercion_label(self) -> &'static str {
485        match self {
486            Self::Case => "CASE/WHEN",
487            other => other.label(),
488        }
489    }
490
491    pub const fn set_operation(kind: crate::ast::SetOpKind) -> Self {
492        match kind {
493            crate::ast::SetOpKind::Union => Self::Union,
494            crate::ast::SetOpKind::Intersect => Self::Intersect,
495            crate::ast::SetOpKind::Except => Self::Except,
496        }
497    }
498
499    /// The context of a function whose arguments share a common type.
500    pub fn function(name: &str) -> Option<Self> {
501        match local_routine_name(name).as_str() {
502            "coalesce" => Some(Self::Coalesce),
503            "greatest" => Some(Self::Greatest),
504            "least" => Some(Self::Least),
505            _ => None,
506        }
507    }
508}
509
510/// 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.
511#[derive(Debug)]
512pub(super) enum CommonTypeFailure {
513    /// The two types, in the order they met.
514    Unmatched(Box<(ColumnType, ColumnType)>),
515    /// The type without an implicit cast, and the selected type it would convert to.
516    Inconvertible(Box<(ColumnType, ColumnType)>),
517    Failed(Box<SQLError>),
518}
519
520impl CommonTypeFailure {
521    fn failed(error: impl Into<SQLError>) -> Self {
522        Self::Failed(Box::new(error.into()))
523    }
524
525    /// The diagnostic the construct `context` reports.
526    fn in_context(self, context: CommonTypeContext) -> SQLError {
527        match self {
528            Self::Unmatched(types) => SQLError::Routine {
529                sqlstate: "42804".into(),
530                message: format!(
531                    "{} types {} and {} cannot be matched",
532                    context.label(),
533                    types.0.sql_name(),
534                    types.1.sql_name()
535                ),
536            },
537            Self::Inconvertible(types) => SQLError::Routine {
538                sqlstate: "42846".into(),
539                message: format!(
540                    "{} could not convert type {} to {}",
541                    context.coercion_label(),
542                    types.0.sql_name(),
543                    types.1.sql_name()
544                ),
545            },
546            Self::Failed(error) => *error,
547        }
548    }
549
550    /// The diagnostic of a selection without a construct: a type mismatch naming the two types.
551    fn without_context(self, left: &ColumnType, right: &ColumnType) -> SQLError {
552        match self {
553            Self::Failed(error) => *error,
554            Self::Unmatched(_) | Self::Inconvertible(_) => SQLError::TypeMismatch(format!(
555                "types {} and {} cannot be matched",
556                left.sql_name(),
557                right.sql_name()
558            )),
559        }
560    }
561}
562
563/// `select_common_input_type_with_control` without production limits.
564pub fn select_common_input_type(
565    types: &[Option<&ColumnType>],
566) -> Result<Option<ColumnType>, SQLError> {
567    select_common_input_type_with_control(types, &ProductionControl::uncontrolled()).map(
568        |selected| {
569            selected.map(|value| {
570                value
571                    .into_uncontrolled()
572                    .expect("ordinary common type has no reservation")
573            })
574        },
575    )
576}
577
578/// `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.
579pub(super) fn select_common_input_type_with_control(
580    types: &[Option<&ColumnType>],
581    control: &ProductionControl<'_>,
582) -> Result<Option<Produced<ColumnType>>, SQLError> {
583    control.check()?;
584    if let Some(Some(first)) = types.first() {
585        if types.iter().all(|ty| ty.is_some_and(|ty| ty == *first)) {
586            return first
587                .clone_with_control(control)
588                .map(Some)
589                .map_err(Into::into);
590        }
591    }
592    let mut selected: Option<Produced<ColumnType>> = None;
593    for ty in types.iter().flatten() {
594        let ty = base_type(ty);
595        selected = Some(match selected {
596            None => ty.clone_with_control(control)?,
597            Some(current) => match common_type_with_control(&current, ty, control) {
598                Ok(common) => common,
599                Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => {
600                    return Err(error)
601                }
602                Err(_) => return Ok(None),
603            },
604        });
605    }
606    match selected {
607        Some(selected) => Ok(Some(selected)),
608        None => control
609            .finish(ColumnType::Text, control.empty_reservation())
610            .map(Some)
611            .map_err(Into::into),
612    }
613}
614
615/// 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.
616pub(super) fn common_type_with_control(
617    left: &ColumnType,
618    right: &ColumnType,
619    control: &ProductionControl<'_>,
620) -> Result<Produced<ColumnType>, SQLError> {
621    select_pair_with_control(left, right, control)
622        .map_err(|failure| failure.without_context(left, right))
623}
624
625/// `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.
626fn select_pair_with_control(
627    left: &ColumnType,
628    right: &ColumnType,
629    control: &ProductionControl<'_>,
630) -> Result<Produced<ColumnType>, CommonTypeFailure> {
631    control.check().map_err(CommonTypeFailure::failed)?;
632    if left == right {
633        return left
634            .clone_with_control(control)
635            .map_err(CommonTypeFailure::failed);
636    }
637    if left != left.without_temporal_modifiers() || right != right.without_temporal_modifiers() {
638        return select_pair_with_control(
639            left.without_temporal_modifiers(),
640            right.without_temporal_modifiers(),
641            control,
642        );
643    }
644    if matches!(left, ColumnType::Domain { .. }) || matches!(right, ColumnType::Domain { .. }) {
645        return select_pair_with_control(base_type(left), base_type(right), control);
646    }
647    let scalar = if let Some(numeric) = common_numeric_type(left, right) {
648        numeric
649    } else if matches!(left, ColumnType::Oid) && is_integral_type(right)
650        || matches!(right, ColumnType::Oid) && is_integral_type(left)
651    {
652        ColumnType::Oid
653    } else if left.is_character_string() && right.is_character_string() {
654        match left {
655            ColumnType::Bpchar | ColumnType::Character(_) => ColumnType::Bpchar,
656            ColumnType::Varchar(_) => ColumnType::Varchar(None),
657            ColumnType::Name => ColumnType::Name,
658            _ => ColumnType::Text,
659        }
660    } else {
661        match (left, right) {
662            (ColumnType::Date, ColumnType::Timestamp)
663            | (ColumnType::Timestamp, ColumnType::Date) => ColumnType::Timestamp,
664            (ColumnType::Date | ColumnType::Timestamp, ColumnType::TimestampTz)
665            | (ColumnType::TimestampTz, ColumnType::Date | ColumnType::Timestamp) => {
666                ColumnType::TimestampTz
667            }
668            (ColumnType::Array(_), ColumnType::Array(_)) => {
669                // 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.
670                let element = select_pair_with_control(
671                    innermost_array_element(left),
672                    innermost_array_element(right),
673                    control,
674                )?;
675                return ColumnType::array_with_control(element, control)
676                    .map_err(CommonTypeFailure::failed);
677            }
678            _ => same_category_common_type(left, right)?,
679        }
680    };
681    control
682        .finish(scalar, control.empty_reservation())
683        .map_err(CommonTypeFailure::failed)
684}
685
686/// `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.
687fn same_category_common_type(
688    left: &ColumnType,
689    right: &ColumnType,
690) -> Result<ColumnType, CommonTypeFailure> {
691    use super::overload_resolution::{
692        routine_type_accepts_implicit_cast as implicit, routine_type_category,
693        routine_type_is_preferred,
694    };
695    let left_name = super::canonical_column_type_name(left);
696    let right_name = super::canonical_column_type_name(right);
697    if routine_type_category(&left_name) != routine_type_category(&right_name) {
698        return Err(CommonTypeFailure::Unmatched(Box::new((
699            left.clone(),
700            right.clone(),
701        ))));
702    }
703    let switch = !routine_type_is_preferred(&left_name)
704        && implicit(&left_name, &right_name)
705        && !implicit(&right_name, &left_name);
706    let (selected, other, selected_name, other_name) = if switch {
707        (right, left, &right_name, &left_name)
708    } else {
709        (left, right, &left_name, &right_name)
710    };
711    if implicit(other_name, selected_name) {
712        Ok(selected.clone())
713    } else {
714        Err(CommonTypeFailure::Inconvertible(Box::new((
715            other.clone(),
716            selected.clone(),
717        ))))
718    }
719}
720
721pub(super) mod case;
722
723/// The element type below every array level of `ty`.
724fn innermost_array_element(mut ty: &ColumnType) -> &ColumnType {
725    while let ColumnType::Array(element) = ty {
726        ty = element;
727    }
728    ty
729}
730
731fn is_integral_type(ty: &ColumnType) -> bool {
732    matches!(
733        base_type(ty),
734        ColumnType::SmallInteger | ColumnType::Integer | ColumnType::BigInteger
735    )
736}
737
738pub(super) fn common_numeric_type(left: &ColumnType, right: &ColumnType) -> Option<ColumnType> {
739    let rank = numeric_rank(left)?.max(numeric_rank(right)?);
740    Some(match rank {
741        0 => ColumnType::SmallInteger,
742        1 => ColumnType::Integer,
743        2 => ColumnType::BigInteger,
744        3 => numeric_type(),
745        4 => ColumnType::Real,
746        _ => ColumnType::DoublePrecision,
747    })
748}
749
750pub(super) fn numeric_rank(ty: &ColumnType) -> Option<u8> {
751    match ty {
752        ColumnType::SmallInteger => Some(0),
753        ColumnType::Integer => Some(1),
754        ColumnType::BigInteger => Some(2),
755        ColumnType::Numeric { .. } => Some(3),
756        ColumnType::Real => Some(4),
757        ColumnType::DoublePrecision => Some(5),
758        _ => None,
759    }
760}
761
762/// Array dimensions belong to values; `PostgreSQL` operator signatures identify an array by its scalar element type, including an element domain's identity.
763pub(super) fn same_operator_type_with_control(
764    left: &ColumnType,
765    right: &ColumnType,
766    control: &ProductionControl<'_>,
767) -> Result<bool, uqa_core::ValueRetentionError> {
768    fn element(mut ty: &ColumnType) -> &ColumnType {
769        while let ColumnType::Array(inner) = ty {
770            ty = inner;
771        }
772        ty
773    }
774    let left = base_type(left);
775    let right = base_type(right);
776    let (left, right) = match (left, right) {
777        (ColumnType::Array(left), ColumnType::Array(right)) => (element(left), element(right)),
778        _ => (left, right),
779    };
780    let left = left.without_type_modifiers_with_control(control)?;
781    let right = right.without_type_modifiers_with_control(control)?;
782    Ok(*left == *right)
783}
784
785#[cfg(test)]
786mod production_tests;