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::{Persistence, RelationState};
use crate::report::violations::{Violation, ViolationTier};
use crate::rules::Rule;
use std::collections::HashMap;
pub struct ConcurrentIndexRule;
impl Rule for ConcurrentIndexRule {
fn id(&self) -> &'static str {
"require-concurrent-index"
}
fn default_tier(&self) -> ViolationTier {
ViolationTier::Tier1
}
fn recipe(&self) -> &'static str {
"Index operations block writes (or both reads and writes) when executed synchronously. Add the CONCURRENTLY keyword."
}
fn evaluate(
&self,
mutation: &Mutation,
result: &MutationResult,
pre_relations: &HashMap<ObjectId, RelationState>,
state: &AnalysisState,
config: &Config,
_cascade: Option<&CascadeResult>,
) -> Vec<Violation> {
if *result == MutationResult::Skipped {
return vec![];
}
let mut violations = Vec::new();
match mutation {
Mutation::CreateIndex(create) if !create.concurrently => {
let (is_temp, is_stale, rows, tx_depth) = match pre_relations.get(&create.table) {
Some(rel) => {
let stale =
rel.is_stale() && state.baseline_relations.contains(&create.table);
(
rel.persistence == Persistence::Temporary,
stale,
rel.estimated_rows.unwrap_or(config.default_rows),
rel.created_at_tx_depth,
)
}
None => (false, true, config.default_rows, 0),
};
if is_temp || tx_depth == state.local.transactions.len() {
return violations;
}
if is_stale {
let key = format!("{}_stale_{}", self.id(), create.table);
violations.push(Violation {
rule_id: self.id(),
title: format!("Table {} statistics are stale. Lock evaluations may be inaccurate.", create.table),
tier: ViolationTier::Tier2,
recipe: "Run ANALYZE to ensure accurate row estimates before structural changes.",
dedup_key: Some(key),
});
}
let tier1_threshold = config.rule_tier1_threshold(self.id());
let tier2_threshold = config.rule_tier2_threshold(self.id());
let tier = if rows >= tier1_threshold {
ViolationTier::Tier1
} else if rows >= tier2_threshold {
ViolationTier::Tier2
} else {
ViolationTier::Tier3
};
if tier != ViolationTier::Tier3 {
let mut title = format!("Synchronous index creation on {}", create.table);
if is_stale {
title.push_str(" [WARNING: Based on offline/stale statistics]");
}
violations.push(Violation {
rule_id: self.id(),
title,
tier,
recipe: self.recipe(),
dedup_key: None,
});
}
}
Mutation::DropIndex(drop) if !drop.concurrently => {
let rule_id = "require-concurrent-drop-index";
let tier1_threshold = config.rule_tier1_threshold(rule_id);
let tier2_threshold = config.rule_tier2_threshold(rule_id);
if pre_relations.is_empty() {
let rows = config.default_rows;
let tier = if rows >= tier1_threshold {
ViolationTier::Tier1
} else if rows >= tier2_threshold {
ViolationTier::Tier2
} else {
ViolationTier::Tier3
};
if tier != ViolationTier::Tier3 {
violations.push(Violation {
rule_id,
title: format!("Synchronous index drop for {}", drop.id),
tier,
recipe: self.recipe(),
dedup_key: None,
});
}
} else {
for rel in pre_relations.values() {
if rel.persistence == Persistence::Temporary {
continue;
}
let is_stale = rel.is_stale() && state.baseline_relations.contains(&rel.id);
if is_stale {
let key = format!("{}_stale_{}", rule_id, rel.id);
violations.push(Violation {
rule_id,
title: format!("Table {} statistics are stale. Lock evaluations may be inaccurate.", rel.id),
tier: ViolationTier::Tier2,
recipe: "Run ANALYZE to ensure accurate row estimates before structural changes.",
dedup_key: Some(key),
});
}
let rows = rel.estimated_rows.unwrap_or(config.default_rows);
let tier = if rows >= tier1_threshold {
ViolationTier::Tier1
} else if rows >= tier2_threshold {
ViolationTier::Tier2
} else {
ViolationTier::Tier3
};
if tier != ViolationTier::Tier3 {
let mut title =
format!("Synchronous index drop for {} on {}", drop.id, rel.id);
if is_stale {
title.push_str(" [WARNING: Based on offline/stale statistics]");
}
violations.push(Violation {
rule_id,
title,
tier,
recipe: self.recipe(),
dedup_key: None,
});
}
}
}
}
_ => {}
}
violations
}
}