safe_migrate/rules/
opaque.rs1use 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}