safe_migrate/rules/
expressions.rs1use crate::analysis::mutations::Mutation;
4use crate::analysis::state::{AnalysisState, CascadeResult, MutationResult};
5use crate::ast::identifiers::ObjectId;
6use crate::engine::config::Config;
7use crate::model::relation::RelationState;
8use crate::report::violations::{Violation, ViolationTier};
9use crate::rules::Rule;
10use std::collections::HashMap;
11
12pub struct VolatileDefaultRule;
13
14impl Rule for VolatileDefaultRule {
15 fn id(&self) -> &'static str {
16 "volatile-default"
17 }
18 fn default_tier(&self) -> ViolationTier {
19 ViolationTier::Tier3
20 }
21 fn recipe(&self) -> &'static str {
22 "Using volatile functions (like random() or now()) as defaults can cause unexpected behavior in logical replication or caching."
23 }
24
25 fn evaluate(
26 &self,
27 mutation: &Mutation,
28 result: &MutationResult,
29 _pre_relations: &HashMap<ObjectId, RelationState>,
30 _state: &AnalysisState,
31 _config: &Config,
32 _cascade: Option<&CascadeResult>,
33 ) -> Vec<Violation> {
34 if *result == MutationResult::Skipped {
35 return vec![];
36 }
37
38 let mut violations = Vec::new();
39
40 if let Mutation::CreateTable(c) = mutation {
41 for col in &c.columns {
42 if let Some(def) = &col.default
43 && def.is_volatile()
44 {
45 violations.push(Violation {
46 rule_id: self.id(),
47 title: format!("Volatile default expression on {}.{}", c.id, col.name),
48 tier: self.default_tier(),
49 recipe: self.recipe(),
50 dedup_key: None,
51 });
52 }
53 }
54 }
55
56 violations
57 }
58}