Skip to main content

safe_migrate/rules/
expressions.rs

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