toasty_sql/
migration.rs

1use std::borrow::Cow;
2
3use toasty_core::{
4    driver::Capability,
5    schema::db::{
6        ColumnsDiff, ColumnsDiffItem, IndicesDiffItem, Schema, SchemaDiff, Table, TablesDiffItem,
7    },
8};
9
10use crate::stmt::{AlterColumnChanges, AlterTable, AlterTableAction, DropTable, Name, Statement};
11
12pub struct MigrationStatement<'a> {
13    statement: Statement,
14    schema: Cow<'a, Schema>,
15}
16
17impl<'a> MigrationStatement<'a> {
18    fn new(statement: Statement, schema: Cow<'a, Schema>) -> Self {
19        MigrationStatement { statement, schema }
20    }
21
22    pub fn from_diff(schema_diff: &'a SchemaDiff<'a>, capability: &Capability) -> Vec<Self> {
23        let mut result = Vec::new();
24        for table in schema_diff.tables().iter() {
25            match table {
26                TablesDiffItem::CreateTable(table) => {
27                    result.push(Self::new(
28                        Statement::create_table(table, capability),
29                        Cow::Borrowed(schema_diff.next()),
30                    ));
31                    for index in &table.indices {
32                        result.push(Self::new(
33                            Statement::create_index(index),
34                            Cow::Borrowed(schema_diff.next()),
35                        ));
36                    }
37                }
38                TablesDiffItem::DropTable(table) => result.push(Self::new(
39                    Statement::drop_table(table),
40                    Cow::Borrowed(schema_diff.previous()),
41                )),
42                TablesDiffItem::AlterTable {
43                    previous,
44                    next,
45                    columns,
46                    indices,
47                    ..
48                } => {
49                    let mut schema = Cow::Borrowed(schema_diff.previous());
50                    if previous.name != next.name {
51                        result.push(Self::new(
52                            Statement::alter_table_rename_to(previous, &next.name),
53                            schema.clone(),
54                        ));
55                        schema.to_mut().table_mut(previous.id).name = next.name.clone();
56                    }
57
58                    // Check if any column alteration requires table recreation
59                    // (e.g. SQLite can't alter column type/nullability/auto_increment)
60                    let needs_recreation = !capability.schema_mutations.alter_column_type
61                        && columns.iter().any(|item| {
62                            matches!(
63                                item,
64                                ColumnsDiffItem::AlterColumn {
65                                    previous: prev_col,
66                                    next: next_col
67                                } if AlterColumnChanges::from_diff(prev_col, next_col).has_type_change()
68                            )
69                        });
70
71                    if needs_recreation {
72                        Self::emit_table_recreation(
73                            &mut result,
74                            schema,
75                            previous,
76                            next,
77                            columns,
78                            capability,
79                        );
80                    } else {
81                        Self::emit_column_changes(&mut result, schema, columns, capability);
82                    }
83
84                    // Indices diff
85                    for item in indices.iter() {
86                        match item {
87                            IndicesDiffItem::CreateIndex(index) => {
88                                result.push(Self::new(
89                                    Statement::create_index(index),
90                                    Cow::Borrowed(schema_diff.next()),
91                                ));
92                            }
93                            IndicesDiffItem::DropIndex(index) => {
94                                result.push(Self::new(
95                                    Statement::drop_index(index),
96                                    Cow::Borrowed(schema_diff.previous()),
97                                ));
98                            }
99                            IndicesDiffItem::AlterIndex { previous, next } => {
100                                result.push(Self::new(
101                                    Statement::drop_index(previous),
102                                    Cow::Borrowed(schema_diff.previous()),
103                                ));
104                                result.push(Self::new(
105                                    Statement::create_index(next),
106                                    Cow::Borrowed(schema_diff.next()),
107                                ));
108                            }
109                        }
110                    }
111                }
112            }
113        }
114        result
115    }
116
117    fn emit_table_recreation(
118        result: &mut Vec<Self>,
119        schema: Cow<'a, Schema>,
120        previous: &Table,
121        next: &Table,
122        columns: &ColumnsDiff<'_>,
123        capability: &Capability,
124    ) {
125        let current_name = schema.table(previous.id).name.clone();
126        let temp_name = format!("_toasty_new_{}", current_name);
127
128        // 1. PRAGMA foreign_keys = OFF
129        result.push(Self::new(
130            Statement::pragma_disable_foreign_keys(),
131            schema.clone(),
132        ));
133
134        // 2. CREATE TABLE temp with new schema
135        let temp_schema = {
136            let mut s = schema.as_ref().clone();
137            let t = s.table_mut(next.id);
138            t.name = temp_name.clone();
139            t.columns = next.columns.clone();
140            t.primary_key = next.primary_key.clone();
141            s
142        };
143        result.push(Self::new(
144            Statement::create_table(next, capability),
145            Cow::Owned(temp_schema),
146        ));
147
148        // 3. INSERT INTO temp SELECT ... FROM current
149        let column_mappings: Vec<(Name, Name)> = next
150            .columns
151            .iter()
152            .filter(|col| {
153                // Skip added columns (no source data)
154                !columns
155                    .iter()
156                    .any(|item| matches!(item, ColumnsDiffItem::AddColumn(c) if c.id == col.id))
157            })
158            .map(|col| {
159                let target_name = Name::from(&col.name[..]);
160                // Check if this column was renamed
161                let source_name = columns
162                    .iter()
163                    .find_map(|item| match item {
164                        ColumnsDiffItem::AlterColumn {
165                            previous: prev_col,
166                            next: next_col,
167                        } if next_col.id == col.id && prev_col.name != next_col.name => {
168                            Some(Name::from(&prev_col.name[..]))
169                        }
170                        _ => None,
171                    })
172                    .unwrap_or_else(|| Name::from(&col.name[..]));
173                (target_name, source_name)
174            })
175            .collect();
176
177        result.push(Self::new(
178            Statement::copy_table(
179                Name::from(current_name.as_str()),
180                Name::from(temp_name.as_str()),
181                column_mappings,
182            ),
183            schema.clone(),
184        ));
185
186        // 4. DROP TABLE current
187        result.push(Self::new(
188            DropTable {
189                name: Name::from(current_name.as_str()),
190                if_exists: false,
191            }
192            .into(),
193            schema.clone(),
194        ));
195
196        // 5. ALTER TABLE temp RENAME TO current
197        result.push(Self::new(
198            AlterTable {
199                name: Name::from(temp_name.as_str()),
200                action: AlterTableAction::RenameTo(Name::from(current_name.as_str())),
201            }
202            .into(),
203            schema.clone(),
204        ));
205
206        // 6. PRAGMA foreign_keys = ON
207        result.push(Self::new(
208            Statement::pragma_enable_foreign_keys(),
209            schema.clone(),
210        ));
211    }
212
213    fn emit_column_changes(
214        result: &mut Vec<Self>,
215        schema: Cow<'a, Schema>,
216        columns: &ColumnsDiff<'_>,
217        capability: &Capability,
218    ) {
219        for item in columns.iter() {
220            match item {
221                ColumnsDiffItem::AddColumn(column) => {
222                    result.push(Self::new(
223                        Statement::add_column(column, capability),
224                        schema.clone(),
225                    ));
226                }
227                ColumnsDiffItem::DropColumn(column) => {
228                    result.push(Self::new(Statement::drop_column(column), schema.clone()));
229                }
230                ColumnsDiffItem::AlterColumn {
231                    previous,
232                    next: col_next,
233                } => {
234                    let changes = AlterColumnChanges::from_diff(previous, col_next);
235                    let changes = if capability.schema_mutations.alter_column_properties_atomic {
236                        vec![changes]
237                    } else {
238                        changes.split()
239                    };
240
241                    for changes in changes {
242                        result.push(Self::new(
243                            Statement::alter_column(previous, changes, capability),
244                            schema.clone(),
245                        ));
246                    }
247                }
248            }
249        }
250    }
251
252    pub fn statement(&self) -> &Statement {
253        &self.statement
254    }
255
256    pub fn schema(&self) -> &Schema {
257        &self.schema
258    }
259}