sea_migrations/
lib.rs

1#![deny(missing_docs)]
2// #![deny(unsafe_code)] // TODO: Waiting on https://github.com/SeaQL/sea-orm/issues/317
3
4//! Effortless database migrations for [SeaORM](https://www.sea-ql.org/SeaORM/)!
5//!
6//! Checkout an example using this package [here](https://github.com/oscartbeaumont/sea-migrations/tree/main/example).
7
8use async_trait::async_trait;
9use sea_orm::{
10    sea_query::Table, ColumnTrait, ConnectionTrait, DbConn, DbErr, EntityTrait, ExecResult,
11    Iterable, RelationTrait,
12};
13
14use crate::seaorm_integration::*;
15
16mod migrations_table;
17mod seaorm_integration;
18
19/// MigrationName is the trait implemented on a migration so that sea_migration knows what the migration is called. This can be derived using (TODO add derive macro).
20pub trait MigrationName {
21    /// Returns the name of the migration.
22    fn name(&self) -> &str;
23}
24
25/// MigratorTrait is the trait implemented on a migrator so that sea_migration knows how to do and undo the migration.
26///
27/// ```rust
28/// use sea_orm::DbErr;
29/// use sea_migrations::{MigrationName, MigratorTrait, MigrationManager};
30/// use async_trait::async_trait;
31///
32/// pub struct M20210101020202DoAThing;
33///
34/// // TODO: MigrationName will be automatically implemented using a Derive macro in future update.
35/// impl MigrationName for M20210101020202DoAThing {
36///     fn name(&self) -> &'static str {
37///         "M20210101020202DoAThing"
38///     }
39/// }
40///
41/// #[async_trait]
42/// impl MigratorTrait for M20210101020202DoAThing {
43///     async fn up(&self, mg: &MigrationManager) -> Result<(), DbErr> {
44///         println!("up: M20210101020202DoAThing");
45///         Ok(())
46///     }
47///     async fn down(&self, mg: &MigrationManager) -> Result<(), DbErr> {
48///         println!("down: M20210101020202DoAThing");
49///         Ok(())
50///     }
51/// }
52/// ```
53#[async_trait]
54pub trait MigratorTrait: MigrationName {
55    /// up is run to apply a database migration. You can assume anything created in here doesn't exist when it is run. If an error occurs the `down` method will be run to undo the migration before retrying.
56    async fn up(&self, mg: &MigrationManager) -> Result<(), DbErr>;
57
58    /// down is used to undo a database migration. You should assume that anything applied in the `up` function is not necessarily created when this is run as the `up` function may have failed.
59    async fn down(&self, mg: &MigrationManager) -> Result<(), DbErr>;
60}
61
62/// MigrationManager is used to manage migrations. It holds the database connection and has many helpers to make your database migration code concise.
63pub struct MigrationManager<'a> {
64    /// db holds the database connection. This can be used to run any custom queries again the database.
65    pub db: &'a DbConn,
66}
67
68impl<'a> MigrationManager<'a> {
69    /// new will create a new MigrationManager. This is primarily designed for internal use but is exposed in case you want to use it.
70    pub fn new(db: &'a DbConn) -> Self {
71        Self { db }
72    }
73
74    /// create_table will create a database table if it does not exist for a SeaORM Entity.
75    ///
76    /// ```rust
77    /// use sea_orm::{Database, DbErr};
78    /// use sea_orm::entity::prelude::*;
79    /// use sea_migrations::MigrationManager;
80    ///
81    /// #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
82    /// #[sea_orm(table_name = "cake")]
83    /// pub struct Model {
84    ///     #[sea_orm(primary_key)]
85    ///     pub id: i32,
86    ///     pub name: String,
87    /// }
88
89    /// #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
90    /// pub enum Relation {}
91    ///
92    /// impl ActiveModelBehavior for ActiveModel {}
93    ///
94    /// #[tokio::main]
95    /// async fn main() -> Result<(), DbErr> {
96    ///     let db = Database::connect("sqlite::memory:").await?;
97    ///     // You would not normally create a MigrationManager by yourself. It would be provided to the `up` or `down` function by sea_migrations.
98    ///     let mg = MigrationManager::new(&db);
99    ///
100    ///     mg.create_table(crate::Entity).await?; // Replace "crate" with the name of the module containing your SeaORM Model.
101    ///
102    ///     Ok(())
103    /// }
104    /// ```
105    pub async fn create_table<E: 'static>(&self, entity: E) -> Result<ExecResult, DbErr>
106    where
107        E: EntityTrait,
108    {
109        let mut stmt = Table::create();
110        stmt.table(entity).if_not_exists();
111
112        for column in E::Column::iter() {
113            stmt.col(&mut get_column_def::<E>(column));
114        }
115
116        for relation in E::Relation::iter() {
117            if relation.def().is_owner {
118                continue;
119            }
120            stmt.foreign_key(&mut get_column_foreign_key_def::<E>(relation));
121        }
122
123        self.db
124            .execute(self.db.get_database_backend().build(&stmt))
125            .await
126    }
127
128    /// drop_table will drop a database table and all of it's data for a SeaORM Entity.
129    ///
130    /// ```rust
131    /// use sea_orm::{Database, DbErr};
132    /// use sea_orm::entity::prelude::*;
133    /// use sea_migrations::MigrationManager;
134    ///
135    /// #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
136    /// #[sea_orm(table_name = "cake")]
137    /// pub struct Model {
138    ///     #[sea_orm(primary_key)]
139    ///     pub id: i32,
140    ///     pub name: String,
141    /// }
142    ///
143    /// #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
144    /// pub enum Relation {}
145    ///
146    /// impl ActiveModelBehavior for ActiveModel {}
147    ///
148    /// #[tokio::main]
149    /// async fn main() -> Result<(), DbErr> {
150    ///     let db = Database::connect("sqlite::memory:").await?;
151    ///     // You would not normally create a MigrationManager by yourself. It would be provided to the `up` or `down` function by sea_migrations.
152    ///     let mg = MigrationManager::new(&db);
153    ///
154    ///     mg.drop_table(crate::Entity).await?; // Replace "crate" with the name of the module containing your SeaORM Model.
155    ///
156    ///     Ok(())
157    /// }
158    /// ```
159    pub async fn drop_table<E: 'static>(&self, entity: E) -> Result<ExecResult, DbErr>
160    where
161        E: EntityTrait,
162    {
163        let stmt = Table::drop().table(entity).if_exists().to_owned();
164        self.db
165            .execute(self.db.get_database_backend().build(&stmt))
166            .await
167    }
168
169    /// add_column will automatically create a new column in the existing database table for a specific column on the Entity.
170    ///
171    /// ```rust
172    /// use sea_orm::{Database, DbErr};
173    /// use sea_migrations::MigrationManager;
174    ///
175    /// mod original_model {
176    ///      use sea_orm::entity::prelude::*;
177    ///     
178    ///     #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
179    ///     #[sea_orm(table_name = "cake")]
180    ///     pub struct Model {
181    ///         #[sea_orm(primary_key)]
182    ///         pub id: i32,
183    ///         pub name: String,
184    ///     }
185    ///
186    ///     #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
187    ///     pub enum Relation {}
188    ///
189    ///     impl ActiveModelBehavior for ActiveModel {}
190    /// }
191    ///
192    /// mod updated_model {
193    ///      use sea_orm::entity::prelude::*;
194    ///     
195    ///     #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
196    ///     #[sea_orm(table_name = "cake")]
197    ///     pub struct Model {
198    ///         #[sea_orm(primary_key)]
199    ///         pub id: i32,
200    ///         pub name: String,
201    ///         pub new_column: String,
202    ///     }
203    ///
204    ///     #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
205    ///     pub enum Relation {}
206    ///
207    ///     impl ActiveModelBehavior for ActiveModel {}
208    /// }
209    ///
210    /// #[tokio::main]
211    /// async fn main() -> Result<(), DbErr> {
212    ///     let db = Database::connect("sqlite::memory:").await?;
213    ///     // You would not normally create a MigrationManager by yourself. It would be provided to the `up` or `down` function by sea_migrations.
214    ///     let mg = MigrationManager::new(&db);
215    ///     mg.create_table(original_model::Entity).await?; // Create the original table without the new column. This would have been done in the previous version of your application.
216    ///
217    ///     mg.add_column(updated_model::Entity, updated_model::Column::NewColumn).await?; // Replace "updated_model" with the name of the module containing your SeaORM Model and NewColumn with the name of your new Column.
218    ///
219    ///     Ok(())
220    /// }
221    /// ```
222    pub async fn add_column<E: 'static, T: 'static>(
223        &self,
224        entity: E,
225        column: T,
226    ) -> Result<ExecResult, DbErr>
227    where
228        E: EntityTrait<Column = T>,
229        T: ColumnTrait,
230    {
231        let mut stmt = Table::alter();
232        stmt.table(entity)
233            .add_column(&mut get_column_def::<E>(column));
234
235        self.db
236            .execute(self.db.get_database_backend().build(&stmt))
237            .await
238    }
239
240    /// drop_column will drop a table's column and all of it's data for a Column on a SeaORM Entity.
241    ///
242    /// The example panics due to SQLite not being able to drop a column.
243    /// ```should_panic
244    /// use sea_orm::{Database, DbErr};
245    /// use sea_orm::entity::prelude::*;
246    /// use sea_migrations::MigrationManager;
247    ///
248    /// #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
249    /// #[sea_orm(table_name = "cake")]
250    /// pub struct Model {
251    ///     #[sea_orm(primary_key)]
252    ///     pub id: i32,
253    ///     pub name: String,
254    ///     pub column_to_remove: String, // Note: This column although removed from the database can NEVER be removed from the Model without causing issues with running older migrations.
255    /// }
256    ///
257    /// #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
258    /// pub enum Relation {}
259    ///
260    /// impl ActiveModelBehavior for ActiveModel {}
261    ///
262    /// #[tokio::main]
263    /// async fn main() -> Result<(), DbErr> {
264    ///     let db = Database::connect("sqlite::memory:").await?;
265    ///     // You would not normally create a MigrationManager by yourself. It would be provided to the `up` or `down` function by sea_migrations.
266    ///     let mg = MigrationManager::new(&db);
267    ///     mg.create_table(crate::Entity).await?; // Create the original table with the column. This would have been done in the previous version of your application.
268    ///
269    ///     mg.drop_column(crate::Entity, crate::Column::ColumnToRemove).await?; // Replace "crate" with the name of the module containing your SeaORM Model and ColumnToRemove with the name of the column to remove.
270    ///
271    ///     Ok(())
272    /// }
273    /// ```
274    pub async fn drop_column<E: 'static, T: 'static>(
275        &self,
276        entity: E,
277        column: T,
278    ) -> Result<ExecResult, DbErr>
279    where
280        E: EntityTrait<Column = T>,
281        T: ColumnTrait,
282    {
283        let mut stmt = Table::alter();
284        stmt.table(entity).drop_column(column);
285
286        self.db
287            .execute(self.db.get_database_backend().build(&stmt))
288            .await
289    }
290}
291
292/// Migrator is used to handle running migration operations.
293pub struct Migrator;
294
295impl Migrator {
296    /// run will run all of the database migrations provided via the migrations parameter.
297    /// In microservice environments think about how this function is called. It contains an internal lock to prevent multiple clients running migrations at the same time but don't rely on it!
298    ///
299    /// ```rust
300    /// use sea_migrations::Migrator;
301    /// use sea_orm::{ Database, DbErr };
302    ///
303    /// #[tokio::main]
304    /// async fn main() -> Result<(), DbErr> {
305    ///     let db = Database::connect("sqlite::memory:").await?;
306    ///     
307    ///     Migrator::run(
308    ///         &db,
309    ///         &mut vec![
310    ///            // Box::new(models::M20210101020202DoAThing),
311    ///         ],
312    ///     )
313    ///     .await
314    /// }
315    ///
316    /// ```
317    // Note(oscar): I don't like that the migrations argument is mutable but it works for now and that argument will be removed in a future version so their is no point trying to fix it.
318    pub async fn run(
319        db: &DbConn,
320        migrations: &mut Vec<Box<dyn MigratorTrait>>,
321    ) -> Result<(), DbErr> {
322        let mg = MigrationManager::new(db);
323        migrations_table::init(db).await?;
324        migrations_table::lock(db).await?;
325        let result = Self::do_migrations(&mg, migrations).await;
326        migrations_table::unlock(db).await?;
327        result
328    }
329
330    // do_migrations runs the Database migrations. This function exists so it is easier to capture the error in the `run` function.
331    async fn do_migrations<'a>(
332        mg: &'a MigrationManager<'a>,
333        migrations: &mut Vec<Box<dyn MigratorTrait>>,
334    ) -> Result<(), DbErr> {
335        // Sort migrations into predictable order
336        migrations.sort_by(|a, b| a.name().cmp(b.name()));
337
338        for migration in migrations.iter() {
339            let migration_name = migration.name();
340            let migration_entry =
341                migrations_table::get_version(mg.db, migration_name.into()).await?;
342
343            match migration_entry {
344                Some(_) => {}
345                None => {
346                    let result = migration.up(mg).await;
347                    match result {
348                        Ok(_) => {
349                            migrations_table::insert_migration(mg.db, migration_name.into())
350                                .await?;
351                        }
352                        Err(err) => {
353                            migration.down(mg).await?;
354                            return Err(err);
355                        }
356                    }
357                }
358            }
359        }
360
361        Ok(())
362    }
363}