renovate 0.2.23

A new way to handle Postgres schema migration.
Documentation
use crate::parser::ConstraintInfo;
use itertools::Itertools;
use pg_query::{
    protobuf::{AExprKind, RoleSpecType, SqlValueFunctionOp, TypeName},
    Node, NodeEnum,
};

pub fn node_to_embed_constraint(node: &Node) -> Option<ConstraintInfo> {
    match &node.node {
        Some(NodeEnum::Constraint(v)) => ConstraintInfo::try_from(v.as_ref()).ok(),
        _ => None,
    }
}

pub fn type_name_to_string(n: &TypeName) -> String {
    let typname = n.names.iter().filter_map(node_to_string).join(".");
    let typmod = n.typmods.iter().filter_map(node_to_string).join("");
    let array_bounds = array_bounds_to_string(&n.array_bounds);

    match (typmod.as_str(), array_bounds.as_str()) {
        ("", "") => typname,
        ("", b) => format!("{}{}", typname, b),
        (m, "") => format!("{}({})", typname, m),
        (m, b) => format!("{}({}){}", typname, m, b),
    }
}

pub fn array_bounds_to_string(bounds: &[Node]) -> String {
    bounds
        .iter()
        .filter_map(node_to_string)
        .map(|s| {
            if s == "-1" {
                "[]".to_owned()
            } else {
                format!("[{}]", s)
            }
        })
        .join("")
}

pub fn node_to_string(node: &Node) -> Option<String> {
    match &node.node {
        Some(n) => node_enum_to_string(n),
        _ => None,
    }
}

pub fn node_enum_to_string(node: &NodeEnum) -> Option<String> {
    match node {
        NodeEnum::String(s) => Some(s.str.clone()),
        NodeEnum::Integer(i) => Some(i.ival.to_string()),
        NodeEnum::AConst(a) => a.val.as_ref().and_then(|v| match &v.node {
            Some(NodeEnum::String(s)) => Some(format!("'{}'", s.str)),
            Some(NodeEnum::Integer(i)) => Some(i.ival.to_string()),
            _ => None,
        }),
        NodeEnum::FuncCall(f) => {
            let fname = f.funcname.iter().filter_map(node_to_string).join(".");
            let args = f.args.iter().filter_map(node_to_string).join(", ");
            Some(format!("{}({})", fname, args))
        }
        NodeEnum::AExpr(e) => {
            let left = e.lexpr.as_deref().and_then(node_to_string);
            let right = e.rexpr.as_deref().and_then(node_to_string);
            let op = e.name.iter().filter_map(node_to_string).join(".");
            let op_kind = e.kind();
            match (left, right) {
                (Some(l), Some(r)) => match e.kind() {
                    AExprKind::AexprOp => Some(format!("{} {} {}", l, op, r)),
                    AExprKind::AexprOpAll => Some(format!("{} {} ALL ({})", l, op, r)),
                    AExprKind::AexprOpAny => Some(format!("{} {} ANY ({})", l, op, r)),
                    _ => panic!("Unsupported AExprKind: {:?}", op_kind),
                },
                (l, r) => panic!("Expect left and right to exists. Got {:?} and {:?}", l, r),
            }
        }
        NodeEnum::TypeCast(c) => {
            let arg = c.arg.as_deref().and_then(node_to_string);
            let typname = c
                .type_name
                .as_ref()
                .map(|t| t.names.iter().filter_map(node_to_string).join("."));
            match (arg, typname) {
                (Some(a), Some(t)) => Some(format!("{}::{}", a, t)),
                _ => None,
            }
        }
        NodeEnum::TypeName(t) => Some(type_name_to_string(t)),
        NodeEnum::ColumnRef(c) => {
            let fields = c.fields.iter().filter_map(node_to_string).join(",");
            Some(fields)
        }
        NodeEnum::SqlvalueFunction(f) => match f.op() {
            SqlValueFunctionOp::SvfopCurrentUser => Some("CURRENT_USER".to_owned()),
            SqlValueFunctionOp::SvfopCurrentRole => Some("CURRENT_ROLE".to_owned()),
            op => unimplemented!("Unsupported SqlValueFunctionOp: {:?}", op),
        },
        NodeEnum::AArrayExpr(a) => {
            let elements = a.elements.iter().filter_map(node_to_string).join(",");
            Some(format!("ARRAY [{}]", elements))
        }
        NodeEnum::RoleSpec(r) => match r.roletype() {
            RoleSpecType::RolespecCstring => Some(r.rolename.clone()),
            RoleSpecType::RolespecCurrentUser => Some("CURRENT_USER".to_owned()),
            RoleSpecType::RolespecSessionUser => Some("SESSION_USER".to_owned()),
            RoleSpecType::RolespecPublic => None,
            RoleSpecType::Undefined => None,
        },
        _ => None,
    }
}