use squawk_syntax::{
Parse, SourceFile,
ast::{self, AstNode},
};
use crate::{Edit, Fix, Linter, Rule, Violation};
fn concurrently_fix(reindex: &ast::Reindex) -> Option<Fix> {
let kw_token = match reindex.reindex_target()? {
ast::ReindexTarget::ReindexTargetDatabase(it) => it.database_token(),
ast::ReindexTarget::ReindexTargetIndex(it) => it.index_token(),
ast::ReindexTarget::ReindexTargetSchema(it) => it.schema_token(),
ast::ReindexTarget::ReindexTargetSystem(it) => it.system_token(),
ast::ReindexTarget::ReindexTargetTable(it) => it.table_token(),
}?;
let at = kw_token.text_range().end();
let edit = Edit::insert(" concurrently", at);
Some(Fix::new("Add `concurrently`", vec![edit]))
}
fn has_concurrently(reindex: &ast::Reindex) -> bool {
reindex
.reindex_target()
.is_some_and(|target| target.concurrently_token().is_some())
|| reindex.reindex_option_list().is_some_and(|option_list| {
option_list
.reindex_options()
.any(|option| matches!(option, ast::ReindexOption::ReindexOptionConcurrently(_)))
})
}
pub(crate) fn require_concurrent_reindex(ctx: &mut Linter, parse: &Parse<SourceFile>) {
for stmt in parse.tree().stmts() {
if let ast::Stmt::Reindex(reindex) = stmt {
if matches!(
reindex.reindex_target(),
Some(ast::ReindexTarget::ReindexTargetSystem(_))
) {
continue;
}
if !has_concurrently(&reindex) {
let fix = concurrently_fix(&reindex);
ctx.report(
Violation::for_node(
Rule::RequireConcurrentReindex,
"Reindexing a table or index without `concurrently` blocks reads and writes."
.into(),
reindex.syntax(),
)
.help("Use `concurrently` to avoid blocking reads and writes.")
.fix(fix),
);
}
}
}
}
#[cfg(test)]
mod test {
use insta::assert_snapshot;
use crate::{
Rule,
test_utils::{fix_sql, lint_errors, lint_ok},
};
#[must_use]
fn fix(sql: &str) -> String {
fix_sql(sql, Rule::RequireConcurrentReindex)
}
#[test]
fn reindex_table_no_concurrently_err() {
let sql = "REINDEX TABLE foo;";
assert_snapshot!(lint_errors(sql, Rule::RequireConcurrentReindex), @"
warning[require-concurrent-reindex]: Reindexing a table or index without `concurrently` blocks reads and writes.
╭▸
1 │ REINDEX TABLE foo;
│ ━━━━━━━━━━━━━━━━━━
│
├ help: Use `concurrently` to avoid blocking reads and writes.
╭╴
1 │ REINDEX TABLE concurrently foo;
╰╴ ++++++++++++
");
}
#[test]
fn reindex_index_no_concurrently_err() {
let sql = "REINDEX INDEX foo;";
assert_snapshot!(lint_errors(sql, Rule::RequireConcurrentReindex), @"
warning[require-concurrent-reindex]: Reindexing a table or index without `concurrently` blocks reads and writes.
╭▸
1 │ REINDEX INDEX foo;
│ ━━━━━━━━━━━━━━━━━━
│
├ help: Use `concurrently` to avoid blocking reads and writes.
╭╴
1 │ REINDEX INDEX concurrently foo;
╰╴ ++++++++++++
");
}
#[test]
fn reindex_schema_no_concurrently_err() {
let sql = "REINDEX SCHEMA foo;";
assert_snapshot!(lint_errors(sql, Rule::RequireConcurrentReindex), @"
warning[require-concurrent-reindex]: Reindexing a table or index without `concurrently` blocks reads and writes.
╭▸
1 │ REINDEX SCHEMA foo;
│ ━━━━━━━━━━━━━━━━━━━
│
├ help: Use `concurrently` to avoid blocking reads and writes.
╭╴
1 │ REINDEX SCHEMA concurrently foo;
╰╴ ++++++++++++
");
}
#[test]
fn reindex_database_no_concurrently_err() {
let sql = "REINDEX DATABASE foo;";
assert_snapshot!(lint_errors(sql, Rule::RequireConcurrentReindex), @"
warning[require-concurrent-reindex]: Reindexing a table or index without `concurrently` blocks reads and writes.
╭▸
1 │ REINDEX DATABASE foo;
│ ━━━━━━━━━━━━━━━━━━━━━
│
├ help: Use `concurrently` to avoid blocking reads and writes.
╭╴
1 │ REINDEX DATABASE concurrently foo;
╰╴ ++++++++++++
");
}
#[test]
fn reindex_table_concurrently_ok() {
let sql = "REINDEX TABLE CONCURRENTLY foo;";
lint_ok(sql, Rule::RequireConcurrentReindex);
}
#[test]
fn reindex_index_concurrently_ok() {
let sql = "REINDEX INDEX CONCURRENTLY foo;";
lint_ok(sql, Rule::RequireConcurrentReindex);
}
#[test]
fn reindex_options_concurrently_ok() {
let sql = "REINDEX (CONCURRENTLY) TABLE foo;";
lint_ok(sql, Rule::RequireConcurrentReindex);
}
#[test]
fn reindex_system_ok() {
let sql = "REINDEX SYSTEM mydb;";
lint_ok(sql, Rule::RequireConcurrentReindex);
}
#[test]
fn fix_reindex_table() {
let sql = "REINDEX TABLE foo;";
assert_snapshot!(fix(sql), @"REINDEX TABLE concurrently foo;");
}
#[test]
fn fix_reindex_index() {
let sql = "REINDEX INDEX foo;";
assert_snapshot!(fix(sql), @"REINDEX INDEX concurrently foo;");
}
}