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}