safe-migrate 0.3.2

Lint PostgreSQL migrations against live database statistics to prevent blocking locks
Documentation
// FILE: src/rules/opaque.rs
use crate::analysis::mutations::Mutation;
use crate::analysis::state::{AnalysisState, CascadeResult, MutationResult};
use crate::ast::identifiers::ObjectId;
use crate::engine::config::Config;
use crate::model::relation::RelationState;
use crate::report::violations::{Violation, ViolationTier};
use crate::rules::Rule;
use std::collections::HashMap;

pub struct OpaqueDynamicSqlRule;

impl Rule for OpaqueDynamicSqlRule {
    fn id(&self) -> &'static str {
        "opaque-dynamic-sql"
    }
    fn default_tier(&self) -> ViolationTier {
        ViolationTier::Tier2
    }
    fn recipe(&self) -> &'static str {
        "Procedural or dynamic SQL (DO blocks, EXECUTE) obscures schema mutations. Lock analysis confidence is heavily degraded."
    }

    fn evaluate(
        &self,
        mutation: &Mutation,
        _result: &MutationResult,
        _pre_relations: &HashMap<ObjectId, RelationState>,
        _state: &AnalysisState,
        _config: &Config,
        _cascade: Option<&CascadeResult>,
    ) -> Vec<Violation> {
        let mut violations = Vec::new();

        if let Mutation::Opaque(op) = mutation {
            let block_type = match op {
                crate::analysis::mutations::OpaqueMutation::DoBlock => "DO block",
                crate::analysis::mutations::OpaqueMutation::Execute => "EXECUTE statement",
                crate::analysis::mutations::OpaqueMutation::DynamicSql => "Dynamic SQL",
                crate::analysis::mutations::OpaqueMutation::PrepareTransaction => {
                    "PREPARE TRANSACTION"
                }
                crate::analysis::mutations::OpaqueMutation::SetTransaction => "SET TRANSACTION",
                crate::analysis::mutations::OpaqueMutation::SetConstraints => "SET CONSTRAINTS",
            };

            violations.push(Violation {
                rule_id: self.id(),
                title: format!("Encountered opaque {}", block_type),
                tier: self.default_tier(),
                recipe: self.recipe(),
                dedup_key: None,
            });
        }

        violations
    }
}