use crate::checks::pg_helpers::{AlterTableType, NodeEnum, alter_table_cmds};
use crate::checks::{Check, Config, MigrationContext, if_exists_clause};
use crate::violation::Violation;
pub struct DropColumnCheck;
impl Check for DropColumnCheck {
fn check(&self, node: &NodeEnum, _config: &Config, _ctx: &MigrationContext) -> Vec<Violation> {
let Some((table_name, cmds)) = alter_table_cmds(node) else {
return vec![];
};
cmds.iter()
.filter_map(|cmd| {
if cmd.subtype != AlterTableType::AtDropColumn as i32 {
return None;
}
let column_name = &cmd.name;
let if_exists = cmd.missing_ok;
Some(Violation::new(
"DROP COLUMN",
format!(
"Dropping column '{column_name}' from table '{table_name}' requires an ACCESS EXCLUSIVE lock, blocking all operations. \
This typically triggers a table rewrite with duration depending on table size."
),
format!(r"1. Mark the column as unused in your application code first.
2. Deploy the application without the column references.
3. (Optional) Set column to NULL to reclaim space:
ALTER TABLE {table} ALTER COLUMN {column} DROP NOT NULL;
UPDATE {table} SET {column} = NULL;
4. Drop the column in a later migration after confirming it's unused:
ALTER TABLE {table} DROP COLUMN {column}{if_exists};
Note: Postgres doesn't support DROP COLUMN CONCURRENTLY. The rewrite is unavoidable but staging the removal reduces risk.",
table = table_name,
column = column_name,
if_exists = if_exists_clause(if_exists)
),
))
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{assert_allows, assert_detects_n_violations, assert_detects_violation};
#[test]
fn test_detects_drop_column() {
assert_detects_violation!(
DropColumnCheck,
"ALTER TABLE users DROP COLUMN email;",
"DROP COLUMN"
);
}
#[test]
fn test_detects_drop_column_if_exists() {
assert_detects_violation!(
DropColumnCheck,
"ALTER TABLE users DROP COLUMN IF EXISTS email;",
"DROP COLUMN"
);
}
#[test]
fn test_detects_drop_multiple_columns() {
assert_detects_n_violations!(
DropColumnCheck,
"ALTER TABLE users DROP COLUMN a, DROP COLUMN b;",
2,
"DROP COLUMN"
);
}
#[test]
fn test_ignores_other_operations() {
assert_allows!(
DropColumnCheck,
"ALTER TABLE users ADD COLUMN email VARCHAR(255);"
);
}
#[test]
fn test_ignores_other_statements() {
assert_allows!(
DropColumnCheck,
"CREATE TABLE users (id SERIAL PRIMARY KEY);"
);
}
}