From 796ae5ce811918c8648a754ad2741b3788bf3208 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 11 May 2026 15:10:49 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(seaorm):=20Revisa=20y?= =?UTF-8?q?=20mejora=20la=20API=20p=C3=BAblica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-seaorm/README.md | 21 +++--- extensions/pagetop-seaorm/src/config.rs | 6 +- extensions/pagetop-seaorm/src/db.rs | 72 +++++++++++++++++-- extensions/pagetop-seaorm/src/db/dbconn.rs | 17 ++--- extensions/pagetop-seaorm/src/db/migration.rs | 4 +- extensions/pagetop-seaorm/src/lib.rs | 3 +- 6 files changed, 92 insertions(+), 31 deletions(-) diff --git a/extensions/pagetop-seaorm/README.md b/extensions/pagetop-seaorm/README.md index 0a1de4f5..fff02d6d 100644 --- a/extensions/pagetop-seaorm/README.md +++ b/extensions/pagetop-seaorm/README.md @@ -38,7 +38,8 @@ db_name = "my_app.db" max_pool_size = 5 ``` -Para MySQL o PostgreSQL añade también `db_user`, `db_pass`, `db_host` y `db_port`. +Para MySQL o PostgreSQL añade también `db_user`, `db_pass` y `db_host`. El campo `db_port` es +opcional; si se omite se usa el puerto predeterminado del motor. **Declara la extensión** en tu aplicación o en la extensión que la requiera: @@ -117,15 +118,15 @@ usarlo como dependencia ya que su paradigma de CLI no es compatible con el ciclo extensiones de PageTop, donde las migraciones deben ejecutarse durante la inicialización de cada extensión. Los ficheros adaptados del original son: -| Archivos | Observaciones | -|----------------------------|--------------------------------------------------------------| -| `lib.rs` en `migration.rs` | Excluye módulos y exportaciones del CLI | -| `connection.rs` | Integración completa | -| `manager.rs` | Adapta *features* propias | -| `migrator.rs` | Adapta *features* propias y omite gestión de errores del CLI | -| `prelude.rs` | Excluye exportaciones del CLI | -| `schema.rs` | Integración ajustada con cambios menores | -| `seaql_migrations.rs` | Integración completa | +| Archivos | Observaciones | +|-----------------------|--------------------------------------------------------------------------| +| `lib.rs` | Incluido en `migration.rs`, descarta módulos y exportaciones del CLI | +| `connection.rs` | Integración completa | +| `manager.rs` | Adapta *features* propias | +| `migrator.rs` | Adapta *features* propias y omite gestión de errores del CLI | +| `prelude.rs` | Excluye exportaciones del CLI | +| `schema.rs` | Integra con ajustes, original de [loco](https://github.com/loco-rs/loco) | +| `seaql_migrations.rs` | Integración completa | ## 🚧 Advertencia diff --git a/extensions/pagetop-seaorm/src/config.rs b/extensions/pagetop-seaorm/src/config.rs index bec565b3..b1276c50 100644 --- a/extensions/pagetop-seaorm/src/config.rs +++ b/extensions/pagetop-seaorm/src/config.rs @@ -34,7 +34,6 @@ include_config!(SETTINGS: Settings => [ "database.db_user" => "", "database.db_pass" => "", "database.db_host" => "localhost", - "database.db_port" => 0, "database.max_pool_size" => 5, ]); @@ -57,8 +56,9 @@ pub struct Database { pub db_pass: String, /// Servidor de conexión a la base de datos (para mysql/postgres). pub db_host: String, - /// Puerto de conexión a la base de datos, normalmente 3306 (para mysql) ó 5432 (para postgres). - pub db_port: u16, + /// Puerto de conexión a la base de datos (para mysql/postgres). Si es `None` se usa el puerto + /// predeterminado para el motor: 3306 para MySQL y 5432 para PostgreSQL. + pub db_port: Option, /// Número máximo de conexiones habilitadas. pub max_pool_size: u32, } diff --git a/extensions/pagetop-seaorm/src/db.rs b/extensions/pagetop-seaorm/src/db.rs index d1188a35..f954d9ce 100644 --- a/extensions/pagetop-seaorm/src/db.rs +++ b/extensions/pagetop-seaorm/src/db.rs @@ -1,7 +1,7 @@ use pagetop::core::TypeInfo; use pagetop::trace; -pub use url::Url as DbUri; +pub(crate) use url::Url as DbUri; pub use sea_orm::error::{DbErr, RuntimeErr}; pub use sea_orm::{DatabaseConnection as DbConn, ExecResult, QueryResult}; @@ -16,7 +16,30 @@ mod migration; pub use migration::prelude::*; pub use migration::schema::*; -pub async fn query(stmt: &mut Q) -> Result, DbErr> { +/// Ejecuta una consulta para devolver todas las filas resultantes. +/// +/// Acepta cualquier tipo que implemente [`QueryStatementWriter`] (p. ej. [`SelectStatement`]) y +/// serializa la sentencia al dialecto de la base de datos configurada antes de ejecutarla. Cada +/// fila se devuelve como un [`QueryResult`] sin tipar; extrae los valores con +/// [`QueryResult::try_get`]. +/// +/// ```rust,no_run +/// use pagetop_seaorm::db::*; +/// +/// async fn example() -> Result<(), DbErr> { +/// let mut stmt = Query::select() +/// .column(Asterisk) +/// .from(Alias::new("users")) +/// .to_owned(); +/// let rows = fetch_all(&mut stmt).await?; +/// for row in rows { +/// let name: String = row.try_get("", "name")?; +/// println!("{name}"); +/// } +/// Ok(()) +/// } +/// ``` +pub async fn fetch_all(stmt: &mut Q) -> Result, DbErr> { let dbconn = &*DBCONN; let dbbackend = dbconn.get_database_backend(); dbconn @@ -31,7 +54,30 @@ pub async fn query(stmt: &mut Q) -> Result(stmt: &mut Q) -> Result, DbErr> { +/// Ejecuta una consulta y devuelve sólo la primera fila, si existe. +/// +/// Funciona igual que [`fetch_all`] pero detiene la ejecución tras la primera fila y devuelve +/// `None` si la consulta no produce resultados. +/// +/// ```rust,no_run +/// use pagetop_seaorm::db::*; +/// +/// async fn example() -> Result<(), DbErr> { +/// let mut stmt = Query::select() +/// .column(Asterisk) +/// .from(Alias::new("users")) +/// .and_where(Expr::col(Alias::new("id")).eq(1)) +/// .to_owned(); +/// if let Some(row) = fetch_one(&mut stmt).await? { +/// let name: String = row.try_get("", "name")?; +/// println!("{name}"); +/// } +/// Ok(()) +/// } +/// ``` +pub async fn fetch_one( + stmt: &mut Q, +) -> Result, DbErr> { let dbconn = &*DBCONN; let dbbackend = dbconn.get_database_backend(); dbconn @@ -46,11 +92,27 @@ pub async fn exec(stmt: &mut Q) -> Result Result { +/// Ejecuta una sentencia SQL en crudo (INSERT, UPDATE, DELETE…) y devuelve el resultado de +/// la operación. +/// +/// A diferencia de [`fetch_all`] y [`fetch_one`], no construye la consulta, sino que la recibe como +/// cadena ya formada. Útil para sentencias avanzadas o para migraciones puntuales. El +/// [`ExecResult`] devuelto permite consultar las filas afectadas o el último ID insertado. +/// +/// ```rust,no_run +/// use pagetop_seaorm::db::*; +/// +/// async fn example() -> Result<(), DbErr> { +/// let result = execute("DELETE FROM sessions WHERE expired = 1").await?; +/// println!("Filas eliminadas: {}", result.rows_affected()); +/// Ok(()) +/// } +/// ``` +pub async fn execute(stmt: impl Into) -> Result { let dbconn = &*DBCONN; let dbbackend = dbconn.get_database_backend(); dbconn - .execute(Statement::from_string(dbbackend, stmt)) + .execute(Statement::from_string(dbbackend, stmt.into())) .await } diff --git a/extensions/pagetop-seaorm/src/db/dbconn.rs b/extensions/pagetop-seaorm/src/db/dbconn.rs index bd227956..e4881c08 100644 --- a/extensions/pagetop-seaorm/src/db/dbconn.rs +++ b/extensions/pagetop-seaorm/src/db/dbconn.rs @@ -35,10 +35,8 @@ pub static DBCONN: LazyLock = LazyLock::new(|| { tmp_uri .set_password(Some(config::SETTINGS.database.db_pass.as_str())) .unwrap(); - if config::SETTINGS.database.db_port != 0 { - tmp_uri - .set_port(Some(config::SETTINGS.database.db_port)) - .unwrap(); + if let Some(port) = config::SETTINGS.database.db_port { + tmp_uri.set_port(Some(port)).unwrap(); } tmp_uri } @@ -51,13 +49,10 @@ pub static DBCONN: LazyLock = LazyLock::new(|| { .as_str(), ) .unwrap(), - _ => { - trace::error!( - "Unrecognized database type \"{}\"", - &config::SETTINGS.database.db_type - ); - DbUri::parse("").unwrap() - } + _ => panic!( + "Unrecognized database type \"{}\"", + config::SETTINGS.database.db_type + ), }; run_now(Database::connect::({ diff --git a/extensions/pagetop-seaorm/src/db/migration.rs b/extensions/pagetop-seaorm/src/db/migration.rs index 29314bf6..0c32c10a 100644 --- a/extensions/pagetop-seaorm/src/db/migration.rs +++ b/extensions/pagetop-seaorm/src/db/migration.rs @@ -28,6 +28,8 @@ pub trait MigrationTrait: MigrationName + Send + Sync { /// Define actions to perform when rolling back the migration async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { - Err(DbErr::Migration("We Don't Do That Here".to_owned())) + Err(DbErr::Migration( + "Rollback not implemented for this migration".to_owned(), + )) } } diff --git a/extensions/pagetop-seaorm/src/lib.rs b/extensions/pagetop-seaorm/src/lib.rs index 103d95eb..0b686cb2 100644 --- a/extensions/pagetop-seaorm/src/lib.rs +++ b/extensions/pagetop-seaorm/src/lib.rs @@ -39,7 +39,8 @@ db_name = "my_app.db" max_pool_size = 5 ``` -Para MySQL o PostgreSQL añade también `db_user`, `db_pass`, `db_host` y `db_port`. +Para MySQL o PostgreSQL añade también `db_user`, `db_pass` y `db_host`. El campo `db_port` es +opcional; si se omite se usa el puerto predeterminado del motor. **Declara la extensión** en tu aplicación o en la extensión que la requiera: From 026448e51108ef76d077c3d81ea7dbf5e7c2fc06 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Fri, 15 May 2026 00:22:55 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(seaorm):=20Separa=20m?= =?UTF-8?q?=C3=B3dulo=20`migration`=20de=20`db`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `db::*` queda como API de consultas (connection, fetch_*). - `migration::*` sube a primer nivel con su propia documentación. - `DBCONN` y `run_now` se trasladan a la raíz de la extensión. - Actualiza README.md y docs para reflejar la nueva estructura. --- extensions/pagetop-seaorm/README.md | 6 +- extensions/pagetop-seaorm/src/db.rs | 130 +++++---------- extensions/pagetop-seaorm/src/db/dbconn.rs | 64 ------- extensions/pagetop-seaorm/src/db/migration.rs | 35 ---- .../src/db/migration/prelude.rs | 12 -- extensions/pagetop-seaorm/src/lib.rs | 67 +++++++- extensions/pagetop-seaorm/src/migration.rs | 156 ++++++++++++++++++ .../src/{db => }/migration/connection.rs | 0 .../src/{db => }/migration/manager.rs | 0 .../src/{db => }/migration/migrator.rs | 0 .../src/{db => }/migration/schema.rs | 26 ++- .../{db => }/migration/seaql_migrations.rs | 0 12 files changed, 279 insertions(+), 217 deletions(-) delete mode 100644 extensions/pagetop-seaorm/src/db/dbconn.rs delete mode 100644 extensions/pagetop-seaorm/src/db/migration.rs delete mode 100644 extensions/pagetop-seaorm/src/db/migration/prelude.rs create mode 100644 extensions/pagetop-seaorm/src/migration.rs rename extensions/pagetop-seaorm/src/{db => }/migration/connection.rs (100%) rename extensions/pagetop-seaorm/src/{db => }/migration/manager.rs (100%) rename extensions/pagetop-seaorm/src/{db => }/migration/migrator.rs (100%) rename extensions/pagetop-seaorm/src/{db => }/migration/schema.rs (96%) rename extensions/pagetop-seaorm/src/{db => }/migration/seaql_migrations.rs (100%) diff --git a/extensions/pagetop-seaorm/README.md b/extensions/pagetop-seaorm/README.md index fff02d6d..42738cd0 100644 --- a/extensions/pagetop-seaorm/README.md +++ b/extensions/pagetop-seaorm/README.md @@ -69,7 +69,7 @@ async fn main() -> std::io::Result<()> { **Escribe las migraciones** usando la API de SeaORM: ```rust,no_run -use pagetop_seaorm::db::*; +use pagetop_seaorm::migration::*; pub struct Migration; @@ -111,7 +111,7 @@ Este *crate* se apoya en bibliotecas del ecosistema [SeaQL](https://github.com/S usada por el módulo de migraciones para interrogar la estructura real de la base de datos (tablas, columnas, índices y claves externas). -El módulo de migraciones (`src/db/migration/`) incorpora una adaptación de +El módulo de migraciones (`src/migration/`) incorpora una adaptación de [sea-orm-migration](https://crates.io/crates/sea-orm-migration). El código que se integra procede de la versión [**1.1.20**](https://github.com/SeaQL/sea-orm/tree/1.1.20/sea-orm-migration) en lugar de usarlo como dependencia ya que su paradigma de CLI no es compatible con el ciclo de vida de las @@ -124,7 +124,7 @@ extensión. Los ficheros adaptados del original son: | `connection.rs` | Integración completa | | `manager.rs` | Adapta *features* propias | | `migrator.rs` | Adapta *features* propias y omite gestión de errores del CLI | -| `prelude.rs` | Excluye exportaciones del CLI | +| `prelude.rs` | Absorbido en `migration.rs`, descarta exportaciones del CLI | | `schema.rs` | Integra con ajustes, original de [loco](https://github.com/loco-rs/loco) | | `seaql_migrations.rs` | Integración completa | diff --git a/extensions/pagetop-seaorm/src/db.rs b/extensions/pagetop-seaorm/src/db.rs index f954d9ce..2b47c399 100644 --- a/extensions/pagetop-seaorm/src/db.rs +++ b/extensions/pagetop-seaorm/src/db.rs @@ -1,30 +1,52 @@ -use pagetop::core::TypeInfo; -use pagetop::trace; +//! API completa de SeaORM para operaciones con la base de datos. +//! +//! Re-exporta el *prelude* de SeaORM (entidades, traits, tipos de valor, macros de derivación…) +//! y expone tres funciones de consulta propias. Con una sola importación tienes todo lo necesario +//! para definir entidades y realizar operaciones CRUD: +//! +//! ```rust,ignore +//! use pagetop_seaorm::db::*; +//! ``` +//! +//! Para definir el esquema de la base de datos o escribir migraciones usa además +//! [`crate::migration`]. -pub(crate) use url::Url as DbUri; +pub use sea_orm::prelude::*; -pub use sea_orm::error::{DbErr, RuntimeErr}; -pub use sea_orm::{DatabaseConnection as DbConn, ExecResult, QueryResult}; +use sea_orm::sea_query::{ + MysqlQueryBuilder, PostgresQueryBuilder, QueryStatementWriter, SqliteQueryBuilder, +}; +use sea_orm::{DatabaseBackend, ExecResult, Statement}; -use sea_orm::{ConnectionTrait, DatabaseBackend, Statement}; - -mod dbconn; -pub(crate) use dbconn::{run_now, DBCONN}; - -// Adaptación de `sea-orm-migration` (ver §Créditos en README.md). -mod migration; -pub use migration::prelude::*; -pub use migration::schema::*; +/// Devuelve una referencia al pool de conexiones para usarla con el sistema de entidades. +/// +/// Permite pasar la conexión a los métodos `all`, `one`, `exec`, etc. del sistema de entidades +/// de SeaORM. El coste de esta llamada es prácticamente nulo: sólo devuelve una referencia a un +/// valor inicializado una sola vez al arrancar la aplicación. +/// +/// ```rust,no_run +/// use pagetop_seaorm::db::*; +/// +/// // Consultas tipadas con el sistema de entidades de SeaORM: +/// // let users = User::find().all(connection()).await?; +/// // let user = User::find_by_id(1).one(connection()).await?; +/// // User::insert(model).exec(connection()).await?; +/// let _conn = connection(); +/// ``` +pub fn connection() -> &'static DatabaseConnection { + &super::DBCONN +} /// Ejecuta una consulta para devolver todas las filas resultantes. /// -/// Acepta cualquier tipo que implemente [`QueryStatementWriter`] (p. ej. [`SelectStatement`]) y +/// Acepta cualquier tipo que implemente [`crate::migration::QueryStatementWriter`] (p. ej. [`crate::migration::SelectStatement`]) y /// serializa la sentencia al dialecto de la base de datos configurada antes de ejecutarla. Cada /// fila se devuelve como un [`QueryResult`] sin tipar; extrae los valores con /// [`QueryResult::try_get`]. /// /// ```rust,no_run /// use pagetop_seaorm::db::*; +/// use pagetop_seaorm::migration::*; /// /// async fn example() -> Result<(), DbErr> { /// let mut stmt = Query::select() @@ -40,7 +62,7 @@ pub use migration::schema::*; /// } /// ``` pub async fn fetch_all(stmt: &mut Q) -> Result, DbErr> { - let dbconn = &*DBCONN; + let dbconn = &*super::DBCONN; let dbbackend = dbconn.get_database_backend(); dbconn .query_all(Statement::from_string( @@ -61,6 +83,7 @@ pub async fn fetch_all(stmt: &mut Q) -> Result Result<(), DbErr> { /// let mut stmt = Query::select() @@ -78,7 +101,7 @@ pub async fn fetch_all(stmt: &mut Q) -> Result( stmt: &mut Q, ) -> Result, DbErr> { - let dbconn = &*DBCONN; + let dbconn = &*super::DBCONN; let dbbackend = dbconn.get_database_backend(); dbconn .query_one(Statement::from_string( @@ -95,8 +118,8 @@ pub async fn fetch_one( /// Ejecuta una sentencia SQL en crudo (INSERT, UPDATE, DELETE…) y devuelve el resultado de /// la operación. /// -/// A diferencia de [`fetch_all`] y [`fetch_one`], no construye la consulta, sino que la recibe como -/// cadena ya formada. Útil para sentencias avanzadas o para migraciones puntuales. El +/// A diferencia de [`fetch_all`] y [`fetch_one`], no construye la consulta, sino que la recibe +/// como cadena ya formada. Útil para sentencias avanzadas o para migraciones puntuales. El /// [`ExecResult`] devuelto permite consultar las filas afectadas o el último ID insertado. /// /// ```rust,no_run @@ -109,76 +132,9 @@ pub async fn fetch_one( /// } /// ``` pub async fn execute(stmt: impl Into) -> Result { - let dbconn = &*DBCONN; + let dbconn = &*super::DBCONN; let dbbackend = dbconn.get_database_backend(); dbconn .execute(Statement::from_string(dbbackend, stmt.into())) .await } - -pub trait MigratorBase { - fn run_up(); - - fn run_down(); -} - -#[rustfmt::skip] -impl MigratorBase for M { - fn run_up() { - if let Err(e) = run_now(Self::up(SchemaManagerConnection::Connection(&DBCONN), None)) { - trace::error!("Migration upgrade failed ({})", e); - }; - } - - fn run_down() { - if let Err(e) = run_now(Self::down(SchemaManagerConnection::Connection(&DBCONN), None)) { - trace::error!("Migration downgrade failed ({})", e); - }; - } -} - -impl MigrationName for M { - fn name(&self) -> &str { - TypeInfo::NameTo(-2).of::() - } -} - -pub type MigrationItem = Box; - -#[macro_export] -macro_rules! install_migrations { - ( $($migration_module:ident),+ $(,)? ) => {{ - use $crate::db::{MigrationItem, MigratorBase, MigratorTrait}; - - struct Migrator; - impl MigratorTrait for Migrator { - fn migrations() -> Vec { - let mut m = Vec::::new(); - $( - m.push(Box::new(migration::$migration_module::Migration)); - )* - m - } - } - Migrator::run_up(); - }}; -} - -#[macro_export] -macro_rules! uninstall_migrations { - ( $($migration_module:ident),+ $(,)? ) => {{ - use $crate::db::{MigrationItem, MigratorBase, MigratorTrait}; - - struct Migrator; - impl MigratorTrait for Migrator { - fn migrations() -> Vec { - let mut m = Vec::::new(); - $( - m.push(Box::new(migration::$migration_module::Migration)); - )* - m - } - } - Migrator::run_down(); - }}; -} diff --git a/extensions/pagetop-seaorm/src/db/dbconn.rs b/extensions/pagetop-seaorm/src/db/dbconn.rs deleted file mode 100644 index e4881c08..00000000 --- a/extensions/pagetop-seaorm/src/db/dbconn.rs +++ /dev/null @@ -1,64 +0,0 @@ -use pagetop::trace; - -use crate::config; -use crate::db::{DbConn, DbUri}; - -use std::sync::LazyLock; - -use sea_orm::{ConnectOptions, Database}; - -pub use futures::executor::block_on as run_now; - -pub static DBCONN: LazyLock = LazyLock::new(|| { - trace::info!( - "Connecting to database \"{}\" using a pool of {} connections", - &config::SETTINGS.database.db_name, - &config::SETTINGS.database.max_pool_size - ); - - let db_uri = match config::SETTINGS.database.db_type.as_str() { - "mysql" | "postgres" => { - let mut tmp_uri = DbUri::parse( - format!( - "{}://{}/{}", - &config::SETTINGS.database.db_type, - &config::SETTINGS.database.db_host, - &config::SETTINGS.database.db_name - ) - .as_str(), - ) - .unwrap(); - tmp_uri - .set_username(config::SETTINGS.database.db_user.as_str()) - .unwrap(); - // https://github.com/launchbadge/sqlx/issues/1624 - tmp_uri - .set_password(Some(config::SETTINGS.database.db_pass.as_str())) - .unwrap(); - if let Some(port) = config::SETTINGS.database.db_port { - tmp_uri.set_port(Some(port)).unwrap(); - } - tmp_uri - } - "sqlite" => DbUri::parse( - format!( - "{}://{}", - &config::SETTINGS.database.db_type, - &config::SETTINGS.database.db_name - ) - .as_str(), - ) - .unwrap(), - _ => panic!( - "Unrecognized database type \"{}\"", - config::SETTINGS.database.db_type - ), - }; - - run_now(Database::connect::({ - let mut db_opt = ConnectOptions::new(db_uri.to_string()); - db_opt.max_connections(config::SETTINGS.database.max_pool_size); - db_opt - })) - .unwrap_or_else(|_| panic!("Failed to connect to database")) -}); diff --git a/extensions/pagetop-seaorm/src/db/migration.rs b/extensions/pagetop-seaorm/src/db/migration.rs deleted file mode 100644 index 0c32c10a..00000000 --- a/extensions/pagetop-seaorm/src/db/migration.rs +++ /dev/null @@ -1,35 +0,0 @@ -//pub mod cli; -pub mod connection; -pub mod manager; -pub mod migrator; -pub mod prelude; -pub mod schema; -pub mod seaql_migrations; -//pub mod util; - -pub use connection::*; -pub use manager::*; -//pub use migrator::*; - -pub use async_trait; -//pub use sea_orm; -//pub use sea_orm::sea_query; -use sea_orm::DbErr; - -pub trait MigrationName { - fn name(&self) -> &str; -} - -/// The migration definition -#[async_trait::async_trait] -pub trait MigrationTrait: MigrationName + Send + Sync { - /// Define actions to perform when applying the migration - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr>; - - /// Define actions to perform when rolling back the migration - async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { - Err(DbErr::Migration( - "Rollback not implemented for this migration".to_owned(), - )) - } -} diff --git a/extensions/pagetop-seaorm/src/db/migration/prelude.rs b/extensions/pagetop-seaorm/src/db/migration/prelude.rs deleted file mode 100644 index e2389faa..00000000 --- a/extensions/pagetop-seaorm/src/db/migration/prelude.rs +++ /dev/null @@ -1,12 +0,0 @@ -//pub use super::cli; - -pub use super::connection::IntoSchemaManagerConnection; -pub use super::connection::SchemaManagerConnection; -pub use super::manager::SchemaManager; -pub use super::migrator::MigratorTrait; -pub use super::{MigrationName, MigrationTrait}; -pub use async_trait; -pub use sea_orm; -pub use sea_orm::sea_query; -pub use sea_orm::sea_query::*; -pub use sea_orm::DeriveIden; diff --git a/extensions/pagetop-seaorm/src/lib.rs b/extensions/pagetop-seaorm/src/lib.rs index 0b686cb2..8af917ab 100644 --- a/extensions/pagetop-seaorm/src/lib.rs +++ b/extensions/pagetop-seaorm/src/lib.rs @@ -70,7 +70,7 @@ async fn main() -> std::io::Result<()> { **Escribe las migraciones** usando la API de SeaORM: ```rust,no_run -use pagetop_seaorm::db::*; +use pagetop_seaorm::migration::*; pub struct Migration; @@ -103,12 +103,75 @@ enum Users { use pagetop::prelude::*; +use sea_orm::{ConnectOptions, Database, DatabaseConnection}; +use url::Url; + +use std::sync::LazyLock; + include_locales!(LOCALES_SEAORM); pub mod config; pub mod db; +pub mod migration; + +pub(crate) use futures::executor::block_on as run_now; + +pub(crate) static DBCONN: LazyLock = LazyLock::new(|| { + trace::info!( + "Connecting to database \"{}\" using a pool of {} connections", + &config::SETTINGS.database.db_name, + &config::SETTINGS.database.max_pool_size + ); + + let db_uri = match config::SETTINGS.database.db_type.as_str() { + "mysql" | "postgres" => { + let mut tmp_uri = Url::parse( + format!( + "{}://{}/{}", + &config::SETTINGS.database.db_type, + &config::SETTINGS.database.db_host, + &config::SETTINGS.database.db_name + ) + .as_str(), + ) + .unwrap(); + tmp_uri + .set_username(config::SETTINGS.database.db_user.as_str()) + .unwrap(); + // https://github.com/launchbadge/sqlx/issues/1624 + tmp_uri + .set_password(Some(config::SETTINGS.database.db_pass.as_str())) + .unwrap(); + if let Some(port) = config::SETTINGS.database.db_port { + tmp_uri.set_port(Some(port)).unwrap(); + } + tmp_uri + } + "sqlite" => Url::parse( + format!( + "{}://{}", + &config::SETTINGS.database.db_type, + &config::SETTINGS.database.db_name + ) + .as_str(), + ) + .unwrap(), + _ => panic!( + "Unrecognized database type \"{}\"", + config::SETTINGS.database.db_type + ), + }; + + run_now(Database::connect::({ + let mut db_opt = ConnectOptions::new(db_uri.to_string()); + db_opt.max_connections(config::SETTINGS.database.max_pool_size); + db_opt + })) + .unwrap_or_else(|_| panic!("Failed to connect to database")) +}); + /// Implementa la extensión. pub struct SeaORM; @@ -122,6 +185,6 @@ impl Extension for SeaORM { } fn initialize(&self) { - std::sync::LazyLock::force(&db::DBCONN); + std::sync::LazyLock::force(&DBCONN); } } diff --git a/extensions/pagetop-seaorm/src/migration.rs b/extensions/pagetop-seaorm/src/migration.rs new file mode 100644 index 00000000..14b8f85b --- /dev/null +++ b/extensions/pagetop-seaorm/src/migration.rs @@ -0,0 +1,156 @@ +//! API para definir y ejecutar migraciones de base de datos. +//! +//! Re-exporta los tipos de SeaORM necesarios para escribir migraciones y ofrece las macros +//! [`crate::install_migrations`] y [`crate::uninstall_migrations`] para aplicarlas o revertirlas al +//! arrancar la extensión. +//! +//! ```rust,ignore +//! use pagetop_seaorm::db::*; +//! use pagetop_seaorm::migration::*; +//! ``` + +// **< Adaptación de `sea-orm-migration` (ver §Créditos en README.md) >***************************** + +//pub mod cli; +pub mod connection; +pub mod manager; +pub mod migrator; +//pub mod prelude; +pub mod schema; +pub mod seaql_migrations; +//pub mod util; + +pub use connection::*; +pub use manager::*; +//pub use migrator::*; + +pub use async_trait; +//pub use sea_orm; +//pub use sea_orm::sea_query; +pub use sea_orm::DbErr; + +pub trait MigrationName { + fn name(&self) -> &str; +} + +/// The migration definition +#[async_trait::async_trait] +pub trait MigrationTrait: MigrationName + Send + Sync { + /// Define actions to perform when applying the migration + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr>; + + /// Define actions to perform when rolling back the migration + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + Err(DbErr::Migration( + "Rollback not implemented for this migration".to_owned(), + )) + } +} + +// ************************************************************************************************* + +pub use migrator::MigratorTrait; +pub use schema::*; +pub use sea_orm::sea_query::*; +pub use sea_orm::DeriveIden; + +use pagetop::core::TypeInfo; +use pagetop::trace; + +impl MigrationName for M { + fn name(&self) -> &str { + TypeInfo::NameTo(-2).of::() + } +} + +pub type MigrationItem = Box; + +pub trait MigratorBase { + fn run_up(); + + fn run_down(); +} + +#[rustfmt::skip] +impl MigratorBase for M { + fn run_up() { + if let Err(e) = super::run_now(Self::up(SchemaManagerConnection::Connection(&super::DBCONN), None)) { + trace::error!("Migration upgrade failed ({})", e); + }; + } + + fn run_down() { + if let Err(e) = super::run_now(Self::down(SchemaManagerConnection::Connection(&super::DBCONN), None)) { + trace::error!("Migration downgrade failed ({})", e); + }; + } +} + +/// Aplica las migraciones pendientes al arrancar una extensión. +/// +/// Recibe uno o más módulos de migración y ejecuta el método `up` de los que aún no estén +/// registrados en la tabla `seaql_migrations`. Se invoca habitualmente desde +/// [`Extension::initialize`](pagetop::core::extension::Extension::initialize). +/// +/// ```rust,ignore +/// impl Extension for MyExt { +/// fn initialize(&self) { +/// install_migrations!( +/// m20240101_000001_create_users_table, +/// m20240115_000002_add_email_index, +/// ); +/// } +/// } +/// ``` +#[macro_export] +macro_rules! install_migrations { + ( $($migration_module:ident),+ $(,)? ) => {{ + use $crate::migration::{MigrationItem, MigratorBase, MigratorTrait}; + + struct Migrator; + impl MigratorTrait for Migrator { + fn migrations() -> Vec { + let mut m = Vec::::new(); + $( + m.push(Box::new(migration::$migration_module::Migration)); + )* + m + } + } + Migrator::run_up(); + }}; +} + +/// Revierte las migraciones de una extensión en orden inverso al de su aplicación. +/// +/// Ejecuta el método `down` de cada migración indicada. Si alguna no implementa `down`, +/// detiene el proceso con un error. Complementario a [`crate::install_migrations`]. +/// +/// ```rust,ignore +/// impl Extension for MyExt { +/// fn uninitialize(&self) { +/// uninstall_migrations!( +/// m20240101_000001_create_users_table, +/// m20240115_000002_add_email_index, +/// ); +/// } +/// } +/// ``` +#[macro_export] +macro_rules! uninstall_migrations { + ( $($migration_module:ident),+ $(,)? ) => {{ + use $crate::migration::{MigrationItem, MigratorBase, MigratorTrait}; + + struct Migrator; + impl MigratorTrait for Migrator { + fn migrations() -> Vec { + let mut m = Vec::::new(); + $( + m.push(Box::new(migration::$migration_module::Migration)); + )* + m + } + } + Migrator::run_down(); + }}; +} diff --git a/extensions/pagetop-seaorm/src/db/migration/connection.rs b/extensions/pagetop-seaorm/src/migration/connection.rs similarity index 100% rename from extensions/pagetop-seaorm/src/db/migration/connection.rs rename to extensions/pagetop-seaorm/src/migration/connection.rs diff --git a/extensions/pagetop-seaorm/src/db/migration/manager.rs b/extensions/pagetop-seaorm/src/migration/manager.rs similarity index 100% rename from extensions/pagetop-seaorm/src/db/migration/manager.rs rename to extensions/pagetop-seaorm/src/migration/manager.rs diff --git a/extensions/pagetop-seaorm/src/db/migration/migrator.rs b/extensions/pagetop-seaorm/src/migration/migrator.rs similarity index 100% rename from extensions/pagetop-seaorm/src/db/migration/migrator.rs rename to extensions/pagetop-seaorm/src/migration/migrator.rs diff --git a/extensions/pagetop-seaorm/src/db/migration/schema.rs b/extensions/pagetop-seaorm/src/migration/schema.rs similarity index 96% rename from extensions/pagetop-seaorm/src/db/migration/schema.rs rename to extensions/pagetop-seaorm/src/migration/schema.rs index 90910277..4dea5ee2 100644 --- a/extensions/pagetop-seaorm/src/db/migration/schema.rs +++ b/extensions/pagetop-seaorm/src/migration/schema.rs @@ -1,17 +1,16 @@ -//! Adapted from +//! Adaptación de //! -//! # Database Table Schema Helpers +//! # Ayudantes de esquema de base de datos //! -//! This module defines functions and helpers for creating database table -//! schemas using the `sea-orm` and `sea-query` libraries. +//! Define funciones y ayudantes para crear esquemas de tablas usando `sea-orm` y `sea-query`. //! -//! # Example +//! # Ejemplo //! -//! The following example shows how the user migration file should be and using -//! the schema helpers to create the Db fields. +//! El siguiente ejemplo muestra cómo escribir un archivo de migración usando los ayudantes +//! de esquema. //! //! ```rust -//! use pagetop_seaorm::db::*; +//! use pagetop_seaorm::migration::*; //! //! pub struct Migration; //! @@ -38,7 +37,7 @@ //! } //! } //! -//! #[derive(Iden)] +//! #[derive(DeriveIden)] //! pub enum Users { //! Table, //! Id, @@ -51,10 +50,9 @@ //! } //! ``` -use crate::db::Iden; - use sea_orm::sea_query::{ - self, Alias, ColumnDef, ColumnType, Expr, IntoIden, PgInterval, Table, TableCreateStatement, + self, Alias, ColumnDef, ColumnType, Expr, Iden, IntoIden, PgInterval, Table, + TableCreateStatement, }; #[derive(Iden)] @@ -599,7 +597,7 @@ pub fn array_uniq(col: T, elem_type: ColumnType) -> ColumnDef { array(col, elem_type).unique_key().take() } -/// Add timestamp columns (`CreatedAt` and `UpdatedAt`) to an existing table. +/// Añade las columnas de timestamp (`CreatedAt` y `UpdatedAt`) a una tabla existente. pub fn timestamps(t: TableCreateStatement) -> TableCreateStatement { let mut t = t; t.col(timestamp(GeneralIds::CreatedAt).default(Expr::current_timestamp())) @@ -607,7 +605,7 @@ pub fn timestamps(t: TableCreateStatement) -> TableCreateStatement { .take() } -/// Create an Alias. +/// Crea un alias. pub fn name>(name: T) -> Alias { Alias::new(name) } diff --git a/extensions/pagetop-seaorm/src/db/migration/seaql_migrations.rs b/extensions/pagetop-seaorm/src/migration/seaql_migrations.rs similarity index 100% rename from extensions/pagetop-seaorm/src/db/migration/seaql_migrations.rs rename to extensions/pagetop-seaorm/src/migration/seaql_migrations.rs