Skip to main content

sea_orm_migration/
migrator.rs

1mod queries;
2
3mod exec;
4use exec::*;
5
6mod with_self;
7pub use with_self::*;
8
9use std::fmt::Display;
10use tracing::info;
11
12use super::{IntoSchemaManagerConnection, MigrationTrait, SchemaManager, seaql_migrations};
13use sea_orm::sea_query::IntoIden;
14use sea_orm::{ConnectionTrait, DbErr, DynIden};
15
16#[derive(Copy, Clone, Debug, PartialEq, Eq)]
17/// Status of migration
18pub enum MigrationStatus {
19    /// Not yet applied
20    Pending,
21    /// Applied
22    Applied,
23}
24
25impl Display for MigrationStatus {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        let status = match self {
28            MigrationStatus::Pending => "Pending",
29            MigrationStatus::Applied => "Applied",
30        };
31        write!(f, "{status}")
32    }
33}
34
35pub struct Migration {
36    migration: Box<dyn MigrationTrait>,
37    status: MigrationStatus,
38}
39
40impl Migration {
41    /// Get migration name from MigrationName trait implementation
42    pub fn name(&self) -> &str {
43        self.migration.name()
44    }
45
46    /// Get migration status
47    pub fn status(&self) -> MigrationStatus {
48        self.status
49    }
50}
51
52/// Performing migrations on a database
53#[async_trait::async_trait]
54pub trait MigratorTrait: Send {
55    /// Vector of migrations in time sequence
56    fn migrations() -> Vec<Box<dyn MigrationTrait>>;
57
58    /// Name of the migration table, it is `seaql_migrations` by default
59    fn migration_table_name() -> DynIden {
60        seaql_migrations::Entity.into_iden()
61    }
62
63    /// Get list of migrations wrapped in `Migration` struct
64    fn get_migration_files() -> Vec<Migration> {
65        Self::migrations()
66            .into_iter()
67            .map(|migration| Migration {
68                migration,
69                status: MigrationStatus::Pending,
70            })
71            .collect()
72    }
73
74    /// Get list of applied migrations from database
75    async fn get_migration_models<C>(db: &C) -> Result<Vec<seaql_migrations::Model>, DbErr>
76    where
77        C: ConnectionTrait,
78    {
79        Self::install(db).await?;
80        get_migration_models(db, Self::migration_table_name()).await
81    }
82
83    /// Get list of migrations with status
84    async fn get_migration_with_status<C>(db: &C) -> Result<Vec<Migration>, DbErr>
85    where
86        C: ConnectionTrait,
87    {
88        Self::install(db).await?;
89        get_migration_with_status(
90            Self::get_migration_files(),
91            Self::get_migration_models(db).await?,
92        )
93    }
94
95    /// Get list of pending migrations
96    async fn get_pending_migrations<C>(db: &C) -> Result<Vec<Migration>, DbErr>
97    where
98        C: ConnectionTrait,
99    {
100        Self::install(db).await?;
101        Ok(Self::get_migration_with_status(db)
102            .await?
103            .into_iter()
104            .filter(|file| file.status == MigrationStatus::Pending)
105            .collect())
106    }
107
108    /// Get list of applied migrations
109    async fn get_applied_migrations<C>(db: &C) -> Result<Vec<Migration>, DbErr>
110    where
111        C: ConnectionTrait,
112    {
113        Self::install(db).await?;
114        Ok(Self::get_migration_with_status(db)
115            .await?
116            .into_iter()
117            .filter(|file| file.status == MigrationStatus::Applied)
118            .collect())
119    }
120
121    /// Create migration table `seaql_migrations` in the database
122    async fn install<C>(db: &C) -> Result<(), DbErr>
123    where
124        C: ConnectionTrait,
125    {
126        install(db, Self::migration_table_name()).await
127    }
128
129    /// Check the status of all migrations
130    async fn status<C>(db: &C) -> Result<(), DbErr>
131    where
132        C: ConnectionTrait,
133    {
134        Self::install(db).await?;
135
136        info!("Checking migration status");
137
138        for Migration { migration, status } in Self::get_migration_with_status(db).await? {
139            info!("Migration '{}'... {}", migration.name(), status);
140        }
141
142        Ok(())
143    }
144
145    /// Drop all tables from the database, then reapply all migrations
146    async fn fresh<'c, C>(db: C) -> Result<(), DbErr>
147    where
148        C: IntoSchemaManagerConnection<'c>,
149    {
150        let db = db.into_database_executor();
151        let manager = SchemaManager::new(db);
152        exec_fresh::<Self>(&manager).await
153    }
154
155    /// Rollback all applied migrations, then reapply all migrations
156    async fn refresh<'c, C>(db: C) -> Result<(), DbErr>
157    where
158        C: IntoSchemaManagerConnection<'c>,
159    {
160        let db = db.into_database_executor();
161        let manager = SchemaManager::new(db);
162        exec_down::<Self>(&manager, None).await?;
163        exec_up::<Self>(&manager, None).await
164    }
165
166    /// Rollback all applied migrations
167    async fn reset<'c, C>(db: C) -> Result<(), DbErr>
168    where
169        C: IntoSchemaManagerConnection<'c>,
170    {
171        let db = db.into_database_executor();
172        let manager = SchemaManager::new(db);
173        exec_down::<Self>(&manager, None).await?;
174        uninstall(&manager, Self::migration_table_name()).await
175    }
176
177    /// Uninstall migration tracking table only (non-destructive)
178    /// This will drop the `seaql_migrations` table but won't rollback other schema changes.
179    async fn uninstall<'c, C>(db: C) -> Result<(), DbErr>
180    where
181        C: IntoSchemaManagerConnection<'c>,
182    {
183        let db = db.into_database_executor();
184        let manager = SchemaManager::new(db);
185        uninstall(&manager, Self::migration_table_name()).await
186    }
187
188    /// Apply pending migrations
189    async fn up<'c, C>(db: C, steps: Option<u32>) -> Result<(), DbErr>
190    where
191        C: IntoSchemaManagerConnection<'c>,
192    {
193        let db = db.into_database_executor();
194        let manager = SchemaManager::new(db);
195        exec_up::<Self>(&manager, steps).await
196    }
197
198    /// Rollback applied migrations
199    async fn down<'c, C>(db: C, steps: Option<u32>) -> Result<(), DbErr>
200    where
201        C: IntoSchemaManagerConnection<'c>,
202    {
203        let db = db.into_database_executor();
204        let manager = SchemaManager::new(db);
205        exec_down::<Self>(&manager, steps).await
206    }
207}
208
209async fn exec_fresh<M>(manager: &SchemaManager<'_>) -> Result<(), DbErr>
210where
211    M: MigratorTrait + ?Sized,
212{
213    let db = manager.get_connection();
214
215    M::install(db).await?;
216
217    drop_everything(db).await?;
218
219    exec_up::<M>(manager, None).await
220}
221
222async fn exec_up<M>(manager: &SchemaManager<'_>, steps: Option<u32>) -> Result<(), DbErr>
223where
224    M: MigratorTrait + ?Sized,
225{
226    let db = manager.get_connection();
227
228    M::install(db).await?;
229
230    exec_up_with(
231        manager,
232        steps,
233        M::get_pending_migrations(db).await?,
234        M::migration_table_name(),
235    )
236    .await
237}
238
239async fn exec_down<M>(manager: &SchemaManager<'_>, steps: Option<u32>) -> Result<(), DbErr>
240where
241    M: MigratorTrait + ?Sized,
242{
243    let db = manager.get_connection();
244
245    M::install(db).await?;
246
247    exec_down_with(
248        manager,
249        steps,
250        M::get_applied_migrations(db).await?,
251        M::migration_table_name(),
252    )
253    .await
254}