1use super::ddl_expr;
4use super::dialect::MigrationDialect;
5use super::diff::{
6 columns_structurally_equal, diff_foreign_keys, diff_indexes, fk_reference_for_property,
7};
8use super::schema_change::SchemaChange;
9use super::types::{
10 Migration, ModelSnapshot, SnapshotCheck, SnapshotColumn, SnapshotEntityType,
11 SnapshotForeignKey, SnapshotIndex, SnapshotIndexColumn, SnapshotTableOptions,
12};
13use crate::error::EFResult;
14use crate::metadata::EntityTypeMeta;
15use std::collections::{HashMap, HashSet};
16
17pub struct MigrationEngine {
20 pub(crate) dialect: MigrationDialect,
21}
22
23impl MigrationEngine {
24 pub fn new(dialect: MigrationDialect) -> Self {
25 Self { dialect }
26 }
27
28 pub fn generate(
30 &self,
31 name: &str,
32 current: &[EntityTypeMeta],
33 previous_snapshot: &Option<ModelSnapshot>,
34 ) -> EFResult<Migration> {
35 let current_snapshot = self.create_snapshot("__current__", current);
36
37 let changes = match previous_snapshot {
38 Some(prev) => self.diff(prev, ¤t_snapshot),
39 None => self.initial_create_with_fks(¤t_snapshot),
40 };
41
42 let up_sql = self.generate_up_sql(&changes);
43 let down_sql = self.generate_down_sql(&changes);
44
45 Ok(Migration {
46 id: name.to_string(),
47 description: name.to_string(),
48 up_sql,
49 down_sql,
50 })
51 }
52
53 pub fn generate_from_snapshot(
60 &self,
61 name: &str,
62 current: &ModelSnapshot,
63 previous_snapshot: &Option<ModelSnapshot>,
64 ) -> EFResult<Migration> {
65 let changes = match previous_snapshot {
66 Some(prev) => self.diff(prev, current),
67 None => self.initial_create_with_fks(current),
68 };
69 Ok(Migration {
70 id: name.to_string(),
71 description: name.to_string(),
72 up_sql: self.generate_up_sql(&changes),
73 down_sql: self.generate_down_sql(&changes),
74 })
75 }
76
77 pub fn create_snapshot(
79 &self,
80 migration_id: &str,
81 entity_types: &[EntityTypeMeta],
82 ) -> ModelSnapshot {
83 let types = entity_types
84 .iter()
85 .map(|et| {
86 let resolve_column = |name: &str| -> String {
89 et.properties
90 .iter()
91 .find(|p| p.column_name == name)
92 .or_else(|| et.properties.iter().find(|p| p.field_name == name))
93 .map(|p| p.column_name.to_string())
94 .unwrap_or_else(|| name.to_string())
95 };
96
97 let explicit_single: HashMap<String, &crate::metadata::ForeignKeyMeta> = et
100 .foreign_keys
101 .iter()
102 .filter(|fk| fk.columns.len() == 1 && fk.referenced_columns.len() == 1)
103 .map(|fk| (resolve_column(&fk.columns[0]), fk))
104 .collect();
105
106 let foreign_keys = et
107 .foreign_keys
108 .iter()
109 .filter(|fk| !(fk.columns.len() == 1 && fk.referenced_columns.len() == 1))
110 .map(|fk| SnapshotForeignKey {
111 name: fk
112 .name
113 .as_ref()
114 .map(|n| n.to_string())
115 .unwrap_or_else(|| fk.derive_name(et.table_name.as_ref())),
116 columns: fk.columns.iter().map(|c| resolve_column(c)).collect(),
117 referenced_table: fk.referenced_table.to_string(),
118 referenced_columns: fk
119 .referenced_columns
120 .iter()
121 .map(|c| c.to_string())
122 .collect(),
123 on_delete: fk.on_delete.as_ref().map(|c| c.to_string()),
124 })
125 .collect();
126
127 let indexes = et
128 .indexes
129 .iter()
130 .map(|idx| SnapshotIndex {
131 name: idx
132 .name
133 .as_ref()
134 .map(|n| n.to_string())
135 .unwrap_or_else(|| idx.derive_name(et.table_name.as_ref())),
136 columns: idx
137 .columns
138 .iter()
139 .map(|c| SnapshotIndexColumn {
140 value: match c {
141 crate::metadata::IndexColumn::Column(name) => {
142 resolve_column(name)
143 }
144 crate::metadata::IndexColumn::Expression(_) => {
145 c.as_str().to_string()
146 }
147 },
148 is_expression: c.is_expression(),
149 })
150 .collect(),
151 is_unique: idx.is_unique,
152 filter: idx
153 .filter
154 .as_ref()
155 .map(|f| ddl_expr::render_filter(f, self.dialect)),
156 include: idx.include.iter().map(|c| c.to_string()).collect(),
157 })
158 .collect();
159
160 let checks = et
161 .checks
162 .iter()
163 .map(|c| SnapshotCheck {
164 name: c.name.to_string(),
165 expression: c.expression.to_string(),
166 })
167 .collect();
168
169 let table_options = SnapshotTableOptions {
170 engine: et.table_options.engine.as_ref().map(|s| s.to_string()),
171 charset: et.table_options.charset.as_ref().map(|s| s.to_string()),
172 collate: et.table_options.collate.as_ref().map(|s| s.to_string()),
173 };
174
175 SnapshotEntityType {
176 type_name: et.type_name.to_string(),
177 table_name: et.table_name.to_string(),
178 columns: et
179 .properties
180 .iter()
181 .filter(|p| !p.is_not_mapped)
182 .map(|p| {
183 let (nav_table, nav_col, nav_on_delete) =
184 fk_reference_for_property(et, p.column_name.as_ref());
185 let (fk_table, fk_col, fk_on_delete) =
186 match explicit_single.get(p.column_name.as_ref()) {
187 Some(fk) => (
188 Some(fk.referenced_table.to_string()),
189 Some(fk.referenced_columns[0].to_string()),
190 fk.on_delete
194 .as_ref()
195 .map(|c| c.to_string())
196 .or(nav_on_delete),
197 ),
198 None => (nav_table, nav_col, nav_on_delete),
199 };
200 let is_foreign_key = p.is_foreign_key || fk_table.is_some();
204 SnapshotColumn {
205 field_name: p.field_name.to_string(),
206 column_name: p.column_name.to_string(),
207 type_name: p.type_name.to_string(),
208 is_primary_key: p.is_primary_key,
209 is_required: p.is_required,
210 is_foreign_key,
211 max_length: p.max_length,
212 is_auto_increment: p.is_auto_increment,
213 is_sequence: p.is_sequence,
214 sequence_name: p.sequence_name.as_ref().map(|s| s.to_string()),
215 fk_referenced_table: fk_table,
216 fk_referenced_column: fk_col,
217 has_index: p.has_index,
218 is_unique: p.is_unique,
219 fk_on_delete,
220 default_value: p.default_value.as_ref().map(|s| s.to_string()),
221 column_type: p.column_type.as_ref().map(|s| s.to_string()),
222 precision: p.precision,
223 scale: p.scale,
224 is_fixed_length: p.is_fixed_length,
225 collation: p.collation.as_ref().map(|s| s.to_string()),
226 comment: p.comment.as_ref().map(|s| s.to_string()),
227 computed_sql: p.computed_sql.as_ref().map(|s| s.to_string()),
228 computed_stored: p.computed_stored,
229 }
230 })
231 .collect(),
232 indexes,
233 foreign_keys,
234 checks,
235 table_options,
236 is_view: et.is_view,
237 is_keyless: et.is_keyless,
238 comment: et.comment.as_ref().map(|s| s.to_string()),
239 }
240 })
241 .collect();
242
243 ModelSnapshot {
244 migration_id: migration_id.to_string(),
245 entity_types: types,
246 }
247 }
248
249 pub(crate) fn initial_create(&self, current: &ModelSnapshot) -> Vec<SchemaChange> {
254 current
255 .entity_types
256 .iter()
257 .filter(|et| !et.is_view)
258 .map(|et| SchemaChange::CreateTable {
259 table: et.table_name.clone(),
260 columns: et.columns.clone(),
261 checks: et.checks.clone(),
262 table_options: et.table_options.clone(),
263 foreign_keys: et.foreign_keys.clone(),
264 comment: et.comment.clone(),
265 })
266 .collect()
267 }
268
269 fn diff(&self, old: &ModelSnapshot, new: &ModelSnapshot) -> Vec<SchemaChange> {
270 let mut changes = Vec::new();
271
272 let old_tables: HashMap<&str, &SnapshotEntityType> = old
275 .entity_types
276 .iter()
277 .filter(|e| !e.is_view)
278 .map(|e| (e.table_name.as_str(), e))
279 .collect();
280 let new_tables: HashMap<&str, &SnapshotEntityType> = new
281 .entity_types
282 .iter()
283 .filter(|e| !e.is_view)
284 .map(|e| (e.table_name.as_str(), e))
285 .collect();
286
287 let old_names: HashSet<&str> = old_tables.keys().copied().collect();
288 let new_names: HashSet<&str> = new_tables.keys().copied().collect();
289
290 for name in old_names.difference(&new_names) {
292 changes.push(SchemaChange::DropTable {
293 table: name.to_string(),
294 });
295 }
296
297 for name in new_names.difference(&old_names) {
299 let et = new_tables[name];
300 changes.push(SchemaChange::CreateTable {
301 table: et.table_name.clone(),
302 columns: et.columns.clone(),
303 checks: et.checks.clone(),
304 table_options: et.table_options.clone(),
305 foreign_keys: et.foreign_keys.clone(),
306 comment: et.comment.clone(),
307 });
308 if self.dialect != MigrationDialect::Sqlite {
311 Self::append_create_table_fks(&mut changes, &et.table_name, &et.columns);
312 Self::append_composite_fks(&mut changes, et);
313 }
314 Self::append_create_table_indexes(&mut changes, &et.table_name, &et.columns);
315 Self::append_declared_indexes(&mut changes, et);
316 }
317
318 for name in old_names.intersection(&new_names) {
320 let old_et = old_tables[name];
321 let new_et = new_tables[name];
322 let table = &old_et.table_name;
323
324 let old_cols: HashMap<&str, &SnapshotColumn> = old_et
325 .columns
326 .iter()
327 .map(|c| (c.column_name.as_str(), c))
328 .collect();
329 let new_cols: HashMap<&str, &SnapshotColumn> = new_et
330 .columns
331 .iter()
332 .map(|c| (c.column_name.as_str(), c))
333 .collect();
334
335 let old_col_names: HashSet<&str> = old_cols.keys().copied().collect();
336 let new_col_names: HashSet<&str> = new_cols.keys().copied().collect();
337
338 for col_name in new_col_names.difference(&old_col_names) {
340 let col = new_cols[col_name];
341 changes.push(SchemaChange::AddColumn {
342 table: table.clone(),
343 column: (*col).clone(),
344 });
345 changes.extend(diff_indexes(table, &SnapshotColumn::default(), col));
346 }
347
348 changes.extend(diff_foreign_keys(table, old_et, new_et));
350 changes.extend(super::diff::diff_composite_foreign_keys(
351 table, old_et, new_et,
352 ));
353
354 changes.extend(super::diff::diff_declared_indexes(table, old_et, new_et));
356 changes.extend(super::diff::diff_checks(table, old_et, new_et));
357 changes.extend(super::diff::diff_table_comment(table, old_et, new_et));
358 changes.extend(super::diff::diff_table_options(table, old_et, new_et));
359
360 for col_name in old_col_names.difference(&new_col_names) {
362 changes.push(SchemaChange::DropColumn {
363 table: table.clone(),
364 column_name: col_name.to_string(),
365 });
366 }
367
368 for col_name in old_col_names.intersection(&new_col_names) {
370 let old_col = old_cols[col_name];
371 let new_col = new_cols[col_name];
372
373 changes.extend(diff_indexes(table, old_col, new_col));
375
376 if !columns_structurally_equal(old_col, new_col) {
378 changes.push(SchemaChange::AlterColumn {
379 table: table.clone(),
380 column_name: col_name.to_string(),
381 old: Box::new((*old_col).clone()),
382 new: Box::new((*new_col).clone()),
383 });
384 }
385 }
386 }
387
388 changes
389 }
390
391 pub(super) fn append_create_table_fks(
392 changes: &mut Vec<SchemaChange>,
393 table: &str,
394 columns: &[SnapshotColumn],
395 ) {
396 for col in columns {
397 if let Some((ref_table, ref_col)) = super::diff::fk_target(col) {
398 changes.push(SchemaChange::AddForeignKey {
399 table: table.to_string(),
400 column: col.column_name.clone(),
401 referenced_table: ref_table,
402 referenced_column: ref_col,
403 on_delete: col.fk_on_delete.clone(),
404 });
405 }
406 }
407 }
408
409 pub(super) fn append_create_table_indexes(
410 changes: &mut Vec<SchemaChange>,
411 table: &str,
412 columns: &[SnapshotColumn],
413 ) {
414 for col in columns {
415 if col.is_unique {
416 changes.push(SchemaChange::CreateIndex {
417 table: table.to_string(),
418 column: col.column_name.clone(),
419 is_unique: true,
420 });
421 } else if col.has_index {
422 changes.push(SchemaChange::CreateIndex {
423 table: table.to_string(),
424 column: col.column_name.clone(),
425 is_unique: false,
426 });
427 }
428 }
429 }
430
431 pub(super) fn append_declared_indexes(
434 changes: &mut Vec<SchemaChange>,
435 et: &SnapshotEntityType,
436 ) {
437 for index in &et.indexes {
438 changes.push(SchemaChange::CreateDeclaredIndex {
439 table: et.table_name.clone(),
440 index: index.clone(),
441 });
442 }
443 }
444
445 pub(super) fn append_composite_fks(changes: &mut Vec<SchemaChange>, et: &SnapshotEntityType) {
447 for fk in &et.foreign_keys {
448 changes.push(SchemaChange::AddCompositeForeignKey {
449 table: et.table_name.clone(),
450 foreign_key: fk.clone(),
451 });
452 }
453 }
454}