1use std::{borrow::Cow, collections::HashSet};
2
3use toasty_core::{
4 driver::Capability,
5 schema::{
6 db::{Column, Schema, Table, TableId, Type, TypeEnum},
7 diff,
8 },
9};
10
11use crate::stmt::{AlterColumnChanges, AlterTable, AlterTableAction, DropTable, Name, Statement};
12
13fn is_named_enum_type_only_change(
16 previous: &Column,
17 next: &Column,
18 renamed_types: &HashSet<(&str, &str)>,
19) -> bool {
20 if previous.name != next.name
21 || previous.nullable != next.nullable
22 || previous.primary_key != next.primary_key
23 || previous.auto_increment != next.auto_increment
24 {
25 return false;
26 }
27
28 let same_shape = matches!(
31 (&previous.storage_ty, &next.storage_ty),
32 (Type::Enum(_), Type::Enum(_)) | (Type::List(_), Type::List(_))
33 );
34
35 if !same_shape {
36 return false;
37 }
38
39 matches!(
40 (
41 previous.storage_ty.named_enum(),
42 next.storage_ty.named_enum(),
43 ),
44 (
45 Some(TypeEnum { name: Some(a), .. }),
46 Some(TypeEnum { name: Some(b), .. }),
47 ) if a == b || renamed_types.contains(&(a.as_str(), b.as_str()))
48 )
49}
50
51pub struct MigrationStatement<'a> {
57 statement: Statement,
58 schema: Cow<'a, Schema>,
59}
60
61impl<'a> MigrationStatement<'a> {
62 fn new(statement: Statement, schema: Cow<'a, Schema>) -> Self {
63 MigrationStatement { statement, schema }
64 }
65
66 pub fn from_diff(schema_diff: &'a diff::Schema<'a>, capability: &Capability) -> Vec<Self> {
74 let mut result = Vec::new();
75 let types_diff = schema_diff.types();
76 let renamed_types: HashSet<(&str, &str)> = types_diff
77 .iter()
78 .filter_map(|item| match item {
79 diff::Type::Rename { previous, next } => Some((
80 previous.name.as_deref().expect("named enum type"),
81 next.name.as_deref().expect("named enum type"),
82 )),
83 _ => None,
84 })
85 .collect();
86
87 if capability.named_enum_types {
90 for item in types_diff.iter() {
91 match item {
92 diff::Type::Create(ty) => {
93 result.push(Self::new(
94 Statement::create_enum_type(ty),
95 Cow::Borrowed(schema_diff.next()),
96 ));
97 }
98 diff::Type::Rename { previous, next } => {
99 result.push(Self::new(
100 Statement::rename_type(
101 previous.name.as_deref().expect("named enum type"),
102 next.name.as_deref().expect("named enum type"),
103 ),
104 Cow::Borrowed(schema_diff.previous()),
105 ));
106 }
107 diff::Type::AddVariants { ty, added } => {
108 let type_name = ty.name.as_deref().expect("named enum type");
109 for variant in added {
110 result.push(Self::new(
111 Statement::alter_type_add_value(type_name, variant),
112 Cow::Borrowed(schema_diff.next()),
113 ));
114 }
115 }
116 }
117 }
118 }
119
120 for table in schema_diff.tables().iter() {
121 match table {
122 diff::Table::Create(table) => {
123 result.push(Self::new(
124 Statement::create_table(table, capability),
125 Cow::Borrowed(schema_diff.next()),
126 ));
127 for index in &table.indices {
128 if index.primary_key {
129 continue; }
131 result.push(Self::new(
132 Statement::create_index(index),
133 Cow::Borrowed(schema_diff.next()),
134 ));
135 }
136 }
137 diff::Table::Drop(table) => result.push(Self::new(
138 Statement::drop_table(table),
139 Cow::Borrowed(schema_diff.previous()),
140 )),
141 diff::Table::Alter {
142 previous,
143 next,
144 columns,
145 indices,
146 ..
147 } => {
148 let mut schema = Cow::Borrowed(schema_diff.previous());
149 if previous.name != next.name {
150 result.push(Self::new(
151 Statement::alter_table_rename_to(previous, &next.name),
152 schema.clone(),
153 ));
154 schema.to_mut().table_mut(previous.id).name = next.name.clone();
155 }
156
157 let needs_recreation = !capability.schema_mutations.alter_column_type
160 && columns.iter().any(|item| {
161 matches!(
162 item,
163 diff::Column::Alter {
164 previous: prev_col,
165 next: next_col
166 } if AlterColumnChanges::from_diff(prev_col, next_col).has_type_change()
167 && !(capability.named_enum_types
168 && is_named_enum_type_only_change(
169 prev_col,
170 next_col,
171 &renamed_types,
172 ))
173 )
174 });
175
176 for item in indices.iter() {
179 match item {
180 diff::Index::Drop(index) => {
181 result.push(Self::new(
182 Statement::drop_index(index),
183 Cow::Borrowed(schema_diff.previous()),
184 ));
185 }
186 diff::Index::Alter { previous, .. } => {
187 result.push(Self::new(
188 Statement::drop_index(previous),
189 Cow::Borrowed(schema_diff.previous()),
190 ));
191 }
192 diff::Index::Create(_) => {}
193 }
194 }
195
196 if needs_recreation {
197 Self::emit_table_recreation(
198 &mut result,
199 schema,
200 previous,
201 next,
202 columns,
203 capability,
204 );
205 } else {
206 Self::emit_column_changes(
207 &mut result,
208 schema,
209 previous.id,
210 columns,
211 capability,
212 &renamed_types,
213 );
214 }
215
216 for item in indices.iter() {
219 match item {
220 diff::Index::Create(index) => {
221 result.push(Self::new(
222 Statement::create_index(index),
223 Cow::Borrowed(schema_diff.next()),
224 ));
225 }
226 diff::Index::Alter { next, .. } => {
227 result.push(Self::new(
228 Statement::create_index(next),
229 Cow::Borrowed(schema_diff.next()),
230 ));
231 }
232 diff::Index::Drop(_) => {}
233 }
234 }
235 }
236 }
237 }
238 result
239 }
240
241 fn emit_table_recreation(
242 result: &mut Vec<Self>,
243 schema: Cow<'a, Schema>,
244 previous: &Table,
245 next: &Table,
246 columns: &[diff::Column<'_>],
247 capability: &Capability,
248 ) {
249 let current_name = schema.table(previous.id).name.clone();
250 let temp_name = format!("_toasty_new_{}", current_name);
251
252 result.push(Self::new(
254 Statement::pragma_disable_foreign_keys(),
255 schema.clone(),
256 ));
257
258 let temp_schema = {
260 let mut s = schema.as_ref().clone();
261 let t = s.table_mut(next.id);
262 t.name = temp_name.clone();
263 t.columns = next.columns.clone();
264 t.primary_key = next.primary_key.clone();
265 s
266 };
267 result.push(Self::new(
268 Statement::create_table(next, capability),
269 Cow::Owned(temp_schema),
270 ));
271
272 let column_mappings: Vec<(Name, Name)> = next
274 .columns
275 .iter()
276 .filter(|col| {
277 !columns
279 .iter()
280 .any(|item| matches!(item, diff::Column::Add(c) if c.id == col.id))
281 })
282 .map(|col| {
283 let target_name = Name::from(&col.name[..]);
284 let source_name = columns
286 .iter()
287 .find_map(|item| match item {
288 diff::Column::Alter {
289 previous: prev_col,
290 next: next_col,
291 } if next_col.id == col.id && prev_col.name != next_col.name => {
292 Some(Name::from(&prev_col.name[..]))
293 }
294 _ => None,
295 })
296 .unwrap_or_else(|| Name::from(&col.name[..]));
297 (target_name, source_name)
298 })
299 .collect();
300
301 result.push(Self::new(
302 Statement::copy_table(
303 Name::from(current_name.as_str()),
304 Name::from(temp_name.as_str()),
305 column_mappings,
306 ),
307 schema.clone(),
308 ));
309
310 result.push(Self::new(
312 DropTable {
313 name: Name::from(current_name.as_str()),
314 if_exists: false,
315 }
316 .into(),
317 schema.clone(),
318 ));
319
320 result.push(Self::new(
322 AlterTable {
323 name: Name::from(temp_name.as_str()),
324 action: AlterTableAction::RenameTo(Name::from(current_name.as_str())),
325 }
326 .into(),
327 schema.clone(),
328 ));
329
330 result.push(Self::new(
332 Statement::pragma_enable_foreign_keys(),
333 schema.clone(),
334 ));
335 }
336
337 fn emit_column_changes(
338 result: &mut Vec<Self>,
339 schema: Cow<'a, Schema>,
340 table: TableId,
341 columns: &[diff::Column<'_>],
342 capability: &Capability,
343 renamed_types: &HashSet<(&str, &str)>,
344 ) {
345 for item in columns.iter() {
346 match item {
347 diff::Column::Add(column) => {
348 result.push(Self::new(
349 Statement::add_column(table, column, capability),
350 schema.clone(),
351 ));
352 }
353 diff::Column::Drop(column) => {
354 result.push(Self::new(Statement::drop_column(column), schema.clone()));
355 }
356 diff::Column::Alter {
357 previous,
358 next: col_next,
359 } => {
360 if capability.named_enum_types
363 && is_named_enum_type_only_change(previous, col_next, renamed_types)
364 {
365 continue;
366 }
367
368 let changes = AlterColumnChanges::from_diff(previous, col_next);
369 let changes = if capability.schema_mutations.alter_column_properties_atomic {
370 vec![changes]
371 } else {
372 changes.split()
373 };
374
375 for changes in changes {
376 result.push(Self::new(
377 Statement::alter_column(previous, changes, capability),
378 schema.clone(),
379 ));
380 }
381 }
382 }
383 }
384 }
385
386 pub fn statement(&self) -> &Statement {
388 &self.statement
389 }
390
391 pub fn schema(&self) -> &Schema {
393 &self.schema
394 }
395}