diff --git a/extensions/pagetop-seaorm/Cargo.toml b/extensions/pagetop-seaorm/Cargo.toml index 6e2b6fc7..e0a32977 100644 --- a/extensions/pagetop-seaorm/Cargo.toml +++ b/extensions/pagetop-seaorm/Cargo.toml @@ -20,7 +20,6 @@ postgres = ["sea-orm/sqlx-postgres"] sqlite = ["sea-orm/sqlx-sqlite"] [dependencies] -async-trait.workspace = true pagetop.workspace = true sea-orm.workspace = true sea-schema.workspace = true diff --git a/extensions/pagetop-seaorm/README.md b/extensions/pagetop-seaorm/README.md index 23f130aa..1f846891 100644 --- a/extensions/pagetop-seaorm/README.md +++ b/extensions/pagetop-seaorm/README.md @@ -70,6 +70,7 @@ mod migration; struct MyApp; +#[async_trait] impl Extension for MyApp { fn dependencies(&self) -> Vec { vec![ @@ -77,14 +78,14 @@ impl Extension for MyApp { ] } - fn initialize(&self) { + async fn initialize(&self) { install_migrations!(m20240101_000001_create_users); } } #[pagetop::main] async fn main() -> std::io::Result<()> { - Application::prepare(&MyApp).run()?.await + Application::prepare(&MyApp).await.run().await } ``` @@ -96,7 +97,7 @@ use pagetop_seaorm::migration::*; pub struct Migration; -#[async_trait::async_trait] +#[pagetop::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager diff --git a/extensions/pagetop-seaorm/src/db.rs b/extensions/pagetop-seaorm/src/db.rs index 3076d834..ee60a427 100644 --- a/extensions/pagetop-seaorm/src/db.rs +++ b/extensions/pagetop-seaorm/src/db.rs @@ -143,10 +143,6 @@ pub use sea_orm::{ ActiveValue, DatabaseTransaction, ExecResult, QueryOrder, QuerySelect, TransactionTrait, }; -/// Permite implementar *traits* con métodos `async`: -#[doc(inline)] -pub use async_trait; - /// Re-exporta el crate `sea_orm` íntegro como puerta de acceso a su API completa. /// /// Útil para tipos o utilidades que no están expuestos directamente en [`db::*`](self). La inmensa @@ -176,7 +172,13 @@ pub use sea_orm::sea_query as query; /// ``` #[inline] pub fn dbconn() -> &'static DatabaseConnection { - &super::DBCONN + super::DBCONN + // La inicialización requiere async (`Database::connect().await`). OnceLock asigna el valor + // con `.set()` en `Extension::initialize()` (contra LazyLock que sólo admite uso síncrono). + .get() + // Este texto no se multiplica al hacer `#[inline]`, es un `&'static str` que el compilador + // almacena una vez. Sólo se duplica una comprobación de nulo y un salto condicional. + .expect("Database not initialized: SeaORM extension must be listed as a dependency") } /// Ejecuta una sentencia SQL en crudo y devuelve su resultado. diff --git a/extensions/pagetop-seaorm/src/lib.rs b/extensions/pagetop-seaorm/src/lib.rs index 9cfcd350..eff88193 100644 --- a/extensions/pagetop-seaorm/src/lib.rs +++ b/extensions/pagetop-seaorm/src/lib.rs @@ -71,6 +71,7 @@ mod migration; struct MyApp; +#[async_trait] impl Extension for MyApp { fn dependencies(&self) -> Vec { vec![ @@ -78,14 +79,14 @@ impl Extension for MyApp { ] } - fn initialize(&self) { + async fn initialize(&self) { install_migrations!(m20240101_000001_create_users); } } #[pagetop::main] async fn main() -> std::io::Result<()> { - Application::prepare(&MyApp).run()?.await + Application::prepare(&MyApp).await.run().await } ``` @@ -97,7 +98,7 @@ use pagetop_seaorm::migration::*; pub struct Migration; -#[async_trait::async_trait] +#[pagetop::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager @@ -176,7 +177,7 @@ use pagetop::prelude::*; use sea_orm::{ConnectOptions, Database, DatabaseConnection}; use url::Url; -use std::sync::LazyLock; +use std::sync::OnceLock; include_locales!(LOCALES_SEAORM); @@ -186,73 +187,12 @@ pub mod db; pub mod migration; -// Ejecuta un *future* de forma síncrona dentro del runtime de Tokio. -// -// Usa [`tokio::task::block_in_place`] para ceder el hilo actual al código bloqueante sin detener el -// *pool* de trabajo de Tokio, y a continuación ejecuta el *future* con el *handle* del *runtime* -// activo. Requiere el *runtime* multi-hilo (predeterminado con `#[pagetop::main]`). -// -// En tests, `#[pagetop::test]` aplica `multi_thread` por defecto. Si se utiliza `#[tokio::test]` -// directamente, habría que añadir `(flavor = "multi_thread")` si el test invoca código que llame a -// esta función. -pub(crate) fn run_now(future: F) -> F::Output { - tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(future)) -} - -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: String = match config::SETTINGS.database.db_type { - config::DbType::Unset => panic!( - "database.db_type is not configured: set it to \"mysql\", \"postgres\" or \"sqlite\"" - ), - config::DbType::Mysql | config::DbType::Postgres => { - let scheme = if matches!(config::SETTINGS.database.db_type, config::DbType::Mysql) { - "mysql" - } else { - "postgres" - }; - let mut tmp_uri = Url::parse(&format!( - "{}://{}/{}", - scheme, - &config::SETTINGS.database.db_host, - &config::SETTINGS.database.db_name - )) - .expect("Invalid database URL: check db_host and db_name in config"); - tmp_uri - .set_username(config::SETTINGS.database.db_user.as_str()) - .expect("Failed to set db_user in connection URL"); - // https://github.com/launchbadge/sqlx/issues/1624 - tmp_uri - .set_password(Some(config::SETTINGS.database.db_pass.as_str())) - .expect("Failed to set db_pass in connection URL"); - if let Some(port) = config::SETTINGS.database.db_port { - tmp_uri - .set_port(Some(port)) - .expect("Failed to set db_port in connection URL"); - } - tmp_uri.to_string() - } - config::DbType::Sqlite => { - format!("sqlite://{}", &config::SETTINGS.database.db_name) - } - }; - - run_now(Database::connect::({ - let mut db_opt = ConnectOptions::new(db_uri); - db_opt.max_connections(config::SETTINGS.database.max_pool_size); - db_opt - })) - .expect("Failed to connect to database") -}); +static DBCONN: OnceLock = OnceLock::new(); /// Implementa la extensión. pub struct SeaORM; +#[async_trait] impl Extension for SeaORM { fn name(&self) -> L10n { L10n::t("extension_name", &LOCALES_SEAORM) @@ -262,7 +202,57 @@ impl Extension for SeaORM { L10n::t("extension_description", &LOCALES_SEAORM) } - fn initialize(&self) { - std::sync::LazyLock::force(&DBCONN); + async fn initialize(&self) { + trace::info!( + "Connecting to database \"{}\" using a pool of {} connections", + &config::SETTINGS.database.db_name, + &config::SETTINGS.database.max_pool_size + ); + + let db_uri: String = match config::SETTINGS.database.db_type { + config::DbType::Unset => panic!( + "database.db_type is not configured: use \"mysql\", \"postgres\" or \"sqlite\"" + ), + config::DbType::Mysql | config::DbType::Postgres => { + let scheme = if matches!(config::SETTINGS.database.db_type, config::DbType::Mysql) { + "mysql" + } else { + "postgres" + }; + let mut tmp_uri = Url::parse(&format!( + "{}://{}/{}", + scheme, + &config::SETTINGS.database.db_host, + &config::SETTINGS.database.db_name + )) + .expect("Invalid database URL: check db_host and db_name in config"); + tmp_uri + .set_username(config::SETTINGS.database.db_user.as_str()) + .expect("Failed to set db_user in connection URL"); + tmp_uri + // https://github.com/launchbadge/sqlx/issues/1624 + .set_password(Some(config::SETTINGS.database.db_pass.as_str())) + .expect("Failed to set db_pass in connection URL"); + if let Some(port) = config::SETTINGS.database.db_port { + tmp_uri + .set_port(Some(port)) + .expect("Failed to set db_port in connection URL"); + } + tmp_uri.to_string() + } + config::DbType::Sqlite => { + format!("sqlite://{}", &config::SETTINGS.database.db_name) + } + }; + + let conn = Database::connect::({ + let mut db_opt = ConnectOptions::new(db_uri); + db_opt.max_connections(config::SETTINGS.database.max_pool_size); + db_opt + }) + .await + .expect("Failed to connect to database"); + + DBCONN.set(conn).expect("DBCONN already initialized"); } } diff --git a/extensions/pagetop-seaorm/src/migration.rs b/extensions/pagetop-seaorm/src/migration.rs index 2e267e8d..729f7afb 100644 --- a/extensions/pagetop-seaorm/src/migration.rs +++ b/extensions/pagetop-seaorm/src/migration.rs @@ -34,7 +34,7 @@ //! //! pub struct Migration; //! -//! #[async_trait::async_trait] +//! #[pagetop::async_trait] //! impl MigrationTrait for Migration { //! async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { //! manager @@ -127,9 +127,7 @@ pub use connection::*; pub use manager::*; //pub use migrator::*; -/// Permite implementar *traits* con métodos `async`: -#[doc(inline)] -pub use async_trait; +//pub use async_trait; //pub use sea_orm; //pub use sea_orm::sea_query; pub use sea_orm::DbErr; @@ -139,7 +137,7 @@ pub trait MigrationName { } /// The migration definition -#[async_trait::async_trait] +#[pagetop::async_trait] pub trait MigrationTrait: MigrationName + Send + Sync { /// Define actions to perform when applying the migration async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr>; @@ -173,35 +171,37 @@ impl MigrationName for M { /// Elemento de migración listo para incluir en la lista de un [`MigratorTrait`]. pub type MigrationItem = Box; -/// Interfaz síncrona para ejecutar migraciones desde código no asíncrono. +/// Interfaz asíncrona para ejecutar migraciones. /// /// Todo tipo que implemente [`MigratorTrait`] obtiene esta interfaz automáticamente, incluidos los /// tipos generados por los macros [`install_migrations!`](crate::install_migrations) y /// [`uninstall_migrations!`](crate::uninstall_migrations). +#[pagetop::async_trait] pub trait MigratorBase { /// Ejecuta las migraciones pendientes en orden ascendente. /// /// Provoca un `panic!` si alguna migración falla, evitando que la aplicación arranque con un /// esquema de base de datos inconsistente. - fn run_up(); + async fn run_up(); /// Revierte todas las migraciones en orden descendente. /// /// Provoca un `panic!` si alguna reversión falla. - fn run_down(); + async fn run_down(); } +#[pagetop::async_trait] impl MigratorBase for M { - fn run_up() { - let conn = SchemaManagerConnection::Connection(&super::DBCONN); - if let Err(e) = super::run_now(Self::up(conn, None)) { + async fn run_up() { + let conn = SchemaManagerConnection::Connection(super::db::dbconn()); + if let Err(e) = Self::up(conn, None).await { panic!("Migration upgrade failed: {e}"); } } - fn run_down() { - let conn = SchemaManagerConnection::Connection(&super::DBCONN); - if let Err(e) = super::run_now(Self::down(conn, None)) { + async fn run_down() { + let conn = SchemaManagerConnection::Connection(super::db::dbconn()); + if let Err(e) = Self::down(conn, None).await { panic!("Migration downgrade failed: {e}"); } } @@ -228,8 +228,9 @@ impl MigratorBase for M { /// ```rust,ignore /// mod migration; /// +/// #[async_trait] /// impl Extension for MyExt { -/// fn initialize(&self) { +/// async fn initialize(&self) { /// install_migrations!( /// m20240101_000001_create_users, /// m20240115_000002_add_email_index, @@ -252,7 +253,7 @@ macro_rules! install_migrations { m } } - Migrator::run_up(); + Migrator::run_up().await; }}; } @@ -269,8 +270,9 @@ macro_rules! install_migrations { /// /// En `src/lib.rs`: /// ```rust,ignore +/// #[async_trait] /// impl Extension for MyExt { -/// fn uninitialize(&self) { +/// async fn uninitialize(&self) { /// uninstall_migrations!( /// m20240101_000001_create_users, /// m20240115_000002_add_email_index, @@ -293,6 +295,6 @@ macro_rules! uninstall_migrations { m } } - Migrator::run_down(); + Migrator::run_down().await; }}; } diff --git a/extensions/pagetop-seaorm/src/migration/connection.rs b/extensions/pagetop-seaorm/src/migration/connection.rs index a34acc47..61fd74d7 100644 --- a/extensions/pagetop-seaorm/src/migration/connection.rs +++ b/extensions/pagetop-seaorm/src/migration/connection.rs @@ -10,7 +10,7 @@ pub enum SchemaManagerConnection<'c> { Transaction(&'c DatabaseTransaction), } -#[async_trait::async_trait] +#[pagetop::async_trait] impl ConnectionTrait for SchemaManagerConnection<'_> { fn get_database_backend(&self) -> DbBackend { match self { @@ -55,7 +55,7 @@ impl ConnectionTrait for SchemaManagerConnection<'_> { } } -#[async_trait::async_trait] +#[pagetop::async_trait] impl TransactionTrait for SchemaManagerConnection<'_> { async fn begin(&self) -> Result { match self { diff --git a/extensions/pagetop-seaorm/src/migration/migrator.rs b/extensions/pagetop-seaorm/src/migration/migrator.rs index d10b4edb..b9167549 100644 --- a/extensions/pagetop-seaorm/src/migration/migrator.rs +++ b/extensions/pagetop-seaorm/src/migration/migrator.rs @@ -57,7 +57,7 @@ impl Migration { } /// Performing migrations on a database -#[async_trait::async_trait] +#[pagetop::async_trait] pub trait MigratorTrait: Send { /// Vector of migrations in time sequence fn migrations() -> Vec>; diff --git a/extensions/pagetop-seaorm/src/migration/schema.rs b/extensions/pagetop-seaorm/src/migration/schema.rs index 0ad92946..88ce8b13 100644 --- a/extensions/pagetop-seaorm/src/migration/schema.rs +++ b/extensions/pagetop-seaorm/src/migration/schema.rs @@ -15,7 +15,7 @@ //! //! pub struct Migration; //! -//! #[async_trait::async_trait] +//! #[pagetop::async_trait] //! impl MigrationTrait for Migration { //! async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { //! let table = table_auto(Users::Table)