♻️ (seaorm): Adapta al arranque async
- Elimina `run_now` y convierte `initialize()` en `async fn`. - `DBCONN` pasa de `LazyLock` a `OnceLock`; `MigratorBase` adopta `#[pagetop::async_trait]` con métodos async nativos.
This commit is contained in:
parent
1139df6210
commit
9b2d430d8b
8 changed files with 94 additions and 100 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ mod migration;
|
|||
|
||||
struct MyApp;
|
||||
|
||||
#[async_trait]
|
||||
impl Extension for MyApp {
|
||||
fn dependencies(&self) -> Vec<ExtensionRef> {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ mod migration;
|
|||
|
||||
struct MyApp;
|
||||
|
||||
#[async_trait]
|
||||
impl Extension for MyApp {
|
||||
fn dependencies(&self) -> Vec<ExtensionRef> {
|
||||
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<F: std::future::Future>(future: F) -> F::Output {
|
||||
tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(future))
|
||||
}
|
||||
|
||||
pub(crate) static DBCONN: LazyLock<DatabaseConnection> = 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::<ConnectOptions>({
|
||||
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<DatabaseConnection> = 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::<ConnectOptions>({
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<M: MigrationTrait> MigrationName for M {
|
|||
/// Elemento de migración listo para incluir en la lista de un [`MigratorTrait`].
|
||||
pub type MigrationItem = Box<dyn MigrationTrait>;
|
||||
|
||||
/// 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<M: MigratorTrait> 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<M: MigratorTrait> 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;
|
||||
}};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<DatabaseTransaction, DbErr> {
|
||||
match self {
|
||||
|
|
|
|||
|
|
@ -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<Box<dyn MigrationTrait>>;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue