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