Skip to main content

safe_migrate/rules/
expressions.rs

1// FILE: src/rules/expressions.rs
2
3use crate::analysis::mutations::Mutation;
4use crate::analysis::state::{AnalysisState, CascadeResult, MutationResult};
5use crate::engine::config::Config;
6use crate::report::violations::{ObjectKind, OperationKind, Violation, ViolationTier};
7use crate::rules::Rule;
8
9pub struct VolatileDefaultRule;
10
11impl Rule for VolatileDefaultRule {
12    fn id(&self) -> &'static str {
13        "volatile-default"
14    }
15    fn default_tier(&self) -> ViolationTier {
16        ViolationTier::Tier3
17    }
18    fn recipe(&self) -> &'static str {
19        "Using volatile functions (like random() or now()) as defaults can cause unexpected behavior in logical replication or caching."
20    }
21
22    fn evaluate(
23        &self,
24        mutation: &Mutation,
25        result: &MutationResult,
26        _pre_state: &crate::analysis::state::PreState,
27        _state: &AnalysisState,
28        _config: &Config,
29        _cascade: Option<&CascadeResult>,
30    ) -> Vec<Violation> {
31        if *result == MutationResult::Skipped {
32            return vec![];
33        }
34
35        let mut violations = Vec::new();
36
37        if let Mutation::CreateTable(c) = mutation {
38            for col in &c.columns {
39                if let Some(def) = &col.default
40                    && def.is_volatile()
41                {
42                    violations.push(Violation {
43                        source_range: None,
44                        rule_id: self.id(),
45                        operation_kind: OperationKind::CreateTable,
46                        object_kind: ObjectKind::Table,
47                        object_name: c.id.to_string(),
48                        tier: self.default_tier(),
49                        reason: format!("Volatile default expression on {}.{}", c.id, col.name),
50                        recipe: self.recipe(),
51                        dedup_key: None,
52                        sql: None,
53                        fk_dependency_related: false,
54                    });
55                }
56            }
57        }
58
59        if let Mutation::AlterTable(a) = mutation {
60            match &a.action {
61                crate::analysis::mutations::AlterTableActionMutation::AddColumn {
62                    name,
63                    default: Some(def),
64                    ..
65                } if def.is_volatile() => {
66                    violations.push(Violation {
67                        source_range: None,
68                        rule_id: self.id(),
69                        operation_kind: OperationKind::AddColumn,
70                        object_kind: ObjectKind::Table,
71                        object_name: a.id.to_string(),
72                        tier: self.default_tier(),
73                        reason: format!("Volatile default expression on {}.{}", a.id, name),
74                        recipe: self.recipe(),
75                        dedup_key: None,
76                        sql: None,
77                        fk_dependency_related: false,
78                    });
79                }
80                crate::analysis::mutations::AlterTableActionMutation::SetDefault {
81                    column,
82                    default: Some(def),
83                } if def.is_volatile() => {
84                    violations.push(Violation {
85                        source_range: None,
86                        rule_id: self.id(),
87                        operation_kind: OperationKind::SetDefault,
88                        object_kind: ObjectKind::Table,
89                        object_name: a.id.to_string(),
90                        tier: self.default_tier(),
91                        reason: format!("Volatile default expression on {}.{}", a.id, column),
92                        recipe: self.recipe(),
93                        dedup_key: None,
94                        sql: None,
95                        fk_dependency_related: false,
96                    });
97                }
98                _ => {}
99            }
100        }
101
102        violations
103    }
104}