diff --git a/CREDITS.md b/CREDITS.md index 41bc1c7b..46ae8495 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -15,7 +15,7 @@ [SeaQuery](https://github.com/SeaQL/sea-query) para interactuar con bases de datos. Sin embargo, para restringir las migraciones a módulos, se ha integrado en el código una versión modificada de [SeaORM Migration](https://github.com/SeaQL/sea-orm/tree/master/sea-orm-migration) (versión - [0.11.3](https://github.com/SeaQL/sea-orm/tree/0.11.3/sea-orm-migration/src)). + [0.12.8](https://github.com/SeaQL/sea-orm/tree/0.12.8/sea-orm-migration/src)). # 🗚 FIGfonts diff --git a/pagetop/Cargo.toml b/pagetop/Cargo.toml index 188598aa..44edc434 100644 --- a/pagetop/Cargo.toml +++ b/pagetop/Cargo.toml @@ -70,13 +70,13 @@ version = "0.3.29" optional = true [dependencies.sea-orm] -version = "0.11.3" +version = "0.12.8" features = ["debug-print", "macros", "runtime-async-std-native-tls"] default-features = false optional = true [dependencies.sea-schema] -version = "0.11.0" +version = "0.14.1" optional = true [build-dependencies] diff --git a/pagetop/src/db/migration/manager.rs b/pagetop/src/db/migration/manager.rs index f70c9212..994204e1 100644 --- a/pagetop/src/db/migration/manager.rs +++ b/pagetop/src/db/migration/manager.rs @@ -136,4 +136,25 @@ impl<'c> SchemaManager<'c> { res.try_get("", "has_column") } + + pub async fn has_index(&self, table: T, index: I) -> Result + where + T: AsRef, + I: AsRef, + { + let stmt = match self.conn.get_database_backend() { + DbBackend::MySql => MySql::has_index(table, index), + DbBackend::Postgres => Postgres::has_index(table, index), + DbBackend::Sqlite => Sqlite::has_index(table, index), + }; + + let builder = self.conn.get_database_backend(); + let res = self + .conn + .query_one(builder.build(&stmt)) + .await? + .ok_or_else(|| DbErr::Custom("Failed to check index exists".to_owned()))?; + + res.try_get("", "has_index") + } } diff --git a/pagetop/src/db/migration/migrator.rs b/pagetop/src/db/migration/migrator.rs index d91cbd7b..9479cff4 100644 --- a/pagetop/src/db/migration/migrator.rs +++ b/pagetop/src/db/migration/migrator.rs @@ -6,18 +6,19 @@ use std::time::SystemTime; use tracing::info; use sea_orm::sea_query::{ - self, extension::postgres::Type, Alias, Expr, ForeignKey, Iden, JoinType, Query, + self, extension::postgres::Type, Alias, Expr, ForeignKey, IntoIden, JoinType, Order, Query, SelectStatement, SimpleExpr, Table, }; use sea_orm::{ - ActiveModelTrait, ActiveValue, ColumnTrait, Condition, ConnectionTrait, DbBackend, DbErr, - EntityTrait, QueryFilter, QueryOrder, Schema, Statement, TransactionTrait, + ActiveModelTrait, ActiveValue, Condition, ConnectionTrait, DbBackend, DbErr, DeriveIden, + DynIden, EntityTrait, FromQueryResult, Iterable, QueryFilter, Schema, Statement, + TransactionTrait, }; use sea_schema::{mysql::MySql, postgres::Postgres, probe::SchemaProbe, sqlite::Sqlite}; use super::{seaql_migrations, IntoSchemaManagerConnection, MigrationTrait, SchemaManager}; -#[derive(Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] /// Status of migration pub enum MigrationStatus { /// Not yet applied @@ -26,11 +27,6 @@ pub enum MigrationStatus { Applied, } -pub struct Migration { - migration: Box, - status: MigrationStatus, -} - impl Display for MigrationStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let status = match self { @@ -41,12 +37,34 @@ impl Display for MigrationStatus { } } +pub struct Migration { + migration: Box, + status: MigrationStatus, +} + +impl Migration { + /// Get migration name from MigrationName trait implementation + pub fn name(&self) -> &str { + self.migration.name() + } + + /// Get migration status + pub fn status(&self) -> MigrationStatus { + self.status + } +} + /// Performing migrations on a database #[async_trait::async_trait] pub trait MigratorTrait: Send { /// Vector of migrations in time sequence fn migrations() -> Vec>; + /// Name of the migration table, it is `seaql_migrations` by default + fn migration_table_name() -> DynIden { + seaql_migrations::Entity.into_iden() + } + /// Get list of migrations wrapped in `Migration` struct fn get_migration_files() -> Vec { Self::migrations() @@ -64,8 +82,13 @@ pub trait MigratorTrait: Send { C: ConnectionTrait, { Self::install(db).await?; - seaql_migrations::Entity::find() - .order_by_asc(seaql_migrations::Column::Version) + let stmt = Query::select() + .table_name(Self::migration_table_name()) + .columns(seaql_migrations::Column::iter().map(IntoIden::into_iden)) + .order_by(seaql_migrations::Column::Version, Order::Asc) + .to_owned(); + let builder = db.get_database_backend(); + seaql_migrations::Model::find_by_statement(builder.build(&stmt)) .all(db) .await } @@ -142,7 +165,9 @@ pub trait MigratorTrait: Send { { let builder = db.get_database_backend(); let schema = Schema::new(builder); - let mut stmt = schema.create_table_from_entity(seaql_migrations::Entity); + let mut stmt = schema + .create_table_from_entity(seaql_migrations::Entity) + .table_name(Self::migration_table_name()); stmt.if_not_exists(); db.execute(builder.build(&stmt)).await.map(|_| ()) } @@ -168,7 +193,7 @@ pub trait MigratorTrait: Send { where C: IntoSchemaManagerConnection<'c>, { - exec_with_connection::<'_, _, _, Self>(db, move |manager| { + exec_with_connection::<'_, _, _>(db, move |manager| { Box::pin(async move { exec_fresh::(manager).await }) }) .await @@ -179,7 +204,7 @@ pub trait MigratorTrait: Send { where C: IntoSchemaManagerConnection<'c>, { - exec_with_connection::<'_, _, _, Self>(db, move |manager| { + exec_with_connection::<'_, _, _>(db, move |manager| { Box::pin(async move { exec_down::(manager, None).await?; exec_up::(manager, None).await @@ -193,7 +218,7 @@ pub trait MigratorTrait: Send { where C: IntoSchemaManagerConnection<'c>, { - exec_with_connection::<'_, _, _, Self>(db, move |manager| { + exec_with_connection::<'_, _, _>(db, move |manager| { Box::pin(async move { exec_down::(manager, None).await }) }) .await @@ -204,7 +229,7 @@ pub trait MigratorTrait: Send { where C: IntoSchemaManagerConnection<'c>, { - exec_with_connection::<'_, _, _, Self>(db, move |manager| { + exec_with_connection::<'_, _, _>(db, move |manager| { Box::pin(async move { exec_up::(manager, steps).await }) }) .await @@ -215,21 +240,19 @@ pub trait MigratorTrait: Send { where C: IntoSchemaManagerConnection<'c>, { - exec_with_connection::<'_, _, _, Self>(db, move |manager| { + exec_with_connection::<'_, _, _>(db, move |manager| { Box::pin(async move { exec_down::(manager, steps).await }) }) .await } } -#[allow(clippy::extra_unused_type_parameters)] -async fn exec_with_connection<'c, C, F, M>(db: C, f: F) -> Result<(), DbErr> +async fn exec_with_connection<'c, C, F>(db: C, f: F) -> Result<(), DbErr> where C: IntoSchemaManagerConnection<'c>, F: for<'b> Fn( &'b SchemaManager<'_>, ) -> Pin> + Send + 'b>>, - M: MigratorTrait + ?Sized, { let db = db.into_schema_manager_connection(); @@ -311,7 +334,7 @@ where let type_name: String = row.try_get("", "typname")?; info!("Dropping type '{}'", type_name); let mut stmt = Type::drop(); - stmt.name(Alias::new(&type_name as &str)); + stmt.name(Alias::new(&type_name)); db.execute(db_backend.build(&stmt)).await?; info!("Type '{}' has been dropped", type_name); } @@ -363,11 +386,12 @@ where let now = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .expect("SystemTime before UNIX EPOCH!"); - seaql_migrations::ActiveModel { + seaql_migrations::Entity::insert(seaql_migrations::ActiveModel { version: ActiveValue::Set(migration.name().to_owned()), applied_at: ActiveValue::Set(now.as_secs() as i64), - } - .insert(db) + }) + .table_name(M::migration_table_name()) + .exec(db) .await?; } @@ -403,7 +427,8 @@ where migration.down(manager).await?; info!("Migration '{}' has been rollbacked", migration.name()); seaql_migrations::Entity::delete_many() - .filter(seaql_migrations::Column::Version.eq(migration.name())) + .filter(Expr::col(seaql_migrations::Column::Version).eq(migration.name())) + .table_name(M::migration_table_name()) .exec(db) .await?; } @@ -433,13 +458,13 @@ where } } -#[derive(Iden)] +#[derive(DeriveIden)] enum InformationSchema { - #[iden = "information_schema"] + #[sea_orm(iden = "information_schema")] Schema, - #[iden = "TABLE_NAME"] + #[sea_orm(iden = "TABLE_NAME")] TableName, - #[iden = "CONSTRAINT_NAME"] + #[sea_orm(iden = "CONSTRAINT_NAME")] ConstraintName, TableConstraints, TableSchema, @@ -476,7 +501,7 @@ where stmt } -#[derive(Iden)] +#[derive(DeriveIden)] enum PgType { Table, Typname, @@ -484,7 +509,7 @@ enum PgType { Typelem, } -#[derive(Iden)] +#[derive(DeriveIden)] enum PgNamespace { Table, Oid, @@ -514,3 +539,51 @@ where ); stmt } + +trait QueryTable { + type Statement; + + fn table_name(self, table_name: DynIden) -> Self::Statement; +} + +impl QueryTable for SelectStatement { + type Statement = SelectStatement; + + fn table_name(mut self, table_name: DynIden) -> SelectStatement { + self.from(table_name); + self + } +} + +impl QueryTable for sea_query::TableCreateStatement { + type Statement = sea_query::TableCreateStatement; + + fn table_name(mut self, table_name: DynIden) -> sea_query::TableCreateStatement { + self.table(table_name); + self + } +} + +impl QueryTable for sea_orm::Insert +where + A: ActiveModelTrait, +{ + type Statement = sea_orm::Insert; + + fn table_name(mut self, table_name: DynIden) -> sea_orm::Insert { + sea_orm::QueryTrait::query(&mut self).into_table(table_name); + self + } +} + +impl QueryTable for sea_orm::DeleteMany +where + E: EntityTrait, +{ + type Statement = sea_orm::DeleteMany; + + fn table_name(mut self, table_name: DynIden) -> sea_orm::DeleteMany { + sea_orm::QueryTrait::query(&mut self).from_table(table_name); + self + } +} diff --git a/pagetop/src/db/migration/prelude.rs b/pagetop/src/db/migration/prelude.rs index fac0e132..2c1132ad 100644 --- a/pagetop/src/db/migration/prelude.rs +++ b/pagetop/src/db/migration/prelude.rs @@ -9,5 +9,7 @@ pub use async_trait; pub use sea_orm; pub use sea_orm::sea_query; pub use sea_orm::sea_query::*; +pub use sea_orm::ConnectionTrait; pub use sea_orm::DbErr; +pub use sea_orm::DeriveIden; pub use sea_orm::DeriveMigrationName;