Compare commits
2 commits
aa931ea052
...
026448e511
| Author | SHA1 | Date | |
|---|---|---|---|
| 026448e511 | |||
| 796ae5ce81 |
13 changed files with 357 additions and 234 deletions
|
|
@ -38,7 +38,8 @@ db_name = "my_app.db"
|
||||||
max_pool_size = 5
|
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:
|
**Declara la extensión** en tu aplicación o en la extensión que la requiera:
|
||||||
|
|
||||||
|
|
@ -68,7 +69,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
**Escribe las migraciones** usando la API de SeaORM:
|
**Escribe las migraciones** usando la API de SeaORM:
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
use pagetop_seaorm::db::*;
|
use pagetop_seaorm::migration::*;
|
||||||
|
|
||||||
pub struct Migration;
|
pub struct Migration;
|
||||||
|
|
||||||
|
|
@ -110,22 +111,22 @@ 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,
|
usada por el módulo de migraciones para interrogar la estructura real de la base de datos (tablas,
|
||||||
columnas, índices y claves externas).
|
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
|
[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
|
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
|
usarlo como dependencia ya que su paradigma de CLI no es compatible con el ciclo de vida de las
|
||||||
extensiones de PageTop, donde las migraciones deben ejecutarse durante la inicialización de cada
|
extensiones de PageTop, donde las migraciones deben ejecutarse durante la inicialización de cada
|
||||||
extensión. Los ficheros adaptados del original son:
|
extensión. Los ficheros adaptados del original son:
|
||||||
|
|
||||||
| Archivos | Observaciones |
|
| Archivos | Observaciones |
|
||||||
|----------------------------|--------------------------------------------------------------|
|
|-----------------------|--------------------------------------------------------------------------|
|
||||||
| `lib.rs` en `migration.rs` | Excluye módulos y exportaciones del CLI |
|
| `lib.rs` | Incluido en `migration.rs`, descarta módulos y exportaciones del CLI |
|
||||||
| `connection.rs` | Integración completa |
|
| `connection.rs` | Integración completa |
|
||||||
| `manager.rs` | Adapta *features* propias |
|
| `manager.rs` | Adapta *features* propias |
|
||||||
| `migrator.rs` | Adapta *features* propias y omite gestión de errores del CLI |
|
| `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` | Integración ajustada con cambios menores |
|
| `schema.rs` | Integra con ajustes, original de [loco](https://github.com/loco-rs/loco) |
|
||||||
| `seaql_migrations.rs` | Integración completa |
|
| `seaql_migrations.rs` | Integración completa |
|
||||||
|
|
||||||
|
|
||||||
## 🚧 Advertencia
|
## 🚧 Advertencia
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ include_config!(SETTINGS: Settings => [
|
||||||
"database.db_user" => "",
|
"database.db_user" => "",
|
||||||
"database.db_pass" => "",
|
"database.db_pass" => "",
|
||||||
"database.db_host" => "localhost",
|
"database.db_host" => "localhost",
|
||||||
"database.db_port" => 0,
|
|
||||||
"database.max_pool_size" => 5,
|
"database.max_pool_size" => 5,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -57,8 +56,9 @@ pub struct Database {
|
||||||
pub db_pass: String,
|
pub db_pass: String,
|
||||||
/// Servidor de conexión a la base de datos (para mysql/postgres).
|
/// Servidor de conexión a la base de datos (para mysql/postgres).
|
||||||
pub db_host: String,
|
pub db_host: String,
|
||||||
/// Puerto de conexión a la base de datos, normalmente 3306 (para mysql) ó 5432 (para postgres).
|
/// Puerto de conexión a la base de datos (para mysql/postgres). Si es `None` se usa el puerto
|
||||||
pub db_port: u16,
|
/// predeterminado para el motor: 3306 para MySQL y 5432 para PostgreSQL.
|
||||||
|
pub db_port: Option<u16>,
|
||||||
/// Número máximo de conexiones habilitadas.
|
/// Número máximo de conexiones habilitadas.
|
||||||
pub max_pool_size: u32,
|
pub max_pool_size: u32,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,68 @@
|
||||||
use pagetop::core::TypeInfo;
|
//! API completa de SeaORM para operaciones con la base de datos.
|
||||||
use pagetop::trace;
|
//!
|
||||||
|
//! 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 use url::Url as DbUri;
|
pub use sea_orm::prelude::*;
|
||||||
|
|
||||||
pub use sea_orm::error::{DbErr, RuntimeErr};
|
use sea_orm::sea_query::{
|
||||||
pub use sea_orm::{DatabaseConnection as DbConn, ExecResult, QueryResult};
|
MysqlQueryBuilder, PostgresQueryBuilder, QueryStatementWriter, SqliteQueryBuilder,
|
||||||
|
};
|
||||||
|
use sea_orm::{DatabaseBackend, ExecResult, Statement};
|
||||||
|
|
||||||
use sea_orm::{ConnectionTrait, DatabaseBackend, Statement};
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
mod dbconn;
|
/// Ejecuta una consulta para devolver todas las filas resultantes.
|
||||||
pub(crate) use dbconn::{run_now, DBCONN};
|
///
|
||||||
|
/// Acepta cualquier tipo que implemente [`crate::migration::QueryStatementWriter`] (p. ej. [`crate::migration::SelectStatement`]) y
|
||||||
// Adaptación de `sea-orm-migration` (ver §Créditos en README.md).
|
/// serializa la sentencia al dialecto de la base de datos configurada antes de ejecutarla. Cada
|
||||||
mod migration;
|
/// fila se devuelve como un [`QueryResult`] sin tipar; extrae los valores con
|
||||||
pub use migration::prelude::*;
|
/// [`QueryResult::try_get`].
|
||||||
pub use migration::schema::*;
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
pub async fn query<Q: QueryStatementWriter>(stmt: &mut Q) -> Result<Vec<QueryResult>, DbErr> {
|
/// use pagetop_seaorm::db::*;
|
||||||
let dbconn = &*DBCONN;
|
/// use pagetop_seaorm::migration::*;
|
||||||
|
///
|
||||||
|
/// 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<Q: QueryStatementWriter>(stmt: &mut Q) -> Result<Vec<QueryResult>, DbErr> {
|
||||||
|
let dbconn = &*super::DBCONN;
|
||||||
let dbbackend = dbconn.get_database_backend();
|
let dbbackend = dbconn.get_database_backend();
|
||||||
dbconn
|
dbconn
|
||||||
.query_all(Statement::from_string(
|
.query_all(Statement::from_string(
|
||||||
|
|
@ -31,8 +76,32 @@ pub async fn query<Q: QueryStatementWriter>(stmt: &mut Q) -> Result<Vec<QueryRes
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn exec<Q: QueryStatementWriter>(stmt: &mut Q) -> Result<Option<QueryResult>, DbErr> {
|
/// Ejecuta una consulta y devuelve sólo la primera fila, si existe.
|
||||||
let dbconn = &*DBCONN;
|
///
|
||||||
|
/// 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::*;
|
||||||
|
/// use pagetop_seaorm::migration::*;
|
||||||
|
///
|
||||||
|
/// 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<Q: QueryStatementWriter>(
|
||||||
|
stmt: &mut Q,
|
||||||
|
) -> Result<Option<QueryResult>, DbErr> {
|
||||||
|
let dbconn = &*super::DBCONN;
|
||||||
let dbbackend = dbconn.get_database_backend();
|
let dbbackend = dbconn.get_database_backend();
|
||||||
dbconn
|
dbconn
|
||||||
.query_one(Statement::from_string(
|
.query_one(Statement::from_string(
|
||||||
|
|
@ -46,77 +115,26 @@ pub async fn exec<Q: QueryStatementWriter>(stmt: &mut Q) -> Result<Option<QueryR
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn exec_raw(stmt: String) -> Result<ExecResult, DbErr> {
|
/// Ejecuta una sentencia SQL en crudo (INSERT, UPDATE, DELETE…) y devuelve el resultado de
|
||||||
let dbconn = &*DBCONN;
|
/// 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<String>) -> Result<ExecResult, DbErr> {
|
||||||
|
let dbconn = &*super::DBCONN;
|
||||||
let dbbackend = dbconn.get_database_backend();
|
let dbbackend = dbconn.get_database_backend();
|
||||||
dbconn
|
dbconn
|
||||||
.execute(Statement::from_string(dbbackend, stmt))
|
.execute(Statement::from_string(dbbackend, stmt.into()))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MigratorBase {
|
|
||||||
fn run_up();
|
|
||||||
|
|
||||||
fn run_down();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
impl<M: MigratorTrait> 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<M: MigrationTrait> MigrationName for M {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
TypeInfo::NameTo(-2).of::<M>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type MigrationItem = Box<dyn MigrationTrait>;
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! install_migrations {
|
|
||||||
( $($migration_module:ident),+ $(,)? ) => {{
|
|
||||||
use $crate::db::{MigrationItem, MigratorBase, MigratorTrait};
|
|
||||||
|
|
||||||
struct Migrator;
|
|
||||||
impl MigratorTrait for Migrator {
|
|
||||||
fn migrations() -> Vec<MigrationItem> {
|
|
||||||
let mut m = Vec::<MigrationItem>::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<MigrationItem> {
|
|
||||||
let mut m = Vec::<MigrationItem>::new();
|
|
||||||
$(
|
|
||||||
m.push(Box::new(migration::$migration_module::Migration));
|
|
||||||
)*
|
|
||||||
m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Migrator::run_down();
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,69 +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<DbConn> = 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 config::SETTINGS.database.db_port != 0 {
|
|
||||||
tmp_uri
|
|
||||||
.set_port(Some(config::SETTINGS.database.db_port))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
tmp_uri
|
|
||||||
}
|
|
||||||
"sqlite" => DbUri::parse(
|
|
||||||
format!(
|
|
||||||
"{}://{}",
|
|
||||||
&config::SETTINGS.database.db_type,
|
|
||||||
&config::SETTINGS.database.db_name
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
_ => {
|
|
||||||
trace::error!(
|
|
||||||
"Unrecognized database type \"{}\"",
|
|
||||||
&config::SETTINGS.database.db_type
|
|
||||||
);
|
|
||||||
DbUri::parse("").unwrap()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
run_now(Database::connect::<ConnectOptions>({
|
|
||||||
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"))
|
|
||||||
});
|
|
||||||
|
|
@ -1,33 +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("We Don't Do That Here".to_owned()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -39,7 +39,8 @@ db_name = "my_app.db"
|
||||||
max_pool_size = 5
|
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:
|
**Declara la extensión** en tu aplicación o en la extensión que la requiera:
|
||||||
|
|
||||||
|
|
@ -69,7 +70,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
**Escribe las migraciones** usando la API de SeaORM:
|
**Escribe las migraciones** usando la API de SeaORM:
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
use pagetop_seaorm::db::*;
|
use pagetop_seaorm::migration::*;
|
||||||
|
|
||||||
pub struct Migration;
|
pub struct Migration;
|
||||||
|
|
||||||
|
|
@ -102,12 +103,75 @@ enum Users {
|
||||||
|
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
|
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
include_locales!(LOCALES_SEAORM);
|
include_locales!(LOCALES_SEAORM);
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
pub mod db;
|
pub mod db;
|
||||||
|
|
||||||
|
pub mod migration;
|
||||||
|
|
||||||
|
pub(crate) use futures::executor::block_on as run_now;
|
||||||
|
|
||||||
|
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 = 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::<ConnectOptions>({
|
||||||
|
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.
|
/// Implementa la extensión.
|
||||||
pub struct SeaORM;
|
pub struct SeaORM;
|
||||||
|
|
||||||
|
|
@ -121,6 +185,6 @@ impl Extension for SeaORM {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize(&self) {
|
fn initialize(&self) {
|
||||||
std::sync::LazyLock::force(&db::DBCONN);
|
std::sync::LazyLock::force(&DBCONN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
156
extensions/pagetop-seaorm/src/migration.rs
Normal file
156
extensions/pagetop-seaorm/src/migration.rs
Normal file
|
|
@ -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<M: MigrationTrait> MigrationName for M {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
TypeInfo::NameTo(-2).of::<M>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type MigrationItem = Box<dyn MigrationTrait>;
|
||||||
|
|
||||||
|
pub trait MigratorBase {
|
||||||
|
fn run_up();
|
||||||
|
|
||||||
|
fn run_down();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
impl<M: MigratorTrait> 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<MigrationItem> {
|
||||||
|
let mut m = Vec::<MigrationItem>::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<MigrationItem> {
|
||||||
|
let mut m = Vec::<MigrationItem>::new();
|
||||||
|
$(
|
||||||
|
m.push(Box::new(migration::$migration_module::Migration));
|
||||||
|
)*
|
||||||
|
m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Migrator::run_down();
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
//! Adapted from <https://github.com/loco-rs/loco/blob/master/src/schema.rs>
|
//! Adaptación de <https://github.com/loco-rs/loco/blob/master/src/schema.rs>
|
||||||
//!
|
//!
|
||||||
//! # Database Table Schema Helpers
|
//! # Ayudantes de esquema de base de datos
|
||||||
//!
|
//!
|
||||||
//! This module defines functions and helpers for creating database table
|
//! Define funciones y ayudantes para crear esquemas de tablas usando `sea-orm` y `sea-query`.
|
||||||
//! schemas using the `sea-orm` and `sea-query` libraries.
|
|
||||||
//!
|
//!
|
||||||
//! # Example
|
//! # Ejemplo
|
||||||
//!
|
//!
|
||||||
//! The following example shows how the user migration file should be and using
|
//! El siguiente ejemplo muestra cómo escribir un archivo de migración usando los ayudantes
|
||||||
//! the schema helpers to create the Db fields.
|
//! de esquema.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use pagetop_seaorm::db::*;
|
//! use pagetop_seaorm::migration::*;
|
||||||
//!
|
//!
|
||||||
//! pub struct Migration;
|
//! pub struct Migration;
|
||||||
//!
|
//!
|
||||||
|
|
@ -38,7 +37,7 @@
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! #[derive(Iden)]
|
//! #[derive(DeriveIden)]
|
||||||
//! pub enum Users {
|
//! pub enum Users {
|
||||||
//! Table,
|
//! Table,
|
||||||
//! Id,
|
//! Id,
|
||||||
|
|
@ -51,10 +50,9 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::db::Iden;
|
|
||||||
|
|
||||||
use sea_orm::sea_query::{
|
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)]
|
#[derive(Iden)]
|
||||||
|
|
@ -599,7 +597,7 @@ pub fn array_uniq<T: IntoIden>(col: T, elem_type: ColumnType) -> ColumnDef {
|
||||||
array(col, elem_type).unique_key().take()
|
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 {
|
pub fn timestamps(t: TableCreateStatement) -> TableCreateStatement {
|
||||||
let mut t = t;
|
let mut t = t;
|
||||||
t.col(timestamp(GeneralIds::CreatedAt).default(Expr::current_timestamp()))
|
t.col(timestamp(GeneralIds::CreatedAt).default(Expr::current_timestamp()))
|
||||||
|
|
@ -607,7 +605,7 @@ pub fn timestamps(t: TableCreateStatement) -> TableCreateStatement {
|
||||||
.take()
|
.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an Alias.
|
/// Crea un alias.
|
||||||
pub fn name<T: Into<String>>(name: T) -> Alias {
|
pub fn name<T: Into<String>>(name: T) -> Alias {
|
||||||
Alias::new(name)
|
Alias::new(name)
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue