1use std::borrow::Cow;
2
3use toasty_core::{
4 driver::Capability,
5 schema::db::{
6 Column, ColumnsDiff, ColumnsDiffItem, IndicesDiffItem, Schema, SchemaDiff, Table,
7 TablesDiffItem, Type, TypeEnum, TypesDiffItem,
8 },
9};
10
11use crate::stmt::{AlterColumnChanges, AlterTable, AlterTableAction, DropTable, Name, Statement};
12
13fn is_named_enum_variant_only_change(previous: &Column, next: &Column) -> bool {
17 if previous.name != next.name
18 || previous.nullable != next.nullable
19 || previous.primary_key != next.primary_key
20 || previous.auto_increment != next.auto_increment
21 {
22 return false;
23 }
24
25 matches!(
26 (&previous.storage_ty, &next.storage_ty),
27 (
28 Type::Enum(TypeEnum { name: Some(a), .. }),
29 Type::Enum(TypeEnum { name: Some(b), .. }),
30 ) if a == b
31 )
32}
33
34pub struct MigrationStatement<'a> {
40 statement: Statement,
41 schema: Cow<'a, Schema>,
42}
43
44impl<'a> MigrationStatement<'a> {
45 fn new(statement: Statement, schema: Cow<'a, Schema>) -> Self {
46 MigrationStatement { statement, schema }
47 }
48
49 pub fn from_diff(schema_diff: &'a SchemaDiff<'a>, capability: &Capability) -> Vec<Self> {
57 let mut result = Vec::new();
58
59 if capability.named_enum_types {
62 let types_diff = schema_diff.types();
63 for item in types_diff.iter() {
64 match item {
65 TypesDiffItem::CreateType(ty) => {
66 result.push(Self::new(
67 Statement::create_enum_type(ty),
68 Cow::Borrowed(schema_diff.next()),
69 ));
70 }
71 TypesDiffItem::AddVariants { ty, added } => {
72 let type_name = ty.name.as_deref().expect("named enum type");
73 for variant in added {
74 result.push(Self::new(
75 Statement::alter_type_add_value(type_name, variant),
76 Cow::Borrowed(schema_diff.next()),
77 ));
78 }
79 }
80 }
81 }
82 }
83
84 for table in schema_diff.tables().iter() {
85 match table {
86 TablesDiffItem::CreateTable(table) => {
87 result.push(Self::new(
88 Statement::create_table(table, capability),
89 Cow::Borrowed(schema_diff.next()),
90 ));
91 for index in &table.indices {
92 if index.primary_key {
93 continue; }
95 result.push(Self::new(
96 Statement::create_index(index),
97 Cow::Borrowed(schema_diff.next()),
98 ));
99 }
100 }
101 TablesDiffItem::DropTable(table) => result.push(Self::new(
102 Statement::drop_table(table),
103 Cow::Borrowed(schema_diff.previous()),
104 )),
105 TablesDiffItem::AlterTable {
106 previous,
107 next,
108 columns,
109 indices,
110 ..
111 } => {
112 let mut schema = Cow::Borrowed(schema_diff.previous());
113 if previous.name != next.name {
114 result.push(Self::new(
115 Statement::alter_table_rename_to(previous, &next.name),
116 schema.clone(),
117 ));
118 schema.to_mut().table_mut(previous.id).name = next.name.clone();
119 }
120
121 let needs_recreation = !capability.schema_mutations.alter_column_type
124 && columns.iter().any(|item| {
125 matches!(
126 item,
127 ColumnsDiffItem::AlterColumn {
128 previous: prev_col,
129 next: next_col
130 } if AlterColumnChanges::from_diff(prev_col, next_col).has_type_change()
131 && !(capability.named_enum_types
132 && is_named_enum_variant_only_change(prev_col, next_col))
133 )
134 });
135
136 if needs_recreation {
137 Self::emit_table_recreation(
138 &mut result,
139 schema,
140 previous,
141 next,
142 columns,
143 capability,
144 );
145 } else {
146 Self::emit_column_changes(&mut result, schema, columns, capability);
147 }
148
149 for item in indices.iter() {
151 match item {
152 IndicesDiffItem::CreateIndex(index) => {
153 result.push(Self::new(
154 Statement::create_index(index),
155 Cow::Borrowed(schema_diff.next()),
156 ));
157 }
158 IndicesDiffItem::DropIndex(index) => {
159 result.push(Self::new(
160 Statement::drop_index(index),
161 Cow::Borrowed(schema_diff.previous()),
162 ));
163 }
164 IndicesDiffItem::AlterIndex { previous, next } => {
165 result.push(Self::new(
166 Statement::drop_index(previous),
167 Cow::Borrowed(schema_diff.previous()),
168 ));
169 result.push(Self::new(
170 Statement::create_index(next),
171 Cow::Borrowed(schema_diff.next()),
172 ));
173 }
174 }
175 }
176 }
177 }
178 }
179 result
180 }
181
182 fn emit_table_recreation(
183 result: &mut Vec<Self>,
184 schema: Cow<'a, Schema>,
185 previous: &Table,
186 next: &Table,
187 columns: &ColumnsDiff<'_>,
188 capability: &Capability,
189 ) {
190 let current_name = schema.table(previous.id).name.clone();
191 let temp_name = format!("_toasty_new_{}", current_name);
192
193 result.push(Self::new(
195 Statement::pragma_disable_foreign_keys(),
196 schema.clone(),
197 ));
198
199 let temp_schema = {
201 let mut s = schema.as_ref().clone();
202 let t = s.table_mut(next.id);
203 t.name = temp_name.clone();
204 t.columns = next.columns.clone();
205 t.primary_key = next.primary_key.clone();
206 s
207 };
208 result.push(Self::new(
209 Statement::create_table(next, capability),
210 Cow::Owned(temp_schema),
211 ));
212
213 let column_mappings: Vec<(Name, Name)> = next
215 .columns
216 .iter()
217 .filter(|col| {
218 !columns
220 .iter()
221 .any(|item| matches!(item, ColumnsDiffItem::AddColumn(c) if c.id == col.id))
222 })
223 .map(|col| {
224 let target_name = Name::from(&col.name[..]);
225 let source_name = columns
227 .iter()
228 .find_map(|item| match item {
229 ColumnsDiffItem::AlterColumn {
230 previous: prev_col,
231 next: next_col,
232 } if next_col.id == col.id && prev_col.name != next_col.name => {
233 Some(Name::from(&prev_col.name[..]))
234 }
235 _ => None,
236 })
237 .unwrap_or_else(|| Name::from(&col.name[..]));
238 (target_name, source_name)
239 })
240 .collect();
241
242 result.push(Self::new(
243 Statement::copy_table(
244 Name::from(current_name.as_str()),
245 Name::from(temp_name.as_str()),
246 column_mappings,
247 ),
248 schema.clone(),
249 ));
250
251 result.push(Self::new(
253 DropTable {
254 name: Name::from(current_name.as_str()),
255 if_exists: false,
256 }
257 .into(),
258 schema.clone(),
259 ));
260
261 result.push(Self::new(
263 AlterTable {
264 name: Name::from(temp_name.as_str()),
265 action: AlterTableAction::RenameTo(Name::from(current_name.as_str())),
266 }
267 .into(),
268 schema.clone(),
269 ));
270
271 result.push(Self::new(
273 Statement::pragma_enable_foreign_keys(),
274 schema.clone(),
275 ));
276 }
277
278 fn emit_column_changes(
279 result: &mut Vec<Self>,
280 schema: Cow<'a, Schema>,
281 columns: &ColumnsDiff<'_>,
282 capability: &Capability,
283 ) {
284 for item in columns.iter() {
285 match item {
286 ColumnsDiffItem::AddColumn(column) => {
287 result.push(Self::new(
288 Statement::add_column(column, capability),
289 schema.clone(),
290 ));
291 }
292 ColumnsDiffItem::DropColumn(column) => {
293 result.push(Self::new(Statement::drop_column(column), schema.clone()));
294 }
295 ColumnsDiffItem::AlterColumn {
296 previous,
297 next: col_next,
298 } => {
299 if capability.named_enum_types
302 && is_named_enum_variant_only_change(previous, col_next)
303 {
304 continue;
305 }
306
307 let changes = AlterColumnChanges::from_diff(previous, col_next);
308 let changes = if capability.schema_mutations.alter_column_properties_atomic {
309 vec![changes]
310 } else {
311 changes.split()
312 };
313
314 for changes in changes {
315 result.push(Self::new(
316 Statement::alter_column(previous, changes, capability),
317 schema.clone(),
318 ));
319 }
320 }
321 }
322 }
323 }
324
325 pub fn statement(&self) -> &Statement {
327 &self.statement
328 }
329
330 pub fn schema(&self) -> &Schema {
332 &self.schema
333 }
334}