Skip to main content

vespertide_query/sql/
mod.rs

1pub mod add_column;
2pub mod add_constraint;
3pub mod create_table;
4pub mod data_migration;
5pub mod delete_column;
6pub mod delete_table;
7pub(crate) mod fill_with;
8pub mod helpers;
9pub mod modify_column_comment;
10pub mod modify_column_default;
11pub mod modify_column_nullable;
12pub mod modify_column_type;
13pub mod raw_sql;
14pub mod remap_enum_values;
15pub mod remove_constraint;
16pub mod rename_column;
17pub mod rename_table;
18pub mod replace_constraint;
19pub mod types;
20
21pub use helpers::{quote_ident, quote_idents};
22pub use types::{BuiltQuery, DatabaseBackend, RawSql};
23
24use crate::error::QueryError;
25use vespertide_core::{MigrationAction, TableConstraint, TableDef};
26
27use self::{
28    add_column::build_add_column, add_constraint::build_add_constraint,
29    create_table::build_create_table, data_migration::build_data_migration,
30    delete_column::build_delete_column, delete_table::build_delete_table,
31    modify_column_comment::build_modify_column_comment,
32    modify_column_default::build_modify_column_default,
33    modify_column_nullable::build_modify_column_nullable,
34    remap_enum_values::build_remap_enum_values, remove_constraint::build_remove_constraint,
35    rename_column::build_rename_column, rename_table::build_rename_table,
36    replace_constraint::build_replace_constraint,
37};
38
39/// Build SQL for a single migration action against a known schema.
40///
41/// For multi-action plans use [`crate::build_plan_queries`] which handles
42/// schema evolution between actions.
43pub fn build_action_queries(
44    backend: DatabaseBackend,
45    action: &MigrationAction,
46    current_schema: &[TableDef],
47) -> Result<Vec<BuiltQuery>, QueryError> {
48    build_action_queries_with_pending(backend, action, current_schema, &[])
49}
50
51/// Build SQL queries for a migration action, with awareness of pending constraints.
52///
53/// `pending_constraints` are constraints that exist in the logical schema but haven't been
54/// physically created as database indexes yet. This is used by `SQLite` temp table rebuilds
55/// to avoid recreating indexes that will be created by future `AddConstraint` actions.
56#[expect(
57    clippy::too_many_lines,
58    reason = "flat 16-variant MigrationAction dispatcher kept inline so the variant→builder mapping stays auditable; extracting individual arms scatters the routing logic"
59)]
60pub fn build_action_queries_with_pending(
61    backend: DatabaseBackend,
62    action: &MigrationAction,
63    current_schema: &[TableDef],
64    pending_constraints: &[TableConstraint],
65) -> Result<Vec<BuiltQuery>, QueryError> {
66    match action {
67        MigrationAction::CreateTable {
68            table,
69            columns,
70            constraints,
71        } => build_create_table(backend, table, columns, constraints),
72
73        MigrationAction::DeleteTable { table } => Ok(vec![build_delete_table(table)]),
74
75        MigrationAction::AddColumn {
76            table,
77            column,
78            fill_with,
79        } => build_add_column(
80            backend,
81            table,
82            column,
83            fill_with.as_deref(),
84            current_schema,
85            pending_constraints,
86        ),
87
88        MigrationAction::RenameColumn { table, from, to } => {
89            Ok(vec![build_rename_column(table, from, to)])
90        }
91
92        MigrationAction::DeleteColumn { table, column } => {
93            // Find the column type from current schema for enum DROP TYPE support
94            let column_type =
95                helpers::find_column_in_schema(current_schema, table, column).map(|c| &c.r#type);
96            Ok(build_delete_column(
97                backend,
98                table,
99                column,
100                column_type,
101                current_schema,
102                pending_constraints,
103            ))
104        }
105
106        MigrationAction::ModifyColumnType {
107            table,
108            column,
109            new_type,
110            fill_with,
111            narrowing_strategy,
112            timezone,
113        } => modify_column_type::build_with_narrowing_preprocess(
114            backend,
115            table.as_str(),
116            column.as_str(),
117            new_type,
118            fill_with.as_ref(),
119            narrowing_strategy.as_ref(),
120            timezone.as_deref(),
121            current_schema,
122            pending_constraints,
123        ),
124
125        MigrationAction::ModifyColumnNullable {
126            table,
127            column,
128            nullable,
129            fill_with,
130            delete_null_rows,
131        } => build_modify_column_nullable(
132            backend,
133            table,
134            column,
135            *nullable,
136            fill_with.as_deref(),
137            delete_null_rows.unwrap_or(false),
138            current_schema,
139            pending_constraints,
140        ),
141
142        MigrationAction::ModifyColumnDefault {
143            table,
144            column,
145            new_default,
146            backfill,
147        } => build_modify_column_default(
148            backend,
149            table,
150            column,
151            new_default.as_deref(),
152            backfill.as_deref(),
153            current_schema,
154            pending_constraints,
155        ),
156
157        MigrationAction::ModifyColumnComment {
158            table,
159            column,
160            new_comment,
161        } => build_modify_column_comment(
162            backend,
163            table,
164            column,
165            new_comment.as_deref(),
166            current_schema,
167        ),
168
169        MigrationAction::RenameTable { from, to } => Ok(vec![build_rename_table(from, to)]),
170
171        MigrationAction::RawSql { sql } => Ok(vec![BuiltQuery::Raw(RawSql::uniform(sql.clone()))]),
172
173        MigrationAction::DataMigration { sql, .. } => Ok(build_data_migration(sql)),
174
175        MigrationAction::AddConstraint { .. }
176        | MigrationAction::RemoveConstraint { .. }
177        | MigrationAction::ReplaceConstraint { .. } => {
178            build_constraint_action_queries(backend, action, current_schema, pending_constraints)
179        }
180
181        MigrationAction::RemapEnumValues {
182            table,
183            column,
184            mapping,
185        } => build_remap_enum_values(backend, table.as_str(), column.as_str(), mapping),
186
187        _ => unreachable!("MigrationAction is #[non_exhaustive]; all variants are matched above"),
188    }
189}
190
191fn build_constraint_action_queries(
192    backend: DatabaseBackend,
193    action: &MigrationAction,
194    current_schema: &[TableDef],
195    pending_constraints: &[TableConstraint],
196) -> Result<Vec<BuiltQuery>, QueryError> {
197    match action {
198        MigrationAction::AddConstraint { table, constraint } => build_add_constraint(
199            backend,
200            table,
201            constraint,
202            current_schema,
203            pending_constraints,
204        ),
205        MigrationAction::RemoveConstraint { table, constraint } => build_remove_constraint(
206            backend,
207            table,
208            constraint,
209            current_schema,
210            pending_constraints,
211        ),
212        MigrationAction::ReplaceConstraint { table, from, to } => build_replace_constraint(
213            backend,
214            table,
215            from,
216            to,
217            current_schema,
218            pending_constraints,
219        ),
220        _ => unreachable!("only constraint actions are dispatched here"),
221    }
222}
223
224#[cfg(test)]
225mod tests;