Skip to main content

safe_migrate/rules/
expressions.rs

1// FILE: src/rules/expressions.rs
2
3use 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}