rupg-analyze 0.1.84

Name resolution, type resolution, the query tree, the rewriter for views and rules.
Documentation
//! `assign_collations_walker` of `parse_collate.c` for the columns of a query: the collation of each expression of the result.
//!
//! The analyzer does not keep a collation on each expression, so this module finds it again from the columns of the relations. A column and a constant have their own collation, a node with a result of a collatable type takes the collation of its inputs, and a node with no collatable input takes the collation of its type. A collation that is not the default wins over the default, and two different collations that are not the default give a conflict. The analyzer drops `COLLATE`, so an explicit collation does not count.

use rupg_catalog::Catalog;

use crate::expr::{Expr, ExprKind, SubLinkKind};
use crate::select::Query;
use crate::types;

/// The OID of the collation `default`.
pub(crate) const DEFAULT_COLLATION: u32 = 100;

/// The collation state of an expression, as `assign_collations_context` without the explicit strength.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum State {
    /// The expression has no collation, because its type is not collatable.
    None,
    /// An implicit collation.
    Implicit(u32),
    /// Two different implicit collations that are not the default.
    Conflict,
}

impl State {
    /// `merge_collation_state` for two implicit states.
    fn merge(self, other: State) -> State {
        match (self, other) {
            (State::Conflict, _) | (_, State::Conflict) => State::Conflict,
            (State::None, s) | (s, State::None) => s,
            (State::Implicit(a), State::Implicit(b)) if a == b => self,
            (State::Implicit(DEFAULT_COLLATION), s) | (s, State::Implicit(DEFAULT_COLLATION)) => s,
            _ => State::Conflict,
        }
    }

    /// `exprCollation` after the walker: 0 for no collation and for a conflict.
    fn oid(self) -> u32 {
        match self {
            State::Implicit(oid) => oid,
            State::None | State::Conflict => 0,
        }
    }
}

/// The collation of an expression of the result of `query`, or 0 for a type that is not collatable and for a conflict. `catalog` gives the collations of the columns of the user tables.
pub(crate) fn target_collation(expr: &Expr, query: &Query, catalog: Option<&Catalog>) -> u32 {
    state(expr, &[query], catalog).oid()
}

/// `get_typcollation`.
fn type_collation(ty: u32) -> u32 {
    types::row(ty).map_or(0, |t| t.collation)
}

/// The collation state of an expression. `levels` holds the query of the expression first, then the queries outside it.
fn state(expr: &Expr, levels: &[&Query], catalog: Option<&Catalog>) -> State {
    let own = type_collation(expr.ty);
    if own == 0 {
        return State::None;
    }
    let inputs: Vec<&Expr> = match &expr.kind {
        ExprKind::Var(var) => {
            return match column_collation(*var, levels, catalog) {
                0 => State::None,
                oid => State::Implicit(oid),
            };
        }
        ExprKind::SubLink(sub) => {
            let column = match sub.kind {
                SubLinkKind::Expr | SubLinkKind::Array => 0,
                SubLinkKind::Multi { column, .. } => column,
                _ => return State::None,
            };
            let mut inner = vec![&sub.query];
            inner.extend_from_slice(levels);
            let target = sub.query.targets.iter().filter(|t| !t.junk).nth(column);
            return State::Implicit(target.map_or(0, |t| state(&t.expr, &inner, catalog).oid()));
        }
        ExprKind::Case(case) => {
            case.whens.iter().map(|(_, then)| then).chain([&*case.default]).collect()
        }
        // `assign_ordered_set_collations`: the items of `WITHIN GROUP` count only when there can be one item.
        ExprKind::Agg(agg) if agg.kind != b'n' => {
            let variadic = rupg_pgcatalog::builtin::proc_by_oid(agg.oid).map_or(0, |p| p.variadic);
            let one = agg.args.len() == 1 && variadic == 0;
            agg.direct.iter().chain(agg.args.iter().filter(|_| one)).collect()
        }
        ExprKind::Agg(agg) => agg.args.iter().take(agg.nargs).collect(),
        // A domain with a collation that is not the default gives its own collation. Any other domain takes the collation of its input.
        ExprKind::CoerceToDomain(..) if own != DEFAULT_COLLATION => {
            return State::Implicit(own);
        }
        _ => expr.children(),
    };
    let merged =
        inputs.into_iter().fold(State::None, |acc, e| acc.merge(state(e, levels, catalog)));
    if merged == State::None { State::Implicit(own) } else { merged }
}

/// The collation of a column of a relation of a query in `levels`.
fn column_collation(var: crate::expr::Var, levels: &[&Query], catalog: Option<&Catalog>) -> u32 {
    let Some(query) = levels.get(var.levels_up) else { return 0 };
    let Some(relation) = query.relations.get(var.relation) else { return 0 };
    let Ok(index) = usize::try_from(var.attnum - 1) else { return 0 };
    if relation.oid != 0 {
        if let Some(rel) = catalog.and_then(|c| c.relation(relation.oid)) {
            return rel.column(var.attnum).map_or(0, |c| c.collation);
        }
        return rupg_pgcatalog::catalog_by_oid(relation.oid)
            .and_then(|c| c.columns.get(index))
            .map_or(0, |c| c.collation);
    }
    let outer = &levels[var.levels_up..];
    if let Some(sub) = &relation.subquery {
        let mut inner = vec![&**sub];
        inner.extend_from_slice(outer);
        let target = sub.targets.iter().filter(|t| !t.junk).nth(index);
        return target.map_or(0, |t| state(&t.expr, &inner, catalog).oid());
    }
    if let Some(rows) = &relation.values {
        let merged = rows
            .iter()
            .filter_map(|row| row.get(index))
            .fold(State::None, |acc, e| acc.merge(state(e, outer, catalog)));
        return merged.oid();
    }
    relation.columns.get(index).map_or(0, |c| type_collation(c.ty))
}