1use std::borrow::Cow;
2
3use toasty_core::{
4 driver::Capability,
5 schema::db::{
6 ColumnsDiff, ColumnsDiffItem, IndicesDiffItem, Schema, SchemaDiff, Table, TablesDiffItem,
7 },
8};
9
10use crate::stmt::{AlterColumnChanges, AlterTable, AlterTableAction, DropTable, Name, Statement};
11
12pub struct MigrationStatement<'a> {
18 statement: Statement,
19 schema: Cow<'a, Schema>,
20}
21
22impl<'a> MigrationStatement<'a> {
23 fn new(statement: Statement, schema: Cow<'a, Schema>) -> Self {
24 MigrationStatement { statement, schema }
25 }
26
27 pub fn from_diff(schema_diff: &'a SchemaDiff<'a>, capability: &Capability) -> Vec<Self> {
34 let mut result = Vec::new();
35 for table in schema_diff.tables().iter() {
36 match table {
37 TablesDiffItem::CreateTable(table) => {
38 result.push(Self::new(
39 Statement::create_table(table, capability),
40 Cow::Borrowed(schema_diff.next()),
41 ));
42 for index in &table.indices {
43 result.push(Self::new(
44 Statement::create_index(index),
45 Cow::Borrowed(schema_diff.next()),
46 ));
47 }
48 }
49 TablesDiffItem::DropTable(table) => result.push(Self::new(
50 Statement::drop_table(table),
51 Cow::Borrowed(schema_diff.previous()),
52 )),
53 TablesDiffItem::AlterTable {
54 previous,
55 next,
56 columns,
57 indices,
58 ..
59 } => {
60 let mut schema = Cow::Borrowed(schema_diff.previous());
61 if previous.name != next.name {
62 result.push(Self::new(
63 Statement::alter_table_rename_to(previous, &next.name),
64 schema.clone(),
65 ));
66 schema.to_mut().table_mut(previous.id).name = next.name.clone();
67 }
68
69 let needs_recreation = !capability.schema_mutations.alter_column_type
72 && columns.iter().any(|item| {
73 matches!(
74 item,
75 ColumnsDiffItem::AlterColumn {
76 previous: prev_col,
77 next: next_col
78 } if AlterColumnChanges::from_diff(prev_col, next_col).has_type_change()
79 )
80 });
81
82 if needs_recreation {
83 Self::emit_table_recreation(
84 &mut result,
85 schema,
86 previous,
87 next,
88 columns,
89 capability,
90 );
91 } else {
92 Self::emit_column_changes(&mut result, schema, columns, capability);
93 }
94
95 for item in indices.iter() {
97 match item {
98 IndicesDiffItem::CreateIndex(index) => {
99 result.push(Self::new(
100 Statement::create_index(index),
101 Cow::Borrowed(schema_diff.next()),
102 ));
103 }
104 IndicesDiffItem::DropIndex(index) => {
105 result.push(Self::new(
106 Statement::drop_index(index),
107 Cow::Borrowed(schema_diff.previous()),
108 ));
109 }
110 IndicesDiffItem::AlterIndex { previous, next } => {
111 result.push(Self::new(
112 Statement::drop_index(previous),
113 Cow::Borrowed(schema_diff.previous()),
114 ));
115 result.push(Self::new(
116 Statement::create_index(next),
117 Cow::Borrowed(schema_diff.next()),
118 ));
119 }
120 }
121 }
122 }
123 }
124 }
125 result
126 }
127
128 fn emit_table_recreation(
129 result: &mut Vec<Self>,
130 schema: Cow<'a, Schema>,
131 previous: &Table,
132 next: &Table,
133 columns: &ColumnsDiff<'_>,
134 capability: &Capability,
135 ) {
136 let current_name = schema.table(previous.id).name.clone();
137 let temp_name = format!("_toasty_new_{}", current_name);
138
139 result.push(Self::new(
141 Statement::pragma_disable_foreign_keys(),
142 schema.clone(),
143 ));
144
145 let temp_schema = {
147 let mut s = schema.as_ref().clone();
148 let t = s.table_mut(next.id);
149 t.name = temp_name.clone();
150 t.columns = next.columns.clone();
151 t.primary_key = next.primary_key.clone();
152 s
153 };
154 result.push(Self::new(
155 Statement::create_table(next, capability),
156 Cow::Owned(temp_schema),
157 ));
158
159 let column_mappings: Vec<(Name, Name)> = next
161 .columns
162 .iter()
163 .filter(|col| {
164 !columns
166 .iter()
167 .any(|item| matches!(item, ColumnsDiffItem::AddColumn(c) if c.id == col.id))
168 })
169 .map(|col| {
170 let target_name = Name::from(&col.name[..]);
171 let source_name = columns
173 .iter()
174 .find_map(|item| match item {
175 ColumnsDiffItem::AlterColumn {
176 previous: prev_col,
177 next: next_col,
178 } if next_col.id == col.id && prev_col.name != next_col.name => {
179 Some(Name::from(&prev_col.name[..]))
180 }
181 _ => None,
182 })
183 .unwrap_or_else(|| Name::from(&col.name[..]));
184 (target_name, source_name)
185 })
186 .collect();
187
188 result.push(Self::new(
189 Statement::copy_table(
190 Name::from(current_name.as_str()),
191 Name::from(temp_name.as_str()),
192 column_mappings,
193 ),
194 schema.clone(),
195 ));
196
197 result.push(Self::new(
199 DropTable {
200 name: Name::from(current_name.as_str()),
201 if_exists: false,
202 }
203 .into(),
204 schema.clone(),
205 ));
206
207 result.push(Self::new(
209 AlterTable {
210 name: Name::from(temp_name.as_str()),
211 action: AlterTableAction::RenameTo(Name::from(current_name.as_str())),
212 }
213 .into(),
214 schema.clone(),
215 ));
216
217 result.push(Self::new(
219 Statement::pragma_enable_foreign_keys(),
220 schema.clone(),
221 ));
222 }
223
224 fn emit_column_changes(
225 result: &mut Vec<Self>,
226 schema: Cow<'a, Schema>,
227 columns: &ColumnsDiff<'_>,
228 capability: &Capability,
229 ) {
230 for item in columns.iter() {
231 match item {
232 ColumnsDiffItem::AddColumn(column) => {
233 result.push(Self::new(
234 Statement::add_column(column, capability),
235 schema.clone(),
236 ));
237 }
238 ColumnsDiffItem::DropColumn(column) => {
239 result.push(Self::new(Statement::drop_column(column), schema.clone()));
240 }
241 ColumnsDiffItem::AlterColumn {
242 previous,
243 next: col_next,
244 } => {
245 let changes = AlterColumnChanges::from_diff(previous, col_next);
246 let changes = if capability.schema_mutations.alter_column_properties_atomic {
247 vec![changes]
248 } else {
249 changes.split()
250 };
251
252 for changes in changes {
253 result.push(Self::new(
254 Statement::alter_column(previous, changes, capability),
255 schema.clone(),
256 ));
257 }
258 }
259 }
260 }
261 }
262
263 pub fn statement(&self) -> &Statement {
265 &self.statement
266 }
267
268 pub fn schema(&self) -> &Schema {
270 &self.schema
271 }
272}