1use super::dialect::MigrationDialect;
4use super::diff::{
5 columns_structurally_equal, diff_foreign_keys, diff_indexes, fk_reference_for_property,
6};
7use super::schema_change::SchemaChange;
8use super::types::{Migration, ModelSnapshot, SnapshotColumn, SnapshotEntityType};
9use crate::error::EFResult;
10use crate::metadata::EntityTypeMeta;
11use std::collections::{HashMap, HashSet};
12
13pub struct MigrationEngine {
16 pub(crate) dialect: MigrationDialect,
17}
18
19impl MigrationEngine {
20 pub fn new(dialect: MigrationDialect) -> Self {
21 Self { dialect }
22 }
23
24 pub fn generate(
26 &self,
27 name: &str,
28 current: &[EntityTypeMeta],
29 previous_snapshot: &Option<ModelSnapshot>,
30 ) -> EFResult<Migration> {
31 let current_snapshot = self.create_snapshot("__current__", current);
32
33 let changes = match previous_snapshot {
34 Some(prev) => self.diff(prev, ¤t_snapshot),
35 None => self.initial_create_with_fks(¤t_snapshot),
36 };
37
38 let up_sql = self.generate_up_sql(&changes);
39 let down_sql = self.generate_down_sql(&changes);
40
41 Ok(Migration {
42 id: name.to_string(),
43 description: name.to_string(),
44 up_sql,
45 down_sql,
46 })
47 }
48
49 pub fn create_snapshot(
51 &self,
52 migration_id: &str,
53 entity_types: &[EntityTypeMeta],
54 ) -> ModelSnapshot {
55 let types = entity_types
56 .iter()
57 .map(|et| SnapshotEntityType {
58 type_name: et.type_name.to_string(),
59 table_name: et.table_name.to_string(),
60 columns: et
61 .properties
62 .iter()
63 .filter(|p| !p.is_not_mapped)
64 .map(|p| {
65 let (fk_table, fk_col, fk_on_delete) =
66 fk_reference_for_property(et, p.column_name.as_ref());
67 SnapshotColumn {
68 field_name: p.field_name.to_string(),
69 column_name: p.column_name.to_string(),
70 type_name: p.type_name.to_string(),
71 is_primary_key: p.is_primary_key,
72 is_required: p.is_required,
73 is_foreign_key: p.is_foreign_key,
74 max_length: p.max_length,
75 is_auto_increment: p.is_auto_increment,
76 is_sequence: p.is_sequence,
77 sequence_name: p.sequence_name.as_ref().map(|s| s.to_string()),
78 fk_referenced_table: fk_table,
79 fk_referenced_column: fk_col,
80 has_index: p.has_index,
81 is_unique: p.is_unique,
82 fk_on_delete,
83 }
84 })
85 .collect(),
86 })
87 .collect();
88
89 ModelSnapshot {
90 migration_id: migration_id.to_string(),
91 entity_types: types,
92 }
93 }
94
95 pub(crate) fn initial_create(&self, current: &ModelSnapshot) -> Vec<SchemaChange> {
100 current
101 .entity_types
102 .iter()
103 .map(|et| SchemaChange::CreateTable {
104 table: et.table_name.clone(),
105 columns: et.columns.clone(),
106 })
107 .collect()
108 }
109
110 fn diff(&self, old: &ModelSnapshot, new: &ModelSnapshot) -> Vec<SchemaChange> {
111 let mut changes = Vec::new();
112
113 let old_tables: HashMap<&str, &SnapshotEntityType> = old
115 .entity_types
116 .iter()
117 .map(|e| (e.table_name.as_str(), e))
118 .collect();
119 let new_tables: HashMap<&str, &SnapshotEntityType> = new
120 .entity_types
121 .iter()
122 .map(|e| (e.table_name.as_str(), e))
123 .collect();
124
125 let old_names: HashSet<&str> = old_tables.keys().copied().collect();
126 let new_names: HashSet<&str> = new_tables.keys().copied().collect();
127
128 for name in old_names.difference(&new_names) {
130 changes.push(SchemaChange::DropTable {
131 table: name.to_string(),
132 });
133 }
134
135 for name in new_names.difference(&old_names) {
137 let et = new_tables[name];
138 changes.push(SchemaChange::CreateTable {
139 table: et.table_name.clone(),
140 columns: et.columns.clone(),
141 });
142 Self::append_create_table_fks(&mut changes, &et.table_name, &et.columns);
143 Self::append_create_table_indexes(&mut changes, &et.table_name, &et.columns);
144 }
145
146 for name in old_names.intersection(&new_names) {
148 let old_et = old_tables[name];
149 let new_et = new_tables[name];
150 let table = &old_et.table_name;
151
152 let old_cols: HashMap<&str, &SnapshotColumn> = old_et
153 .columns
154 .iter()
155 .map(|c| (c.column_name.as_str(), c))
156 .collect();
157 let new_cols: HashMap<&str, &SnapshotColumn> = new_et
158 .columns
159 .iter()
160 .map(|c| (c.column_name.as_str(), c))
161 .collect();
162
163 let old_col_names: HashSet<&str> = old_cols.keys().copied().collect();
164 let new_col_names: HashSet<&str> = new_cols.keys().copied().collect();
165
166 for col_name in new_col_names.difference(&old_col_names) {
168 let col = new_cols[col_name];
169 changes.push(SchemaChange::AddColumn {
170 table: table.clone(),
171 column: (*col).clone(),
172 });
173 changes.extend(diff_indexes(table, &SnapshotColumn::default(), col));
174 }
175
176 changes.extend(diff_foreign_keys(table, old_et, new_et));
178
179 for col_name in old_col_names.difference(&new_col_names) {
181 changes.push(SchemaChange::DropColumn {
182 table: table.clone(),
183 column_name: col_name.to_string(),
184 });
185 }
186
187 for col_name in old_col_names.intersection(&new_col_names) {
189 let old_col = old_cols[col_name];
190 let new_col = new_cols[col_name];
191
192 changes.extend(diff_indexes(table, old_col, new_col));
194
195 if !columns_structurally_equal(old_col, new_col) {
197 changes.push(SchemaChange::AlterColumn {
198 table: table.clone(),
199 column_name: col_name.to_string(),
200 old: Box::new((*old_col).clone()),
201 new: Box::new((*new_col).clone()),
202 });
203 }
204 }
205 }
206
207 changes
208 }
209
210 pub(super) fn append_create_table_fks(
211 changes: &mut Vec<SchemaChange>,
212 table: &str,
213 columns: &[SnapshotColumn],
214 ) {
215 for col in columns {
216 if let Some((ref_table, ref_col)) = super::diff::fk_target(col) {
217 changes.push(SchemaChange::AddForeignKey {
218 table: table.to_string(),
219 column: col.column_name.clone(),
220 referenced_table: ref_table,
221 referenced_column: ref_col,
222 on_delete: col.fk_on_delete.clone(),
223 });
224 }
225 }
226 }
227
228 pub(super) fn append_create_table_indexes(
229 changes: &mut Vec<SchemaChange>,
230 table: &str,
231 columns: &[SnapshotColumn],
232 ) {
233 for col in columns {
234 if col.is_unique {
235 changes.push(SchemaChange::CreateIndex {
236 table: table.to_string(),
237 column: col.column_name.clone(),
238 is_unique: true,
239 });
240 } else if col.has_index {
241 changes.push(SchemaChange::CreateIndex {
242 table: table.to_string(),
243 column: col.column_name.clone(),
244 is_unique: false,
245 });
246 }
247 }
248 }
249}