safe_migrate/rules/
expressions.rs1use 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}