safe_migrate/analysis/
expr_ir.rs1use 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 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}