Skip to main content

safe_migrate/analysis/
expr_ir.rs

1// FILE: src/analysis/expr_ir.rs
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
5pub enum ExprIr {
6    Literal(String),
7    ColumnRef(String),
8    FunctionCall {
9        name: String,
10        args: Vec<ExprIr>,
11    },
12    BinaryOp {
13        left: Box<ExprIr>,
14        op: String,
15        right: Box<ExprIr>,
16    },
17    Cast {
18        expr: Box<ExprIr>,
19        target_type: String,
20    },
21    Omitted, // Added to prevent positional shifting in incomplete expressions (e.g., arr[2:])
22}
23
24impl ExprIr {
25    pub fn is_volatile(&self) -> bool {
26        match self {
27            ExprIr::FunctionCall { name, args } => {
28                // Synthetic wrapper functions for nested expressions
29                // e.g. <case>, <array>, <between>, <slice>
30                if name.starts_with('<') && name.ends_with('>') {
31                    return args.iter().any(|a| a.is_volatile());
32                }
33
34                const VOLATILE: &[&str] = &[
35                    // Truly VOLATILE: return different values on every call
36                    "clock_timestamp",
37                    "timeofday",
38                    "random",
39                    "setseed",
40                    "txid_current",
41                    "txid_current_snapshot",
42                    "txid_snapshot_xip",
43                    "txid_snapshot_xmax",
44                    "txid_snapshot_xmin",
45                    "nextval",
46                    "currval",
47                    "lastval",
48                    "setval",
49                    "gen_random_uuid",
50                    "uuid_generate_v1",
51                    "uuid_generate_v1mc",
52                    "uuid_generate_v4",
53                    // Note: now(), current_timestamp, current_date, current_user,
54                    // transaction_timestamp(), statement_timestamp() are all STABLE —
55                    // they return the transaction start time and are constant within
56                    // a statement. They do NOT require a table rewrite on PG11+.
57                ];
58
59                VOLATILE.contains(&name.to_lowercase().as_str())
60            }
61            ExprIr::BinaryOp { left, right, .. } => left.is_volatile() || right.is_volatile(),
62            ExprIr::Cast { expr, .. } => expr.is_volatile(),
63            ExprIr::Literal(_) | ExprIr::ColumnRef(_) | ExprIr::Omitted => false,
64        }
65    }
66}