rust_ef/migration/
engine_ops.rs1use super::dialect::MigrationDialect;
4use super::engine::MigrationEngine;
5use super::history::{
6 create_migration_history_table_sql, seed_insert_sql, split_sql_statements,
7 MigrationHistoryEntry,
8};
9use super::types::Migration;
10use crate::entity_snapshot::EntitySnapshot;
11use crate::error::{EFError, EFResult};
12use crate::metadata::EntityTypeMeta;
13use crate::provider::IDatabaseProvider;
14
15impl MigrationEngine {
16 pub async fn ensure_history_table(&self, provider: &dyn IDatabaseProvider) -> EFResult<()> {
18 let sql = create_migration_history_table_sql(self.dialect);
19 provider.execute_migration_command(&sql).await
20 }
21
22 pub async fn apply(
30 &self,
31 provider: &dyn IDatabaseProvider,
32 migration: &Migration,
33 ) -> EFResult<()> {
34 if self.is_applied(provider, &migration.id).await? {
35 return Ok(());
36 }
37 self.ensure_history_table(provider).await?;
38 let sql = migration.up_sql.replace("{migration_id}", &migration.id);
39 self.execute_script(provider, &sql).await
40 }
41
42 async fn execute_script(&self, provider: &dyn IDatabaseProvider, sql: &str) -> EFResult<()> {
44 let statements = split_sql_statements(sql, self.dialect);
45 if statements.is_empty() {
46 return Ok(());
47 }
48
49 if self.dialect == MigrationDialect::MySql {
53 for statement in statements {
54 provider.execute_migration_command(&statement).await?;
55 }
56 return Ok(());
57 }
58
59 let mut conn = provider.get_connection().await?;
60 conn.begin_transaction().await?;
61 for statement in &statements {
62 if let Err(err) = conn.execute(statement, &[]).await {
63 let _ = conn.rollback_transaction().await;
64 return Err(err);
65 }
66 }
67 if let Err(err) = conn.commit_transaction().await {
68 let _ = conn.rollback_transaction().await;
69 return Err(err);
70 }
71 Ok(())
72 }
73
74 pub async fn revert(
76 &self,
77 provider: &dyn IDatabaseProvider,
78 migration: &Migration,
79 ) -> EFResult<()> {
80 if !self.is_applied(provider, &migration.id).await? {
81 return Err(EFError::migration(format!(
82 "migration '{}' is not applied",
83 migration.id
84 )));
85 }
86 let sql = migration.down_sql.replace("{migration_id}", &migration.id);
87 self.execute_script(provider, &sql).await
88 }
89
90 pub async fn get_applied_migrations(
92 &self,
93 provider: &dyn IDatabaseProvider,
94 ) -> EFResult<Vec<MigrationHistoryEntry>> {
95 self.ensure_history_table(provider).await?;
96 let q = |s: &str| self.dialect.quote(s);
97 let table = q(super::history::MIGRATION_HISTORY_TABLE);
98 let sql = format!(
99 "SELECT {}, {} FROM {} ORDER BY {}",
100 q("migration_id"),
101 q("product_version"),
102 table,
103 q("migration_id")
104 );
105 let mut conn = provider.get_connection().await?;
106 let rows = conn.query(&sql, &[]).await?;
107 Ok(rows
108 .into_iter()
109 .map(|row| MigrationHistoryEntry {
110 migration_id: row
111 .first()
112 .and_then(|v| String::try_from(v.clone()).ok())
113 .unwrap_or_default(),
114 product_version: row
115 .get(1)
116 .and_then(|v| String::try_from(v.clone()).ok())
117 .unwrap_or_default(),
118 })
119 .collect())
120 }
121
122 pub async fn is_applied(
124 &self,
125 provider: &dyn IDatabaseProvider,
126 migration_id: &str,
127 ) -> EFResult<bool> {
128 let applied = self.get_applied_migrations(provider).await?;
129 Ok(applied.iter().any(|e| e.migration_id == migration_id))
130 }
131
132 pub async fn apply_pending(
134 &self,
135 provider: &dyn IDatabaseProvider,
136 migrations: &[Migration],
137 ) -> EFResult<usize> {
138 let applied: std::collections::HashSet<String> = self
139 .get_applied_migrations(provider)
140 .await?
141 .into_iter()
142 .map(|e| e.migration_id)
143 .collect();
144 let mut count = 0usize;
145 for migration in migrations {
146 if applied.contains(&migration.id) {
147 continue;
148 }
149 self.apply(provider, migration).await?;
150 count += 1;
151 }
152 Ok(count)
153 }
154
155 pub async fn revert_last(
157 &self,
158 provider: &dyn IDatabaseProvider,
159 migrations: &[Migration],
160 ) -> EFResult<Option<String>> {
161 let applied = self.get_applied_migrations(provider).await?;
162 let last = applied.last().map(|e| e.migration_id.clone());
163 let Some(last_id) = last else {
164 return Ok(None);
165 };
166 let migration = migrations.iter().find(|m| m.id == last_id).ok_or_else(|| {
167 EFError::migration(format!(
168 "applied migration '{}' not found in local migration set",
169 last_id
170 ))
171 })?;
172 self.revert(provider, migration).await?;
173 Ok(Some(last_id))
174 }
175
176 pub async fn revert_to_target(
183 &self,
184 provider: &dyn IDatabaseProvider,
185 migrations: &[Migration],
186 target: Option<&str>,
187 ) -> EFResult<Vec<String>> {
188 let applied = self.get_applied_migrations(provider).await?;
189 let applied_ids: Vec<&str> = applied.iter().map(|e| e.migration_id.as_str()).collect();
190
191 let to_revert: Vec<String> = match target {
192 Some(t) => {
193 let idx = applied_ids.iter().position(|id| *id == t).ok_or_else(|| {
194 EFError::migration(format!("target migration '{t}' is not applied"))
195 })?;
196 applied_ids[idx + 1..]
197 .iter()
198 .map(|s| s.to_string())
199 .collect()
200 }
201 None => applied_ids.iter().map(|s| s.to_string()).collect(),
202 };
203
204 let mut reverted = Vec::with_capacity(to_revert.len());
205 for id in to_revert.iter().rev() {
206 let migration = migrations.iter().find(|m| m.id == *id).ok_or_else(|| {
207 EFError::migration(format!(
208 "applied migration '{id}' not found in local migration set"
209 ))
210 })?;
211 self.revert(provider, migration).await?;
212 reverted.push(id.clone());
213 }
214 Ok(reverted)
215 }
216
217 pub fn generate_script(
229 migrations: &[Migration],
230 from: Option<&str>,
231 to: Option<&str>,
232 ) -> EFResult<String> {
233 let from_idx: i64 = match from {
236 Some(f) => migrations.iter().position(|m| m.id == f).ok_or_else(|| {
237 EFError::migration(format!("from migration '{f}' not found in local set"))
238 })? as i64,
239 None => -1,
240 };
241 let to_idx: i64 = match to {
242 Some(t) => migrations.iter().position(|m| m.id == t).ok_or_else(|| {
243 EFError::migration(format!("to migration '{t}' not found in local set"))
244 })? as i64,
245 None => migrations.len() as i64 - 1,
246 };
247
248 let mut sql = String::new();
249 if from_idx < to_idx {
250 sql.push_str("-- Migration script (forward)\n\n");
252 let start = (from_idx + 1) as usize;
253 let end = (to_idx + 1) as usize; for m in &migrations[start..end] {
255 sql.push_str(&format!("-- Up: {}\n", m.id));
256 sql.push_str(&m.up_sql.replace("{migration_id}", &m.id));
257 sql.push('\n');
258 }
259 } else if from_idx > to_idx {
260 sql.push_str("-- Migration script (reverse)\n\n");
262 let start = to_idx as usize + 1; let end = from_idx as usize + 1; for m in migrations[start..end].iter().rev() {
265 sql.push_str(&format!("-- Down: {}\n", m.id));
266 sql.push_str(&m.down_sql.replace("{migration_id}", &m.id));
267 sql.push('\n');
268 }
269 } else {
270 sql.push_str("-- Nothing to do (from == to)\n");
271 }
272 Ok(sql)
273 }
274
275 pub async fn ensure_created(
279 &self,
280 provider: &dyn IDatabaseProvider,
281 entity_types: &[EntityTypeMeta],
282 ) -> EFResult<()> {
283 let snapshot = self.create_snapshot("__ensure_created__", entity_types);
284 let changes = self.initial_create(&snapshot);
285 let sql = self.generate_ddl_sql(&changes);
286 self.execute_script(provider, &sql).await
287 }
288
289 pub async fn ensure_deleted(
292 &self,
293 provider: &dyn IDatabaseProvider,
294 entity_types: &[EntityTypeMeta],
295 ) -> EFResult<()> {
296 let q = |s: &str| self.dialect.quote(s);
297 for meta in entity_types.iter().filter(|m| !m.is_view) {
298 let sql = format!("DROP TABLE IF EXISTS {};", q(meta.table_name.as_ref()));
299 provider.execute_migration_command(&sql).await?;
300 }
301 Ok(())
302 }
303
304 pub async fn apply_seed_data(
306 &self,
307 provider: &dyn IDatabaseProvider,
308 meta: &EntityTypeMeta,
309 rows: &[EntitySnapshot],
310 ) -> EFResult<()> {
311 if rows.is_empty() {
312 return Ok(());
313 }
314
315 let gen = provider.sql_generator();
316 let scalar_props: Vec<_> = meta.writable_scalar_properties().collect();
317 if scalar_props.is_empty() {
318 return Ok(());
319 }
320
321 let col_names: Vec<&str> = scalar_props
322 .iter()
323 .map(|p| p.column_name.as_ref())
324 .collect();
325
326 let mut conn = provider.get_connection().await?;
327 for row in rows {
328 let params: Vec<crate::provider::DbValue> = scalar_props
329 .iter()
330 .map(|p| {
331 row.get(p.field_name.as_ref())
332 .cloned()
333 .unwrap_or(crate::provider::DbValue::Null)
334 })
335 .collect();
336
337 let sql = seed_insert_sql(self.dialect, meta.table_name.as_ref(), &col_names, gen);
338 conn.execute(&sql, ¶ms).await?;
339 }
340 Ok(())
341 }
342}