use crate::checks::pg_helpers::NodeEnum;
use crate::checks::{Check, Config, MigrationContext, if_exists_clause};
use crate::violation::Violation;
pub struct DropDatabaseCheck;
impl Check for DropDatabaseCheck {
fn check(&self, node: &NodeEnum, _config: &Config, _ctx: &MigrationContext) -> Vec<Violation> {
let NodeEnum::DropdbStmt(drop_db) = node else {
return vec![];
};
let db_name = &drop_db.dbname;
let if_exists_str = if_exists_clause(drop_db.missing_ok);
vec![Violation::new(
"DROP DATABASE",
format!(
"Dropping database '{db_name}' permanently deletes the entire database \
including all tables, data, and objects. This operation requires \
exclusive access (all connections must be terminated) and cannot \
run inside a transaction block."
),
format!(
r"DROP DATABASE should almost never appear in application migrations.
If you need to drop a database:
1. Verify this is intentional and coordinate with your DBA:
-- Confirm database '{db_name}' is scheduled for removal
2. Create a complete backup before proceeding:
pg_dump -Fc {db_name} > {db_name}_backup.dump
3. Terminate all active connections to the database:
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = '{db_name}' AND pid <> pg_backend_pid();
4. Drop the database (outside of application migrations):
DROP DATABASE{if_exists_str} {db_name};
If this is intentional (e.g., test cleanup), use a safety-assured block:
-- safety-assured:start
DROP DATABASE{if_exists_str} {db_name};
-- safety-assured:end
Note: Postgres 13+ supports WITH (FORCE) to auto-terminate connections, but this is even more dangerous."
),
)]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{assert_allows, assert_detects_violation};
#[test]
fn test_detects_drop_database() {
assert_detects_violation!(DropDatabaseCheck, "DROP DATABASE mydb;", "DROP DATABASE");
}
#[test]
fn test_detects_drop_database_if_exists() {
assert_detects_violation!(
DropDatabaseCheck,
"DROP DATABASE IF EXISTS mydb;",
"DROP DATABASE"
);
}
#[test]
fn test_ignores_drop_table() {
assert_allows!(DropDatabaseCheck, "DROP TABLE users;");
}
#[test]
fn test_ignores_drop_index() {
assert_allows!(DropDatabaseCheck, "DROP INDEX idx_users_email;");
}
#[test]
fn test_ignores_create_database() {
assert_allows!(DropDatabaseCheck, "CREATE DATABASE mydb;");
}
}