rust_ef/migration/
engine_ops.rs1use super::engine::MigrationEngine;
4use super::history::{
5 create_migration_history_table_sql, seed_insert_sql, split_sql_statements,
6 MigrationHistoryEntry,
7};
8use super::types::Migration;
9use crate::error::{EFError, EFResult};
10use crate::metadata::EntityTypeMeta;
11use crate::provider::IDatabaseProvider;
12
13impl MigrationEngine {
14 pub async fn ensure_history_table(&self, provider: &dyn IDatabaseProvider) -> EFResult<()> {
16 let sql = create_migration_history_table_sql(self.dialect);
17 provider.execute_migration_command(&sql).await
18 }
19
20 pub async fn apply(
22 &self,
23 provider: &dyn IDatabaseProvider,
24 migration: &Migration,
25 ) -> EFResult<()> {
26 if self.is_applied(provider, &migration.id).await? {
27 return Ok(());
28 }
29 self.ensure_history_table(provider).await?;
30 let sql = migration.up_sql.replace("{migration_id}", &migration.id);
31 for statement in split_sql_statements(&sql) {
32 if !statement.is_empty() {
33 provider.execute_migration_command(&statement).await?;
34 }
35 }
36 Ok(())
37 }
38
39 pub async fn revert(
41 &self,
42 provider: &dyn IDatabaseProvider,
43 migration: &Migration,
44 ) -> EFResult<()> {
45 if !self.is_applied(provider, &migration.id).await? {
46 return Err(EFError::migration(format!(
47 "migration '{}' is not applied",
48 migration.id
49 )));
50 }
51 let sql = migration.down_sql.replace("{migration_id}", &migration.id);
52 for statement in split_sql_statements(&sql) {
53 if !statement.is_empty() {
54 provider.execute_migration_command(&statement).await?;
55 }
56 }
57 Ok(())
58 }
59
60 pub async fn get_applied_migrations(
62 &self,
63 provider: &dyn IDatabaseProvider,
64 ) -> EFResult<Vec<MigrationHistoryEntry>> {
65 self.ensure_history_table(provider).await?;
66 let q = |s: &str| self.dialect.quote(s);
67 let table = q(super::history::MIGRATION_HISTORY_TABLE);
68 let sql = format!(
69 "SELECT {}, {} FROM {} ORDER BY {}",
70 q("migration_id"),
71 q("product_version"),
72 table,
73 q("migration_id")
74 );
75 let mut conn = provider.get_connection().await?;
76 let rows = conn.query(&sql, &[]).await?;
77 Ok(rows
78 .into_iter()
79 .map(|row| MigrationHistoryEntry {
80 migration_id: row
81 .first()
82 .and_then(|v| String::try_from(v.clone()).ok())
83 .unwrap_or_default(),
84 product_version: row
85 .get(1)
86 .and_then(|v| String::try_from(v.clone()).ok())
87 .unwrap_or_default(),
88 })
89 .collect())
90 }
91
92 pub async fn is_applied(
94 &self,
95 provider: &dyn IDatabaseProvider,
96 migration_id: &str,
97 ) -> EFResult<bool> {
98 let applied = self.get_applied_migrations(provider).await?;
99 Ok(applied.iter().any(|e| e.migration_id == migration_id))
100 }
101
102 pub async fn apply_pending(
104 &self,
105 provider: &dyn IDatabaseProvider,
106 migrations: &[Migration],
107 ) -> EFResult<usize> {
108 let applied: std::collections::HashSet<String> = self
109 .get_applied_migrations(provider)
110 .await?
111 .into_iter()
112 .map(|e| e.migration_id)
113 .collect();
114 let mut count = 0usize;
115 for migration in migrations {
116 if applied.contains(&migration.id) {
117 continue;
118 }
119 self.apply(provider, migration).await?;
120 count += 1;
121 }
122 Ok(count)
123 }
124
125 pub async fn revert_last(
127 &self,
128 provider: &dyn IDatabaseProvider,
129 migrations: &[Migration],
130 ) -> EFResult<Option<String>> {
131 let applied = self.get_applied_migrations(provider).await?;
132 let last = applied.last().map(|e| e.migration_id.clone());
133 let Some(last_id) = last else {
134 return Ok(None);
135 };
136 let migration = migrations.iter().find(|m| m.id == last_id).ok_or_else(|| {
137 EFError::migration(format!(
138 "applied migration '{}' not found in local migration set",
139 last_id
140 ))
141 })?;
142 self.revert(provider, migration).await?;
143 Ok(Some(last_id))
144 }
145
146 pub async fn revert_to_target(
153 &self,
154 provider: &dyn IDatabaseProvider,
155 migrations: &[Migration],
156 target: Option<&str>,
157 ) -> EFResult<Vec<String>> {
158 let applied = self.get_applied_migrations(provider).await?;
159 let applied_ids: Vec<&str> = applied.iter().map(|e| e.migration_id.as_str()).collect();
160
161 let to_revert: Vec<String> = match target {
162 Some(t) => {
163 let idx = applied_ids.iter().position(|id| *id == t).ok_or_else(|| {
164 EFError::migration(format!("target migration '{t}' is not applied"))
165 })?;
166 applied_ids[idx + 1..]
167 .iter()
168 .map(|s| s.to_string())
169 .collect()
170 }
171 None => applied_ids.iter().map(|s| s.to_string()).collect(),
172 };
173
174 let mut reverted = Vec::with_capacity(to_revert.len());
175 for id in to_revert.iter().rev() {
176 let migration = migrations.iter().find(|m| m.id == *id).ok_or_else(|| {
177 EFError::migration(format!(
178 "applied migration '{id}' not found in local migration set"
179 ))
180 })?;
181 self.revert(provider, migration).await?;
182 reverted.push(id.clone());
183 }
184 Ok(reverted)
185 }
186
187 pub fn generate_script(
199 migrations: &[Migration],
200 from: Option<&str>,
201 to: Option<&str>,
202 ) -> EFResult<String> {
203 let from_idx: i64 = match from {
206 Some(f) => migrations.iter().position(|m| m.id == f).ok_or_else(|| {
207 EFError::migration(format!("from migration '{f}' not found in local set"))
208 })? as i64,
209 None => -1,
210 };
211 let to_idx: i64 = match to {
212 Some(t) => migrations.iter().position(|m| m.id == t).ok_or_else(|| {
213 EFError::migration(format!("to migration '{t}' not found in local set"))
214 })? as i64,
215 None => migrations.len() as i64 - 1,
216 };
217
218 let mut sql = String::new();
219 if from_idx < to_idx {
220 sql.push_str("-- Migration script (forward)\n\n");
222 let start = (from_idx + 1) as usize;
223 let end = (to_idx + 1) as usize; for m in &migrations[start..end] {
225 sql.push_str(&format!("-- Up: {}\n", m.id));
226 sql.push_str(&m.up_sql.replace("{migration_id}", &m.id));
227 sql.push('\n');
228 }
229 } else if from_idx > to_idx {
230 sql.push_str("-- Migration script (reverse)\n\n");
232 let start = to_idx as usize + 1; let end = from_idx as usize + 1; for m in migrations[start..end].iter().rev() {
235 sql.push_str(&format!("-- Down: {}\n", m.id));
236 sql.push_str(&m.down_sql.replace("{migration_id}", &m.id));
237 sql.push('\n');
238 }
239 } else {
240 sql.push_str("-- Nothing to do (from == to)\n");
241 }
242 Ok(sql)
243 }
244
245 pub async fn ensure_created(
249 &self,
250 provider: &dyn IDatabaseProvider,
251 entity_types: &[EntityTypeMeta],
252 ) -> EFResult<()> {
253 let snapshot = self.create_snapshot("__ensure_created__", entity_types);
254 let changes = self.initial_create(&snapshot);
255 let sql = self.generate_ddl_sql(&changes);
256 for statement in split_sql_statements(&sql) {
257 if !statement.is_empty() {
258 provider.execute_migration_command(&statement).await?;
259 }
260 }
261 Ok(())
262 }
263
264 pub async fn ensure_deleted(
267 &self,
268 provider: &dyn IDatabaseProvider,
269 entity_types: &[EntityTypeMeta],
270 ) -> EFResult<()> {
271 let q = |s: &str| self.dialect.quote(s);
272 for meta in entity_types {
273 let sql = format!("DROP TABLE IF EXISTS {};", q(meta.table_name.as_ref()));
274 provider.execute_migration_command(&sql).await?;
275 }
276 Ok(())
277 }
278
279 pub async fn apply_seed_data(
281 &self,
282 provider: &dyn IDatabaseProvider,
283 meta: &EntityTypeMeta,
284 rows: &[std::collections::HashMap<String, crate::provider::DbValue>],
285 ) -> EFResult<()> {
286 if rows.is_empty() {
287 return Ok(());
288 }
289
290 let gen = provider.sql_generator();
291 let scalar_props: Vec<_> = meta.mapped_scalar_properties().collect();
292 if scalar_props.is_empty() {
293 return Ok(());
294 }
295
296 let col_names: Vec<&str> = scalar_props
297 .iter()
298 .map(|p| p.column_name.as_ref())
299 .collect();
300
301 let mut conn = provider.get_connection().await?;
302 for row in rows {
303 let params: Vec<crate::provider::DbValue> = scalar_props
304 .iter()
305 .map(|p| {
306 row.get(p.field_name.as_ref())
307 .cloned()
308 .unwrap_or(crate::provider::DbValue::Null)
309 })
310 .collect();
311
312 let sql = seed_insert_sql(self.dialect, meta.table_name.as_ref(), &col_names, gen);
313 conn.execute(&sql, ¶ms).await?;
314 }
315 Ok(())
316 }
317}