use crate::analysis::mutations::{AlterTableActionMutation, 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 IdempotencyRule;
impl Rule for IdempotencyRule {
fn id(&self) -> &'static str {
"missing-idempotency"
}
fn default_tier(&self) -> ViolationTier {
ViolationTier::Tier3
}
fn recipe(&self) -> &'static str {
"Use IF EXISTS or IF NOT EXISTS to prevent migration failures on partial re-runs."
}
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();
let mut add_violation = |title: String| {
violations.push(Violation {
rule_id: self.id(),
title,
tier: self.default_tier(),
recipe: self.recipe(),
dedup_key: None,
});
};
match mutation {
Mutation::CreateTable(c) if !c.if_not_exists => {
add_violation(format!("CREATE TABLE {} without IF NOT EXISTS", c.id));
}
Mutation::CreateIndex(c) if !c.if_not_exists => {
add_violation(format!("CREATE INDEX {} without IF NOT EXISTS", c.id));
}
Mutation::CreateSequence(c) if !c.if_not_exists => {
add_violation(format!("CREATE SEQUENCE {} without IF NOT EXISTS", c.id));
}
Mutation::DropTable(d) if !d.if_exists => {
add_violation(format!("DROP TABLE {} without IF EXISTS", d.id));
}
Mutation::DropIndex(d) if !d.if_exists => {
add_violation(format!("DROP INDEX {} without IF EXISTS", d.id));
}
Mutation::DropPolicy(d) if !d.if_exists => {
add_violation(format!(
"DROP POLICY {} on {} without IF EXISTS",
d.name, d.table
));
}
Mutation::DropTrigger(d) if !d.if_exists => {
add_violation(format!(
"DROP TRIGGER {} on {} without IF EXISTS",
d.name, d.table
));
}
Mutation::DropSequence(d) if !d.if_exists => {
for id in &d.ids {
add_violation(format!("DROP SEQUENCE {} without IF EXISTS", id));
}
}
Mutation::DropView(d) if !d.if_exists => {
for id in &d.ids {
add_violation(format!("DROP VIEW {} without IF EXISTS", id));
}
}
Mutation::DropMaterializedView(d) if !d.if_exists => {
for id in &d.ids {
add_violation(format!("DROP MATERIALIZED VIEW {} without IF EXISTS", id));
}
}
Mutation::DropDomain(d) if !d.if_exists => {
for id in &d.ids {
add_violation(format!("DROP DOMAIN {} without IF EXISTS", id));
}
}
Mutation::AlterTable(a) => match &a.action {
AlterTableActionMutation::AddColumn {
name,
if_not_exists,
..
} if !*if_not_exists => {
add_violation(format!(
"ALTER TABLE {} ADD COLUMN {} without IF NOT EXISTS",
a.id, name
));
}
AlterTableActionMutation::DropColumn {
name, if_exists, ..
} if !*if_exists => {
add_violation(format!(
"ALTER TABLE {} DROP COLUMN {} without IF EXISTS",
a.id, name
));
}
_ => {}
},
_ => {}
}
violations
}
}