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