safe-migrate 0.8.0

Check PostgreSQL migrations against a synchronized database baseline
Documentation
use crate::_internal::analysis::expr_ir::ExprIr;
use squawk_syntax::ast::{AstNode, Expr};

pub struct ExprVisitor;

impl ExprVisitor {
    pub fn convert(expr: Expr) -> ExprIr {
        match expr {
            Expr::Literal(lit) => Self::convert_literal(lit),
            Expr::NameRef(nr) => Self::convert_name_ref(nr),
            Expr::CallExpr(ce) => Self::convert_call_expr(ce),
            Expr::BinExpr(be) => Self::convert_bin_expr(be),
            Expr::CastExpr(ce) => Self::convert_cast_expr(ce),
            Expr::PrefixExpr(pe) => pe
                .expr()
                .map(Self::convert)
                .unwrap_or(ExprIr::Sentinel("<prefix>".into())),
            Expr::ParenExpr(pe) => pe
                .expr()
                .map(Self::convert)
                .unwrap_or(ExprIr::Sentinel("<paren>".into())),
            Expr::CaseExpr(ce) => Self::convert_case_expr(ce),
            Expr::ArrayExpr(ae) => Self::convert_array_expr(ae),
            Expr::BetweenExpr(be) => Self::convert_between_expr(be),
            Expr::IndexExpr(ie) => Self::convert_index_expr(ie),
            Expr::SliceExpr(se) => Self::convert_slice_expr(se),
            Expr::FieldExpr(fe) => Self::convert_field_expr(fe),
            Expr::PostfixExpr(pe) => Self::convert_postfix_expr(pe),
            Expr::Collate(ce) => {
                let left = ce
                    .expr()
                    .map(Self::convert)
                    .unwrap_or(ExprIr::Sentinel("<lhs>".into()));
                let right = ce
                    .collation_ref()
                    .map(|c| c.syntax().text().to_string())
                    .unwrap_or_else(|| "<collation>".into());
                ExprIr::BinaryOp {
                    left: Box::new(left),
                    op: "COLLATE".to_string(),
                    right: Box::new(ExprIr::Literal(right)),
                }
            }
            _ => ExprIr::Sentinel("<complex>".into()),
        }
    }

    fn convert_literal(lit: squawk_syntax::ast::Literal) -> ExprIr {
        ExprIr::Literal(lit.syntax().text().to_string())
    }

    fn convert_name_ref(nr: squawk_syntax::ast::NameRef) -> ExprIr {
        let name = nr.text().to_string();
        ExprIr::ColumnRef(name)
    }

    fn convert_call_expr(ce: squawk_syntax::ast::CallExpr) -> ExprIr {
        let name = ce
            .expr()
            .map(|e| match e {
                Expr::NameRef(nr) => nr.text().to_string(),
                other => other.syntax().text().to_string(),
            })
            .unwrap_or_else(|| "<fn>".into());

        let args = ce
            .arg_list()
            .map(|al| {
                al.args()
                    .filter_map(|arg| arg.expr())
                    .map(Self::convert)
                    .collect()
            })
            .unwrap_or_default();

        ExprIr::FunctionCall { name, args }
    }

    fn convert_bin_expr(be: squawk_syntax::ast::BinExpr) -> ExprIr {
        let left = be
            .lhs()
            .map(Self::convert)
            .unwrap_or(ExprIr::Sentinel("<lhs>".into()));
        let right = be
            .rhs()
            .map(Self::convert)
            .unwrap_or(ExprIr::Sentinel("<rhs>".into()));

        use squawk_syntax::ast::BinOp;
        let op = be
            .op()
            .map(|o| match o {
                BinOp::And(t) => t.text().to_string(),
                BinOp::Caret(t) => t.text().to_string(),
                BinOp::ColonEq(t) => t.text().to_string(),
                BinOp::Eq(t) => t.text().to_string(),
                BinOp::FatArrow(t) => t.text().to_string(),
                BinOp::Gteq(t) => t.text().to_string(),
                BinOp::Ilike(t) => t.text().to_string(),
                BinOp::In(t) => t.text().to_string(),
                BinOp::Is(t) => t.text().to_string(),
                BinOp::LAngle(t) => t.text().to_string(),
                BinOp::Like(t) => t.text().to_string(),
                BinOp::Lteq(t) => t.text().to_string(),
                BinOp::Minus(t) => t.text().to_string(),
                BinOp::Neq(t) => t.text().to_string(),
                BinOp::Neqb(t) => t.text().to_string(),
                BinOp::Or(t) => t.text().to_string(),
                BinOp::Overlaps(t) => t.text().to_string(),
                BinOp::Percent(t) => t.text().to_string(),
                BinOp::Plus(t) => t.text().to_string(),
                BinOp::RAngle(t) => t.text().to_string(),
                BinOp::Slash(t) => t.text().to_string(),
                BinOp::Star(t) => t.text().to_string(),
                BinOp::AtTimeZone(n) => n.syntax().text().to_string(),
                BinOp::ColonColon(n) => n.syntax().text().to_string(),
                BinOp::CustomOp(n) => n.syntax().text().to_string(),
                BinOp::IsDistinctFrom(n) => n.syntax().text().to_string(),
                BinOp::IsNot(n) => n.syntax().text().to_string(),
                BinOp::IsNotDistinctFrom(n) => n.syntax().text().to_string(),
                BinOp::NotIlike(n) => n.syntax().text().to_string(),
                BinOp::NotIn(n) => n.syntax().text().to_string(),
                BinOp::NotLike(n) => n.syntax().text().to_string(),
                BinOp::NotSimilarTo(n) => n.syntax().text().to_string(),
                BinOp::OperatorCall(n) => n.syntax().text().to_string(),
                BinOp::SimilarTo(n) => n.syntax().text().to_string(),
                BinOp::Escape(t) => t.text().to_string(),
            })
            .unwrap_or_else(|| "<op>".into());

        ExprIr::BinaryOp {
            left: Box::new(left),
            op,
            right: Box::new(right),
        }
    }

    fn convert_cast_expr(ce: squawk_syntax::ast::CastExpr) -> ExprIr {
        let inner = ce
            .expr()
            .map(Self::convert)
            .unwrap_or(ExprIr::Sentinel("<cast_inner>".into()));
        let target_type = ce
            .ty()
            .map(|t| t.syntax().text().to_string())
            .unwrap_or_else(|| "<type>".into());
        ExprIr::Cast {
            expr: Box::new(inner),
            target_type,
        }
    }

    fn convert_case_expr(ce: squawk_syntax::ast::CaseExpr) -> ExprIr {
        let mut branches: Vec<ExprIr> = Vec::new();

        branches.push(ce.expr().map(Self::convert).unwrap_or(ExprIr::Omitted));

        if let Some(wcl) = ce.when_clause_list() {
            for when in wcl.when_clauses() {
                branches.push(
                    when.condition()
                        .map(Self::convert)
                        .unwrap_or(ExprIr::Omitted),
                );
                branches.push(when.then().map(Self::convert).unwrap_or(ExprIr::Omitted));
            }
        }

        branches.push(
            ce.else_clause()
                .and_then(|ec| ec.expr())
                .map(Self::convert)
                .unwrap_or(ExprIr::Omitted),
        );

        ExprIr::FunctionCall {
            name: "<case>".into(),
            args: branches,
        }
    }

    fn convert_array_expr(ae: squawk_syntax::ast::ArrayExpr) -> ExprIr {
        let elements: Vec<ExprIr> = ae.exprs().map(Self::convert).collect();
        ExprIr::FunctionCall {
            name: "<array>".into(),
            args: elements,
        }
    }

    fn convert_between_expr(be: squawk_syntax::ast::BetweenExpr) -> ExprIr {
        let args = vec![
            be.target().map(Self::convert).unwrap_or(ExprIr::Omitted),
            be.start().map(Self::convert).unwrap_or(ExprIr::Omitted),
            be.end().map(Self::convert).unwrap_or(ExprIr::Omitted),
        ];
        ExprIr::FunctionCall {
            name: "<between>".into(),
            args,
        }
    }

    fn convert_index_expr(ie: squawk_syntax::ast::IndexExpr) -> ExprIr {
        let args = vec![
            ie.base().map(Self::convert).unwrap_or(ExprIr::Omitted),
            ie.index().map(Self::convert).unwrap_or(ExprIr::Omitted),
        ];
        ExprIr::FunctionCall {
            name: "<index>".into(),
            args,
        }
    }

    fn convert_slice_expr(se: squawk_syntax::ast::SliceExpr) -> ExprIr {
        let args = vec![
            se.base().map(Self::convert).unwrap_or(ExprIr::Omitted),
            se.start().map(Self::convert).unwrap_or(ExprIr::Omitted),
            se.end().map(Self::convert).unwrap_or(ExprIr::Omitted),
        ];
        ExprIr::FunctionCall {
            name: "<slice>".into(),
            args,
        }
    }

    fn convert_field_expr(fe: squawk_syntax::ast::FieldExpr) -> ExprIr {
        let args = vec![
            fe.base().map(Self::convert).unwrap_or(ExprIr::Omitted),
            fe.field()
                .map(|f| ExprIr::Literal(f.text().to_string()))
                .unwrap_or(ExprIr::Omitted),
        ];
        ExprIr::FunctionCall {
            name: "<field>".into(),
            args,
        }
    }

    fn convert_postfix_expr(pe: squawk_syntax::ast::PostfixExpr) -> ExprIr {
        let mut args = Vec::new();
        for child in pe.syntax().children() {
            if let Some(expr) = Expr::cast(child) {
                args.push(Self::convert(expr));
            }
        }
        ExprIr::FunctionCall {
            name: "<postfix>".into(),
            args,
        }
    }
}