1use super::{MigrationAction, MigrationPlan};
2use crate::schema::TableName;
3
4impl MigrationPlan {
5 pub fn with_prefix(self, prefix: &str) -> Self {
8 if prefix.is_empty() {
9 return self;
10 }
11 Self {
12 actions: self
13 .actions
14 .into_iter()
15 .map(|action| action.with_prefix(prefix))
16 .collect(),
17 ..self
18 }
19 }
20}
21
22impl MigrationAction {
23 pub fn with_prefix(self, prefix: &str) -> Self {
25 if prefix.is_empty() {
26 return self;
27 }
28
29 prefix_migration_action(self, prefix)
30 }
31}
32
33fn prefix_migration_action(action: MigrationAction, prefix: &str) -> MigrationAction {
34 match action {
35 MigrationAction::CreateTable {
36 table,
37 columns,
38 constraints,
39 } => MigrationAction::CreateTable {
40 table: add_prefix(table, prefix),
41 columns,
42 constraints: constraints
43 .into_iter()
44 .map(|c| c.with_prefix(prefix))
45 .collect(),
46 },
47 MigrationAction::DeleteTable { table } => MigrationAction::DeleteTable {
48 table: add_prefix(table, prefix),
49 },
50 MigrationAction::RenameTable { from, to } => MigrationAction::RenameTable {
51 from: add_prefix(from, prefix),
52 to: add_prefix(to, prefix),
53 },
54 MigrationAction::RawSql { sql } => MigrationAction::RawSql { sql },
55 action => prefix_column_or_constraint_action(action, prefix),
56 }
57}
58
59fn prefix_column_or_constraint_action(action: MigrationAction, prefix: &str) -> MigrationAction {
60 match action {
61 MigrationAction::AddColumn {
62 table,
63 column,
64 fill_with,
65 } => MigrationAction::AddColumn {
66 table: add_prefix(table, prefix),
67 column,
68 fill_with,
69 },
70 MigrationAction::RenameColumn { table, from, to } => MigrationAction::RenameColumn {
71 table: add_prefix(table, prefix),
72 from,
73 to,
74 },
75 MigrationAction::DeleteColumn { table, column } => MigrationAction::DeleteColumn {
76 table: add_prefix(table, prefix),
77 column,
78 },
79 MigrationAction::ModifyColumnType {
80 table,
81 column,
82 new_type,
83 fill_with,
84 narrowing_strategy,
85 timezone,
86 } => MigrationAction::ModifyColumnType {
87 table: add_prefix(table, prefix),
88 column,
89 new_type,
90 fill_with,
91 narrowing_strategy,
92 timezone,
93 },
94 MigrationAction::ModifyColumnNullable {
95 table,
96 column,
97 nullable,
98 fill_with,
99 delete_null_rows,
100 } => MigrationAction::ModifyColumnNullable {
101 table: add_prefix(table, prefix),
102 column,
103 nullable,
104 fill_with,
105 delete_null_rows,
106 },
107 action => prefix_remaining_action(action, prefix),
108 }
109}
110
111fn prefix_remaining_action(action: MigrationAction, prefix: &str) -> MigrationAction {
112 match action {
113 MigrationAction::ModifyColumnDefault {
114 table,
115 column,
116 new_default,
117 backfill,
118 } => MigrationAction::ModifyColumnDefault {
119 table: add_prefix(table, prefix),
120 column,
121 new_default,
122 backfill,
123 },
124 MigrationAction::ModifyColumnComment {
125 table,
126 column,
127 new_comment,
128 } => MigrationAction::ModifyColumnComment {
129 table: add_prefix(table, prefix),
130 column,
131 new_comment,
132 },
133 MigrationAction::AddConstraint { table, constraint } => MigrationAction::AddConstraint {
134 table: format!("{prefix}{table}").into(),
135 constraint: constraint.with_prefix(prefix),
136 },
137 MigrationAction::RemoveConstraint { table, constraint } => {
138 MigrationAction::RemoveConstraint {
139 table: add_prefix(table, prefix),
140 constraint: constraint.with_prefix(prefix),
141 }
142 }
143 MigrationAction::ReplaceConstraint { table, from, to } => {
144 MigrationAction::ReplaceConstraint {
145 table: add_prefix(table, prefix),
146 from: from.with_prefix(prefix),
147 to: to.with_prefix(prefix),
148 }
149 }
150 other => other,
151 }
152}
153
154fn add_prefix(table: TableName, prefix: &str) -> TableName {
155 let mut table = table.into_inner();
156 table.insert_str(0, prefix);
157 table.into()
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
165
166 #[test]
167 fn raw_sql_with_prefix_is_a_noop_on_sql_body() {
168 let action = MigrationAction::RawSql {
170 sql: "SELECT 1".to_string(),
171 };
172 let prefixed = action.with_prefix("p_");
173 match prefixed {
174 MigrationAction::RawSql { sql } => assert_eq!(sql, "SELECT 1"),
175 other => panic!("expected RawSql, got {other:?}"),
176 }
177 }
178
179 #[test]
180 fn raw_sql_within_plan_with_prefix_preserves_sql() {
181 let plan = MigrationPlan {
183 id: String::new(),
184 comment: None,
185 created_at: None,
186 version: 1,
187 actions: vec![MigrationAction::RawSql {
188 sql: "UPDATE x SET y = 1".to_string(),
189 }],
190 };
191 let prefixed = plan.with_prefix("tenant_");
192 match prefixed.actions.into_iter().next() {
193 Some(MigrationAction::RawSql { sql }) => assert_eq!(sql, "UPDATE x SET y = 1"),
194 other => panic!("expected RawSql, got {other:?}"),
195 }
196 }
197
198 #[test]
199 fn remap_enum_values_with_prefix_passes_through_catch_all() {
200 let action = MigrationAction::RemapEnumValues {
205 table: "user".into(),
206 column: "status".into(),
207 mapping: vec![(1_i64, 2_i64)].into_iter().collect(),
208 };
209 let prefixed = action.with_prefix("t_");
210 match prefixed {
211 MigrationAction::RemapEnumValues {
212 table,
213 column,
214 mapping,
215 } => {
216 assert_eq!(table.as_str(), "user");
221 assert_eq!(column.as_str(), "status");
222 assert_eq!(mapping, vec![(1_i64, 2_i64)].into_iter().collect());
223 }
224 other => panic!("expected RemapEnumValues, got {other:?}"),
225 }
226 }
227}