Skip to main content

safe_migrate/rules/
opaque.rs

1// FILE: src/rules/opaque.rs
2use crate::analysis::mutations::Mutation;
3use crate::analysis::state::{AnalysisState, CascadeResult, MutationResult};
4use crate::engine::config::Config;
5use crate::report::violations::{ObjectKind, OperationKind, Violation, ViolationTier};
6use crate::rules::Rule;
7
8pub struct OpaqueDynamicSqlRule;
9
10impl Rule for OpaqueDynamicSqlRule {
11    fn id(&self) -> &'static str {
12        "opaque-dynamic-sql"
13    }
14    fn default_tier(&self) -> ViolationTier {
15        ViolationTier::Tier2
16    }
17    fn recipe(&self) -> &'static str {
18        "Procedural or dynamic SQL (DO blocks, EXECUTE) obscures schema mutations. Lock analysis confidence is heavily degraded."
19    }
20
21    fn evaluate(
22        &self,
23        mutation: &Mutation,
24        _result: &MutationResult,
25        _pre_state: &crate::analysis::state::PreState,
26        _state: &AnalysisState,
27        _config: &Config,
28        _cascade: Option<&CascadeResult>,
29    ) -> Vec<Violation> {
30        let mut violations = Vec::new();
31
32        if let Mutation::Opaque(op) = mutation {
33            if matches!(
34                op,
35                crate::analysis::mutations::OpaqueMutation::UnresolvedReference { .. }
36            ) {
37                return vec![];
38            }
39            let block_type = match op {
40                crate::analysis::mutations::OpaqueMutation::DoBlock => "DO block",
41                crate::analysis::mutations::OpaqueMutation::Execute => "EXECUTE statement",
42                crate::analysis::mutations::OpaqueMutation::DynamicSql => "Dynamic SQL",
43                crate::analysis::mutations::OpaqueMutation::PrepareTransaction => {
44                    "PREPARE TRANSACTION"
45                }
46                crate::analysis::mutations::OpaqueMutation::SetTransaction => "SET TRANSACTION",
47                crate::analysis::mutations::OpaqueMutation::SetConstraints => "SET CONSTRAINTS",
48                crate::analysis::mutations::OpaqueMutation::UnresolvedReference { .. } => {
49                    unreachable!()
50                }
51            };
52
53            violations.push(Violation {
54                source_range: None,
55                rule_id: self.id(),
56                operation_kind: OperationKind::OpaqueSql,
57                object_kind: ObjectKind::Unknown,
58                object_name: "<dynamic>".to_string(),
59                tier: self.default_tier(),
60                reason: format!("Encountered opaque {}", block_type),
61                recipe: self.recipe(),
62                dedup_key: None,
63                sql: None,
64            });
65        }
66
67        violations
68    }
69}