rupg-analyze 0.1.84

Name resolution, type resolution, the query tree, the rewriter for views and rules.
Documentation
//! `transformSubLink` of `parse_expr.c`: a subquery in an expression, as `EXISTS`, `ANY`, `ALL`, `IN`, `ARRAY` or a scalar subquery.

use std::cmp::Ordering;

use rupg_common::{Error, Result, SqlState};
use rupg_sql::nodes::{Node, SubLink as RawSubLink, SubLinkType};
use rupg_types::oid;

use crate::Analyzer;
use crate::agg::Kind;
use crate::coerce::AtOpt;
use crate::expr::{Expr, ExprKind, SubLink, SubLinkKind};
use crate::select::Query;
use crate::typename::{names, place};
use crate::types;

/// An error for a subquery that the analyzer does not take yet.
fn not_yet(what: &str, at: Option<usize>) -> Error {
    Error::new(SqlState::FEATURE_NOT_SUPPORTED, format!("{what} is not supported yet")).at_opt(at)
}

impl Analyzer<'_> {
    /// `parse_sub_analyze`: the query tree of a subquery. The subquery sees the names of this query and of the queries outside it.
    pub(crate) fn sub_analyze(&mut self, node: Option<&Node>) -> Result<Query> {
        let Some(Node::SelectStmt(stmt)) = node else {
            return Err(Error::internal("a subquery that is not SelectStmt"));
        };
        self.sub_query(|analyzer| analyzer.select(stmt))
    }

    /// [`Analyzer::sub_analyze`] for the analysis `f` of a statement below this query, which sees the names of this query.
    pub(crate) fn sub_query(
        &mut self,
        f: impl FnOnce(&mut Self) -> Result<Query>,
    ) -> Result<Query> {
        let scope = std::mem::take(&mut self.scope);
        self.outer.push(scope);
        let kind = std::mem::replace(&mut self.kind, Kind::Other);
        let has_aggs = std::mem::replace(&mut self.has_aggs, false);
        self.outer_kinds.push((Some(kind), has_aggs));
        let srfs = self.srfs;
        let result = f(self);
        self.srfs = srfs;
        // An aggregate of this query in the subquery sets `has_aggs` of this query.
        self.has_aggs = self.outer_kinds.pop().map_or(has_aggs, |(_, has_aggs)| has_aggs);
        self.kind = kind;
        self.scope = self.outer.pop().unwrap_or_default();
        result
    }

    /// `transformSubLink`.
    pub(crate) fn transform_sublink(&mut self, s: &RawSubLink) -> Result<Expr> {
        let at = place(s.location);
        if self.kind.is_standalone() {
            return Err(Error::new(
                SqlState::FEATURE_NOT_SUPPORTED,
                format!("cannot use subquery in {}", self.kind.subquery_name()),
            )
            .at_opt(at));
        }
        let kind = match s.subLinkType {
            SubLinkType::EXISTS_SUBLINK => SubLinkKind::Exists,
            SubLinkType::ALL_SUBLINK => SubLinkKind::All,
            SubLinkType::ANY_SUBLINK => SubLinkKind::Any,
            SubLinkType::EXPR_SUBLINK => SubLinkKind::Expr,
            SubLinkType::ARRAY_SUBLINK => SubLinkKind::Array,
            SubLinkType::ROWCOMPARE_SUBLINK => SubLinkKind::RowCompare,
            _ => return Err(not_yet("this kind of subquery", at)),
        };
        let query = self.sub_analyze(s.subselect.as_ref())?;
        let columns: Vec<(u32, i32)> =
            query.targets.iter().filter(|t| !t.junk).map(|t| (t.expr.ty, t.expr.typmod)).collect();
        let (ty, typmod, test) = match kind {
            SubLinkKind::Exists => (oid::BOOL, -1, None),
            SubLinkKind::Multi { .. } => {
                return Err(Error::internal("a multiple-column subquery outside of SET"));
            }
            SubLinkKind::Expr | SubLinkKind::Array => {
                let [(ty, typmod)] = columns[..] else {
                    return Err(Error::new(
                        SqlState::SYNTAX_ERROR,
                        "subquery must return only one column",
                    )
                    .at_opt(at));
                };
                if kind == SubLinkKind::Expr {
                    (ty, typmod, None)
                } else {
                    // `get_promoted_array_type`: an array column with no array type of its own gives arrays of one more dimension.
                    let array = match types::array_of(ty) {
                        0 if types::element(ty) != 0 => ty,
                        array => array,
                    };
                    if array == 0 {
                        return Err(Error::new(
                            SqlState::UNDEFINED_OBJECT,
                            format!("could not find array type for data type {}", types::name(ty)),
                        ));
                    }
                    (array, typmod, None)
                }
            }
            SubLinkKind::Any | SubLinkKind::All | SubLinkKind::RowCompare => {
                let left = match &s.testexpr {
                    Some(Node::RowExpr(r)) => self.transform_expression_list(&r.args)?,
                    node => vec![self.transform(node.as_ref())?],
                };
                let op_names = if s.operName.is_empty() { vec!["="] } else { names(&s.operName) };
                let message = match left.len().cmp(&columns.len()) {
                    Ordering::Less => Some("subquery has too many columns"),
                    Ordering::Greater => Some("subquery has too few columns"),
                    Ordering::Equal => None,
                };
                if let Some(message) = message {
                    return Err(Error::new(SqlState::SYNTAX_ERROR, message).at_opt(at));
                }
                let right = columns
                    .iter()
                    .enumerate()
                    .map(|(n, &(ty, typmod))| Expr {
                        kind: ExprKind::SubColumn(n),
                        ty,
                        typmod,
                        location: None,
                    })
                    .collect();
                let test = self.row_comparison(&op_names, left, right, at)?;
                (oid::BOOL, -1, Some(test))
            }
        };
        let sub = SubLink { kind, test, query, alternative: None };
        Ok(Expr { kind: ExprKind::SubLink(Box::new(sub)), ty, typmod, location: at })
    }
}