1use super::dialect::MigrationDialect;
4use super::diff::index_name;
5use super::engine::MigrationEngine;
6use super::history::{MIGRATION_HISTORY_TABLE, PRODUCT_VERSION};
7use super::schema_change::SchemaChange;
8use super::types::{ModelSnapshot, SnapshotColumn};
9
10impl MigrationEngine {
11 pub(crate) fn initial_create_with_fks(&self, current: &ModelSnapshot) -> Vec<SchemaChange> {
12 let mut changes = self.initial_create(current);
13 for et in ¤t.entity_types {
14 Self::append_create_table_fks(&mut changes, &et.table_name, &et.columns);
15 Self::append_create_table_indexes(&mut changes, &et.table_name, &et.columns);
16 }
17 changes
18 }
19
20 pub fn foreign_key_name(table: &str, column: &str, referenced_table: &str) -> String {
26 format!("fk_{}_{}_{}", table, column, referenced_table)
27 }
28
29 pub fn index_name(table: &str, column: &str) -> String {
31 format!("ix_{}_{}", table, column)
32 }
33
34 pub fn generate_alter_column_sql(
35 &self,
36 table: &str,
37 column_name: &str,
38 new: &SnapshotColumn,
39 ) -> String {
40 let q = |s: &str| self.dialect.quote(s);
41 let col_type = self.dialect.map_column_type(new);
42 match self.dialect {
43 MigrationDialect::Postgres => {
44 let nullable = if new.is_required {
45 "SET NOT NULL"
46 } else {
47 "DROP NOT NULL"
48 };
49 format!(
50 "ALTER TABLE {} ALTER COLUMN {} TYPE {};\nALTER TABLE {} ALTER COLUMN {} {};\n",
51 q(table),
52 q(column_name),
53 col_type,
54 q(table),
55 q(column_name),
56 nullable
57 )
58 }
59 MigrationDialect::MySql => {
60 let nullable = if new.is_required { "NOT NULL" } else { "NULL" };
61 format!(
62 "ALTER TABLE {} MODIFY COLUMN {} {} {};\n",
63 q(table),
64 q(column_name),
65 col_type,
66 nullable
67 )
68 }
69 MigrationDialect::Sqlite => format!(
70 "-- SQLite does not support ALTER COLUMN type changes; \
71 rebuild table manually for {}.{}\n",
72 q(table),
73 q(column_name)
74 ),
75 }
76 }
77
78 pub(crate) fn generate_up_sql(&self, changes: &[SchemaChange]) -> String {
79 self.generate_up_sql_inner(changes, true)
80 }
81
82 pub(crate) fn generate_ddl_sql(&self, changes: &[SchemaChange]) -> String {
83 self.generate_up_sql_inner(changes, false)
84 }
85
86 fn generate_up_sql_inner(&self, changes: &[SchemaChange], record_history: bool) -> String {
87 let mut sql = String::from("-- Up Migration (auto-generated by rust-ef)\n\n");
88 let q = |s: &str| self.dialect.quote(s);
89 let create_kw = if record_history {
90 "CREATE TABLE"
91 } else {
92 "CREATE TABLE IF NOT EXISTS"
93 };
94
95 for change in changes {
96 match change {
97 SchemaChange::CreateTable { table, columns } => {
98 if self.dialect == MigrationDialect::Postgres {
100 for c in columns {
101 if c.is_sequence {
102 if let Some(seq_name) = &c.sequence_name {
103 sql.push_str(&format!(
104 "CREATE SEQUENCE IF NOT EXISTS {};\n",
105 q(seq_name)
106 ));
107 }
108 }
109 }
110 }
111
112 sql.push_str(&format!("{} {} (\n", create_kw, q(table)));
113
114 let pk_columns: Vec<&str> = columns
116 .iter()
117 .filter(|c| c.is_primary_key)
118 .map(|c| c.column_name.as_str())
119 .collect();
120
121 let col_defs: Vec<String> = columns
122 .iter()
123 .map(|c| {
124 let nullable = if c.is_required { "NOT NULL" } else { "NULL" };
125 let col_type = self.dialect.map_column_type(c);
126 if c.is_sequence && self.dialect == MigrationDialect::Postgres {
128 if let Some(seq_name) = &c.sequence_name {
129 return format!(
130 "{} {} DEFAULT nextval('{}') {}",
131 q(&c.column_name),
132 col_type,
133 seq_name,
134 nullable
135 );
136 }
137 }
138 [q(&c.column_name), col_type, nullable.to_string()]
140 .into_iter()
141 .filter(|s| !s.is_empty())
142 .collect::<Vec<_>>()
143 .join(" ")
144 })
145 .collect();
146 sql.push_str(&format!(" {}\n", col_defs.join(",\n ")));
147
148 if pk_columns.len() == 1 {
150 sql.push_str(&format!(" ,PRIMARY KEY ({})\n", q(pk_columns[0])));
151 } else if pk_columns.len() > 1 {
152 let pk_list: Vec<String> = pk_columns.iter().map(|c| q(c)).collect();
153 sql.push_str(&format!(" ,PRIMARY KEY ({})\n", pk_list.join(", ")));
154 }
155
156 sql.push_str(");\n\n");
157 }
158 SchemaChange::DropTable { table } => {
159 sql.push_str(&format!("DROP TABLE IF EXISTS {};\n", q(table)));
160 }
161 SchemaChange::AddColumn { table, column } => {
162 let col_type = self.dialect.map_column_type(column);
163 let nullable = if column.is_required {
164 "NOT NULL"
165 } else {
166 "NULL"
167 };
168 sql.push_str(&format!(
169 "ALTER TABLE {} ADD COLUMN {} {} {};\n",
170 q(table),
171 q(&column.column_name),
172 col_type,
173 nullable
174 ));
175 }
176 SchemaChange::DropColumn { table, column_name } => {
177 sql.push_str(&format!(
178 "ALTER TABLE {} DROP COLUMN IF EXISTS {};\n",
179 q(table),
180 q(column_name)
181 ));
182 }
183 SchemaChange::AlterColumn {
184 table,
185 column_name,
186 old: _,
187 new,
188 } => {
189 sql.push_str(&self.generate_alter_column_sql(table, column_name, new));
190 }
191 SchemaChange::AddForeignKey {
192 table,
193 column,
194 referenced_table,
195 referenced_column,
196 on_delete,
197 } => {
198 let fk_name = Self::foreign_key_name(table, column, referenced_table);
199 let on_delete_clause = on_delete
200 .as_deref()
201 .map(|c| format!(" ON DELETE {}", c))
202 .unwrap_or_default();
203 sql.push_str(&format!(
204 "ALTER TABLE {} ADD CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {} ({}){};\n",
205 q(table),
206 q(&fk_name),
207 q(column),
208 q(referenced_table),
209 q(referenced_column),
210 on_delete_clause
211 ));
212 }
213 SchemaChange::DropForeignKey {
214 table,
215 column,
216 referenced_table,
217 } => {
218 let fk_name = Self::foreign_key_name(table, column, referenced_table);
219 match self.dialect {
220 MigrationDialect::MySql => sql.push_str(&format!(
221 "ALTER TABLE {} DROP FOREIGN KEY {};\n",
222 q(table),
223 q(&fk_name)
224 )),
225 _ => sql.push_str(&format!(
226 "ALTER TABLE {} DROP CONSTRAINT IF EXISTS {};\n",
227 q(table),
228 q(&fk_name)
229 )),
230 }
231 }
232 SchemaChange::CreateIndex {
233 table,
234 column,
235 is_unique,
236 } => {
237 let unique_kw = if *is_unique { "UNIQUE " } else { "" };
238 let idx_name = index_name(table, column);
239 sql.push_str(&format!(
240 "CREATE {unique_kw}INDEX {} ON {} ({});\n",
241 q(&idx_name),
242 q(table),
243 q(column)
244 ));
245 }
246 SchemaChange::DropIndex {
247 table,
248 column,
249 is_unique: _,
250 } => {
251 let idx_name = index_name(table, column);
252 match self.dialect {
253 MigrationDialect::MySql => {
254 sql.push_str(&format!("DROP INDEX {} ON {};\n", q(&idx_name), q(table)))
255 }
256 _ => sql.push_str(&format!("DROP INDEX IF EXISTS {};\n", q(&idx_name))),
257 }
258 }
259 }
260 }
261
262 if record_history {
263 sql.push_str(&format!(
264 "INSERT INTO {}(migration_id, product_version) VALUES ('{{migration_id}}', '{}');\n",
265 q(MIGRATION_HISTORY_TABLE),
266 PRODUCT_VERSION
267 ));
268 }
269
270 sql
271 }
272
273 pub(super) fn generate_down_sql(&self, changes: &[SchemaChange]) -> String {
274 let mut sql = String::from("-- Down Migration (auto-generated by rust-ef)\n\n");
275 let q = |s: &str| self.dialect.quote(s);
276
277 for change in changes.iter().rev() {
279 match change {
280 SchemaChange::CreateTable { table, .. } => {
281 sql.push_str(&format!("DROP TABLE IF EXISTS {};\n", q(table)));
282 }
283 SchemaChange::DropTable { table } => {
284 sql.push_str(&format!(
285 "-- WARNING: Cannot restore table {} (original schema unknown)\n",
286 q(table)
287 ));
288 }
289 SchemaChange::AddColumn { table, column } => {
290 sql.push_str(&format!(
291 "ALTER TABLE {} DROP COLUMN IF EXISTS {};\n",
292 q(table),
293 q(&column.column_name)
294 ));
295 }
296 SchemaChange::DropColumn { table, column_name } => {
297 sql.push_str(&format!(
298 "-- WARNING: Cannot restore column {} on {} (original type unknown)\n",
299 q(column_name),
300 q(table)
301 ));
302 }
303 SchemaChange::AlterColumn {
304 table,
305 column_name,
306 old,
307 new: _,
308 } => {
309 sql.push_str(&self.generate_alter_column_sql(table, column_name, old));
310 }
311 SchemaChange::AddForeignKey {
312 table,
313 column,
314 referenced_table,
315 ..
316 } => {
317 let fk_name = Self::foreign_key_name(table, column, referenced_table);
318 match self.dialect {
319 MigrationDialect::MySql => sql.push_str(&format!(
320 "ALTER TABLE {} DROP FOREIGN KEY {};\n",
321 q(table),
322 q(&fk_name)
323 )),
324 _ => sql.push_str(&format!(
325 "ALTER TABLE {} DROP CONSTRAINT IF EXISTS {};\n",
326 q(table),
327 q(&fk_name)
328 )),
329 }
330 }
331 SchemaChange::DropForeignKey {
332 table,
333 column,
334 referenced_table,
335 ..
336 } => {
337 sql.push_str(&format!(
338 "-- WARNING: Cannot restore foreign key constraint {} on {}.{}\n",
339 q(&Self::foreign_key_name(table, column, referenced_table)),
340 q(table),
341 q(column)
342 ));
343 }
344 SchemaChange::CreateIndex {
345 table,
346 column,
347 is_unique: _,
348 } => {
349 let idx_name = index_name(table, column);
350 match self.dialect {
351 MigrationDialect::MySql => {
352 sql.push_str(&format!("DROP INDEX {} ON {};\n", q(&idx_name), q(table)))
353 }
354 _ => sql.push_str(&format!("DROP INDEX IF EXISTS {};\n", q(&idx_name))),
355 }
356 }
357 SchemaChange::DropIndex {
358 table,
359 column,
360 is_unique,
361 } => {
362 let unique_kw = if *is_unique { "UNIQUE " } else { "" };
363 let idx_name = index_name(table, column);
364 sql.push_str(&format!(
365 "CREATE {unique_kw}INDEX {} ON {} ({});\n",
366 q(&idx_name),
367 q(table),
368 q(column)
369 ));
370 }
371 }
372 }
373
374 sql.push_str(&format!(
375 "DELETE FROM {} WHERE migration_id = '{{migration_id}}';\n",
376 q(MIGRATION_HISTORY_TABLE)
377 ));
378
379 sql
380 }
381}