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