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