safe-migrate 0.6.1

Sync PostgreSQL metadata, then lint migrations offline
Documentation
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ExprIr {
    Literal(String),
    ColumnRef(String),
    FunctionCall {
        name: String,
        args: Vec<ExprIr>,
    },
    BinaryOp {
        left: Box<ExprIr>,
        op: String,
        right: Box<ExprIr>,
    },
    Cast {
        expr: Box<ExprIr>,
        target_type: String,
    },
    Omitted,
}

impl ExprIr {
    pub fn is_volatile(&self) -> bool {
        match self {
            ExprIr::FunctionCall { name, args } => {
                const VOLATILE: &[&str] = &[
                    "clock_timestamp",
                    "timeofday",
                    "random",
                    "setseed",
                    "txid_current",
                    "txid_current_snapshot",
                    "txid_snapshot_xip",
                    "txid_snapshot_xmax",
                    "txid_snapshot_xmin",
                    "nextval",
                    "currval",
                    "lastval",
                    "setval",
                    "gen_random_uuid",
                    "uuid_generate_v1",
                    "uuid_generate_v1mc",
                    "uuid_generate_v4",
                ];

                // The lookup contains only VOLATILE functions; nested calls
                // are classified recursively below.
                let normalized = name.to_ascii_lowercase();
                let known_volatile = VOLATILE.contains(&normalized.as_str())
                    || normalized
                        .strip_prefix("pg_catalog.")
                        .is_some_and(|name| VOLATILE.contains(&name));
                known_volatile || args.iter().any(ExprIr::is_volatile)
            }
            ExprIr::BinaryOp { left, right, .. } => left.is_volatile() || right.is_volatile(),
            ExprIr::Cast { expr, .. } => expr.is_volatile(),
            ExprIr::Literal(_) | ExprIr::ColumnRef(_) | ExprIr::Omitted => false,
        }
    }
}