vespertide_query/sql/
delete_column.rs

1use sea_query::{Alias, Table};
2
3use vespertide_core::ColumnType;
4
5use super::helpers::build_drop_enum_type_sql;
6use super::types::BuiltQuery;
7
8/// Build SQL to delete a column, optionally with DROP TYPE for enum columns (PostgreSQL)
9pub fn build_delete_column(
10    table: &str,
11    column: &str,
12    column_type: Option<&ColumnType>,
13) -> Vec<BuiltQuery> {
14    let mut stmts = Vec::new();
15
16    // Drop the column first
17    let stmt = Table::alter()
18        .table(Alias::new(table))
19        .drop_column(Alias::new(column))
20        .to_owned();
21    stmts.push(BuiltQuery::AlterTable(Box::new(stmt)));
22
23    // If column type is an enum, drop the type after (PostgreSQL only)
24    // Note: Only drop if this is the last column using this enum type
25    if let Some(col_type) = column_type
26        && let Some(drop_type_sql) = build_drop_enum_type_sql(table, col_type)
27    {
28        stmts.push(BuiltQuery::Raw(drop_type_sql));
29    }
30
31    stmts
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use crate::sql::types::DatabaseBackend;
38    use insta::{assert_snapshot, with_settings};
39    use rstest::rstest;
40    use vespertide_core::{ComplexColumnType, SimpleColumnType};
41
42    #[rstest]
43    #[case::delete_column_postgres(
44        "delete_column_postgres",
45        DatabaseBackend::Postgres,
46        &["ALTER TABLE \"users\" DROP COLUMN \"email\""]
47    )]
48    #[case::delete_column_mysql(
49        "delete_column_mysql",
50        DatabaseBackend::MySql,
51        &["ALTER TABLE `users` DROP COLUMN `email`"]
52    )]
53    #[case::delete_column_sqlite(
54        "delete_column_sqlite",
55        DatabaseBackend::Sqlite,
56        &["ALTER TABLE \"users\" DROP COLUMN \"email\""]
57    )]
58    fn test_delete_column(
59        #[case] title: &str,
60        #[case] backend: DatabaseBackend,
61        #[case] expected: &[&str],
62    ) {
63        let result = build_delete_column("users", "email", None);
64        let sql = result[0].build(backend);
65        for exp in expected {
66            assert!(
67                sql.contains(exp),
68                "Expected SQL to contain '{}', got: {}",
69                exp,
70                sql
71            );
72        }
73
74        with_settings!({ snapshot_suffix => format!("delete_column_{}", title) }, {
75            assert_snapshot!(sql);
76        });
77    }
78
79    #[test]
80    fn test_delete_enum_column_postgres() {
81        use vespertide_core::EnumValues;
82
83        let enum_type = ColumnType::Complex(ComplexColumnType::Enum {
84            name: "status".into(),
85            values: EnumValues::String(vec!["active".into(), "inactive".into()]),
86        });
87        let result = build_delete_column("users", "status", Some(&enum_type));
88
89        // Should have 2 statements: ALTER TABLE and DROP TYPE
90        assert_eq!(result.len(), 2);
91
92        let alter_sql = result[0].build(DatabaseBackend::Postgres);
93        assert!(alter_sql.contains("DROP COLUMN"));
94
95        let drop_type_sql = result[1].build(DatabaseBackend::Postgres);
96        assert!(drop_type_sql.contains("DROP TYPE IF EXISTS \"users_status\""));
97
98        // MySQL and SQLite should have empty DROP TYPE
99        let drop_type_mysql = result[1].build(DatabaseBackend::MySql);
100        assert!(drop_type_mysql.is_empty());
101    }
102
103    #[test]
104    fn test_delete_non_enum_column_no_drop_type() {
105        let text_type = ColumnType::Simple(SimpleColumnType::Text);
106        let result = build_delete_column("users", "name", Some(&text_type));
107
108        // Should only have 1 statement: ALTER TABLE
109        assert_eq!(result.len(), 1);
110    }
111}