Skip to main content

safe_migrate/analysis/
expr_ir.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4pub enum ExprIr {
5    Literal(String),
6    ColumnRef(String),
7    FunctionCall {
8        name: String,
9        args: Vec<ExprIr>,
10    },
11    BinaryOp {
12        left: Box<ExprIr>,
13        op: String,
14        right: Box<ExprIr>,
15    },
16    Cast {
17        expr: Box<ExprIr>,
18        target_type: String,
19    },
20    Omitted,
21}
22
23impl ExprIr {
24    pub fn is_volatile(&self) -> bool {
25        match self {
26            ExprIr::FunctionCall { name, args } => {
27                const VOLATILE: &[&str] = &[
28                    "clock_timestamp",
29                    "timeofday",
30                    "random",
31                    "setseed",
32                    "txid_current",
33                    "txid_current_snapshot",
34                    "txid_snapshot_xip",
35                    "txid_snapshot_xmax",
36                    "txid_snapshot_xmin",
37                    "nextval",
38                    "currval",
39                    "lastval",
40                    "setval",
41                    "gen_random_uuid",
42                    "uuid_generate_v1",
43                    "uuid_generate_v1mc",
44                    "uuid_generate_v4",
45                ];
46
47                // The lookup contains only VOLATILE functions; nested calls
48                // are classified recursively below.
49                let normalized = name.to_ascii_lowercase();
50                let known_volatile = VOLATILE.contains(&normalized.as_str())
51                    || normalized
52                        .strip_prefix("pg_catalog.")
53                        .is_some_and(|name| VOLATILE.contains(&name));
54                known_volatile || args.iter().any(ExprIr::is_volatile)
55            }
56            ExprIr::BinaryOp { left, right, .. } => left.is_volatile() || right.is_volatile(),
57            ExprIr::Cast { expr, .. } => expr.is_volatile(),
58            ExprIr::Literal(_) | ExprIr::ColumnRef(_) | ExprIr::Omitted => false,
59        }
60    }
61}