use crate::ast::NodeEnum;
use super::Rule;
use crate::{RuleHit, Severity};
pub struct AddIndexNonConcurrent;
impl Rule for AddIndexNonConcurrent {
fn id(&self) -> &'static str {
"add-index-non-concurrent"
}
fn severity(&self) -> Severity {
Severity::Error
}
fn check(&self, node: &NodeEnum, out: &mut Vec<RuleHit>) {
if let NodeEnum::IndexStmt(stmt) = node {
if !stmt.concurrent {
out.push(RuleHit {
message: "CREATE INDEX without CONCURRENTLY takes a lock that blocks writes \
to the table for the entire build."
.into(),
guidance:
"Use CREATE INDEX CONCURRENTLY (outside a transaction block). A failed \
CONCURRENTLY build leaves an INVALID index: drop it with DROP INDEX \
CONCURRENTLY and retry, or rebuild with REINDEX INDEX CONCURRENTLY."
.into(),
fix: Some(crate::fix::FixDraft {
title: "Add CONCURRENTLY",
edits: vec![crate::fix::FixDraftEdit {
anchor: crate::fix::FixAnchor::AfterKeyword("INDEX"),
replacement: " CONCURRENTLY".into(),
}],
}),
});
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{lint_sql, LintOptions};
#[test]
fn flags_plain_create_index() {
let findings = lint_sql("CREATE INDEX idx ON t (col)", &LintOptions::default()).unwrap();
assert!(findings
.iter()
.any(|f| f.rule_id == "add-index-non-concurrent"));
}
#[test]
fn ignores_concurrent_create_index() {
let findings = lint_sql(
"CREATE INDEX CONCURRENTLY idx ON t (col)",
&LintOptions::default(),
)
.unwrap();
assert!(findings
.iter()
.all(|f| f.rule_id != "add-index-non-concurrent"));
}
#[test]
fn emits_a_concurrently_fix() {
use crate::fix::apply;
let sql = "CREATE INDEX idx ON t (col);";
let fs = lint_sql(sql, &LintOptions::default()).unwrap();
let f = fs
.iter()
.find(|f| f.rule_id == "add-index-non-concurrent")
.unwrap();
let fix = f.fix.as_ref().expect("fix present");
assert_eq!(fix.title, "Add CONCURRENTLY");
let fixed = apply(sql, &fix.edits);
assert_eq!(fixed, "CREATE INDEX CONCURRENTLY idx ON t (col);");
assert!(lint_sql(&fixed, &LintOptions::default())
.unwrap()
.iter()
.all(|f| f.rule_id != "add-index-non-concurrent"));
}
}