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 });
54 }
55 }
56 }
57
58 if let Mutation::AlterTable(a) = mutation {
59 match &a.action {
60 crate::analysis::mutations::AlterTableActionMutation::AddColumn {
61 name,
62 default: Some(def),
63 ..
64 } if def.is_volatile() => {
65 violations.push(Violation {
66 source_range: None,
67 rule_id: self.id(),
68 operation_kind: OperationKind::AddColumn,
69 object_kind: ObjectKind::Table,
70 object_name: a.id.to_string(),
71 tier: self.default_tier(),
72 reason: format!("Volatile default expression on {}.{}", a.id, name),
73 recipe: self.recipe(),
74 dedup_key: None,
75 sql: None,
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::Other("set_default".to_string()),
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 });
94 }
95 _ => {}
96 }
97 }
98
99 violations
100 }
101}