Skip to main content

safe_migrate/rules/
triggers.rs

1use crate::analysis::mutations::{AlterTableActionMutation, 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 DisableTriggerRule;
8
9impl Rule for DisableTriggerRule {
10    fn id(&self) -> &'static str {
11        "disable-trigger"
12    }
13    fn default_tier(&self) -> ViolationTier {
14        ViolationTier::Tier1
15    }
16    fn recipe(&self) -> &'static str {
17        "Disabling triggers can lead to data inconsistency and bypasses critical business logic. Use with extreme caution and ensure triggers are re-enabled."
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        let mut violations = Vec::new();
33
34        if let Mutation::AlterTable(alter) = mutation {
35            match &alter.action {
36                AlterTableActionMutation::DisableTrigger { trigger_name } => {
37                    let name = trigger_name.as_deref().unwrap_or("ALL");
38                    violations.push(Violation {
39                        source_range: None,
40                        rule_id: self.id(),
41                        operation_kind: OperationKind::DisableTrigger,
42                        object_kind: ObjectKind::Trigger,
43                        object_name: format!("{} on {}", name, alter.id),
44                        tier: self.default_tier(),
45                        reason: format!("Disabling trigger {} on {}", name, alter.id),
46                        recipe: self.recipe(),
47                        dedup_key: None,
48                        sql: None,
49                        fk_dependency_related: false,
50                    });
51                }
52                AlterTableActionMutation::EnableTrigger { trigger_name } => {
53                    let name = trigger_name.as_deref().unwrap_or("ALL");
54                    violations.push(Violation { source_range: None,
55                        rule_id: self.id(),
56                        operation_kind: OperationKind::EnableTrigger,
57                        object_kind: ObjectKind::Trigger,
58                        object_name: format!("{} on {}", name, alter.id),
59                        tier: ViolationTier::Tier3, // Tier 3 because it is restorative
60                        reason: format!("Enabling trigger {} on {}", name, alter.id),
61                        recipe: "Re-enabling triggers restores business logic. Ensure state consistency was maintained during the disabled window.",
62                        dedup_key: None,
63                                    sql: None,
64                                    fk_dependency_related: false,
65                    });
66                }
67                _ => {}
68            }
69        }
70
71        violations
72    }
73}