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    /// Get list of migrations with status, without creating the migration table.
122    ///
123    /// Unlike [`get_migration_with_status`](Self::get_migration_with_status), this never runs
124    /// `CREATE TABLE`, so it can be called by a database user without DDL privileges (for
125    /// example, checking pending migrations from a read-only connection). If the migration
126    /// table does not exist, every migration is reported as [`MigrationStatus::Pending`].
127    async fn get_migration_with_status_read_only<C>(db: &C) -> Result<Vec<Migration>, DbErr>
128    where
129        C: ConnectionTrait,
130    {
131        get_migration_with_status(
132            Self::get_migration_files(),
133            get_migration_models_read_only(db, Self::migration_table_name()).await?,
134        )
135    }
136
137    /// Get list of pending migrations without creating the migration table.
138    ///
139    /// The read-only counterpart of [`get_pending_migrations`](Self::get_pending_migrations);
140    /// see [`get_migration_with_status_read_only`](Self::get_migration_with_status_read_only).
141    async fn get_pending_migrations_read_only<C>(db: &C) -> Result<Vec<Migration>, DbErr>
142    where
143        C: ConnectionTrait,
144    {
145        Ok(Self::get_migration_with_status_read_only(db)
146            .await?
147            .into_iter()
148            .filter(|file| file.status == MigrationStatus::Pending)
149            .collect())
150    }
151
152    /// Get list of applied migrations without creating the migration table.
153    ///
154    /// The read-only counterpart of [`get_applied_migrations`](Self::get_applied_migrations);
155    /// see [`get_migration_with_status_read_only`](Self::get_migration_with_status_read_only).
156    async fn get_applied_migrations_read_only<C>(db: &C) -> Result<Vec<Migration>, DbErr>
157    where
158        C: ConnectionTrait,
159    {
160        Ok(Self::get_migration_with_status_read_only(db)
161            .await?
162            .into_iter()
163            .filter(|file| file.status == MigrationStatus::Applied)
164            .collect())
165    }
166
167    /// Create migration table `seaql_migrations` in the database
168    async fn install<C>(db: &C) -> Result<(), DbErr>
169    where
170        C: ConnectionTrait,
171    {
172        install(db, Self::migration_table_name()).await
173    }
174
175    /// Check the status of all migrations
176    async fn status<C>(db: &C) -> Result<(), DbErr>
177    where
178        C: ConnectionTrait,
179    {
180        Self::install(db).await?;
181
182        info!("Checking migration status");
183
184        for Migration { migration, status } in Self::get_migration_with_status(db).await? {
185            info!("Migration '{}'... {}", migration.name(), status);
186        }
187
188        Ok(())
189    }
190
191    /// Drop all tables from the database, then reapply all migrations
192    async fn fresh<'c, C>(db: C) -> Result<(), DbErr>
193    where
194        C: IntoSchemaManagerConnection<'c>,
195    {
196        let db = db.into_database_executor();
197        let manager = SchemaManager::new(db);
198        exec_fresh::<Self>(&manager).await
199    }
200
201    /// Rollback all applied migrations, then reapply all migrations
202    async fn refresh<'c, C>(db: C) -> Result<(), DbErr>
203    where
204        C: IntoSchemaManagerConnection<'c>,
205    {
206        let db = db.into_database_executor();
207        let manager = SchemaManager::new(db);
208        exec_down::<Self>(&manager, None).await?;
209        exec_up::<Self>(&manager, None).await
210    }
211
212    /// Rollback all applied migrations
213    async fn reset<'c, C>(db: C) -> Result<(), DbErr>
214    where
215        C: IntoSchemaManagerConnection<'c>,
216    {
217        let db = db.into_database_executor();
218        let manager = SchemaManager::new(db);
219        exec_down::<Self>(&manager, None).await?;
220        uninstall(&manager, Self::migration_table_name()).await
221    }
222
223    /// Uninstall migration tracking table only (non-destructive)
224    /// This will drop the `seaql_migrations` table but won't rollback other schema changes.
225    async fn uninstall<'c, C>(db: C) -> Result<(), DbErr>
226    where
227        C: IntoSchemaManagerConnection<'c>,
228    {
229        let db = db.into_database_executor();
230        let manager = SchemaManager::new(db);
231        uninstall(&manager, Self::migration_table_name()).await
232    }
233
234    /// Apply pending migrations
235    async fn up<'c, C>(db: C, steps: Option<u32>) -> Result<(), DbErr>
236    where
237        C: IntoSchemaManagerConnection<'c>,
238    {
239        let db = db.into_database_executor();
240        let manager = SchemaManager::new(db);
241        exec_up::<Self>(&manager, steps).await
242    }
243
244    /// Rollback applied migrations
245    async fn down<'c, C>(db: C, steps: Option<u32>) -> Result<(), DbErr>
246    where
247        C: IntoSchemaManagerConnection<'c>,
248    {
249        let db = db.into_database_executor();
250        let manager = SchemaManager::new(db);
251        exec_down::<Self>(&manager, steps).await
252    }
253}
254
255async fn exec_fresh<M>(manager: &SchemaManager<'_>) -> Result<(), DbErr>
256where
257    M: MigratorTrait + ?Sized,
258{
259    let db = manager.get_connection();
260
261    M::install(db).await?;
262
263    drop_everything(db).await?;
264
265    exec_up::<M>(manager, None).await
266}
267
268async fn exec_up<M>(manager: &SchemaManager<'_>, steps: Option<u32>) -> Result<(), DbErr>
269where
270    M: MigratorTrait + ?Sized,
271{
272    let db = manager.get_connection();
273
274    M::install(db).await?;
275
276    exec_up_with(
277        manager,
278        steps,
279        M::get_pending_migrations(db).await?,
280        M::migration_table_name(),
281    )
282    .await
283}
284
285async fn exec_down<M>(manager: &SchemaManager<'_>, steps: Option<u32>) -> Result<(), DbErr>
286where
287    M: MigratorTrait + ?Sized,
288{
289    let db = manager.get_connection();
290
291    M::install(db).await?;
292
293    exec_down_with(
294        manager,
295        steps,
296        M::get_applied_migrations(db).await?,
297        M::migration_table_name(),
298    )
299    .await
300}