//! Definición de entidades y acceso a la base de datos. //! //! Agrupa los *traits*, macros y tipos del sistema de entidades de SeaORM, junto con las funciones //! [`dbconn`], [`execute`], [`fetch_all`] y [`fetch_one`], en una sola importación: //! //! ```rust,no_run //! use pagetop_seaorm::db::*; //! ``` //! //! El sistema de entidades (`Entity::find()`, `Entity::insert()`, transacciones) es el camino //! recomendado para la mayoría de operaciones. Las funciones [`execute`], [`fetch_all`] y //! [`fetch_one`] ofrecen vías alternativas para cuando ese sistema no es suficiente, como consultas //! sin entidad concreta, SQL específico para el motor de base de datos utilizado o sentencias //! puntuales. //! //! Estas funciones integran los valores como literales escapados, no como parámetros de base de //! datos. Para consultas con datos del usuario, el sistema de entidades es más robusto. Si aun así //! se necesita SQL en crudo con parámetros reales, se puede construir un [`sea_orm::Statement`] //! directamente con [`sea_orm::Statement::from_sql_and_values`]. //! //! # Tipos esenciales //! //! Destacan los siguientes elementos de uso más frecuente: //! //! - **Acceso**: [`DatabaseConnection`], [`dbconn`] (para obtener el pool de conexiones). //! - **Consultas**: [`EntityTrait`], [`QueryFilter`], [`QueryOrder`], [`QuerySelect`]. //! - **Transacciones**: [`TransactionTrait`], [`DatabaseTransaction`]. //! - **Modelos activos**: [`ActiveModelTrait`], [`ActiveValue`] ([`ActiveValue::Set`], //! [`ActiveValue::Unchanged`], [`ActiveValue::NotSet`]). //! - **Macros de derivación**: [`DeriveEntityModel`], [`DeriveColumn`], [`DerivePrimaryKey`], //! [`DeriveRelation`], [`EnumIter`]. //! - **Errores**: [`DbErr`]. //! - **Resultados**: [`QueryResult`] (filas sin tipar), [`ExecResult`] (INSERT/UPDATE/DELETE). //! //! # Definir una entidad //! //! ```rust,no_run //! use pagetop_seaorm::db::*; //! //! #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] //! #[sea_orm(table_name = "users")] //! // El struct debe llamarse `Model`: es un requisito de `DeriveEntityModel`. //! pub struct Model { //! #[sea_orm(primary_key)] //! pub id: i32, //! pub email: String, //! pub name: String, //! } //! //! #[derive(Clone, Copy, Debug, EnumIter, DeriveRelation)] //! pub enum Relation {} //! //! // `DeriveEntityModel` genera también `ActiveModel`, `Entity`, `Column` y `PrimaryKey`. //! impl ActiveModelBehavior for ActiveModel {} //! ``` //! //! # Operaciones CRUD //! //! ```rust,ignore //! use pagetop_seaorm::db::*; //! //! // El código asume que existe un módulo `user` con una entidad SeaORM definida. //! //! async fn example() -> Result<(), DbErr> { //! // Buscar todos los registros. //! let users = user::Entity::find().all(dbconn()).await?; //! //! // Buscar con filtro. //! let alices = user::Entity::find() //! .filter(user::Column::Name.eq("Alice")) //! .all(dbconn()) //! .await?; //! //! // Buscar por clave primaria. //! let found = user::Entity::find_by_id(1).one(dbconn()).await?; //! //! // Insertar. //! let model = user::ActiveModel { //! name: ActiveValue::Set("Alice".into()), //! ..Default::default() //! }; //! user::Entity::insert(model).exec(dbconn()).await?; //! //! // Actualizar: campos con `ActiveValue::Set`, clave primaria con `ActiveValue::Unchanged`. //! let patch = user::ActiveModel { //! id: ActiveValue::Unchanged(1), //! name: ActiveValue::Set("Bob".into()), //! ..Default::default() //! }; //! patch.update(dbconn()).await?; //! //! // Eliminar por clave primaria. //! user::Entity::delete_by_id(1).exec(dbconn()).await?; //! //! // Transacción. `Box::pin` es necesario: `TransactionTrait` exige `Pin>`. //! dbconn().transaction::<_, DbErr, _>(|txn| Box::pin(async move { //! user::Entity::insert( //! user::ActiveModel { name: ActiveValue::Set("Carol".into()), ..Default::default() } //! ).exec(txn).await?; //! user::Entity::delete_by_id(2).exec(txn).await?; //! Ok(()) //! })).await?; //! Ok(()) //! } //! ``` //! //! Para migraciones y definición de esquemas usa [`migration`](crate::migration). //! //! # Acceso completo a SeaORM //! //! Este módulo re-exporta el crate `sea_orm` íntegro. Úsalo cuando necesites un tipo o función que //! no esté expuesto directamente en `db::*`: //! //! ```rust,no_run //! use pagetop_seaorm::db::sea_orm; //! //! // Tipos o utilidades no incluidos en db::*: //! let _: sea_orm::DatabaseBackend = sea_orm::DatabaseBackend::Sqlite; //! ``` //! //! # Construcción de consultas en tiempo de ejecución //! //! El módulo [`query`] re-exporta `sea_query` para construir las sentencias SQL que se pasan a //! [`fetch_all`] y [`fetch_one`]. Es el compañero natural de esas funciones dentro del módulo `db`: //! //! ```rust,no_run //! use pagetop_seaorm::db::*; //! use pagetop_seaorm::db::query::*; //! //! async fn example() -> Result<(), DbErr> { //! let stmt = Query::select() //! .column(Asterisk) //! .from(Alias::new("users")) //! .to_owned(); //! let rows = fetch_all(&stmt).await?; //! Ok(()) //! } //! ``` pub use sea_orm::prelude::*; // 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::*`; la inmensa mayoría // de operaciones no necesitan este módulo, `db::*` cubre los casos habituales. // // Por otro lado, este re-export es funcionalmente necesario para que los derive de `sea-orm-macros` // (`DeriveEntityModel`, `DeriveRelation`...) compilen sin declarar `sea-orm` como dependencia // directa en el `Cargo.toml` de la extensión o aplicación. Los derive generan rutas `sea_orm::...` // (vía `quote!`, sin `Span::mixed_site()`), que resuelven contra cualquier item `sea_orm` visible // en el módulo que invoca la macro. Basta con declarar `use pagetop_seaorm::db::*;` en cada entidad // para compilar sin errores. #[doc(hidden)] pub use sea_orm; pub use sea_orm::{ ActiveValue, Condition, DatabaseTransaction, DbBackend, ExecResult, NotSet, Order, QueryOrder, QuerySelect, Set, TransactionError, TransactionTrait, Unchanged, }; /// Re-exporta `sea_query` para construir sentencias SQL en tiempo de ejecución. /// /// Proporciona los constructores de consultas (`Query`, `Expr`, `Alias`, ...) que se pasan a /// [`fetch_all`] y [`fetch_one`]. Aunque [`migration`](crate::migration) expone las mismas /// herramientas en el contexto de la definición de esquemas, `query` las sitúa donde corresponde /// cuando se trata del acceso a la base de datos en tiempo de ejecución. #[doc(inline)] pub use sea_orm::sea_query as query; /// Devuelve una referencia estática al pool de conexiones. /// /// El pool se inicializa una sola vez al arrancar la aplicación; las llamadas posteriores devuelven /// la misma referencia sin coste apreciable. Se puede invocar tantas veces como sea necesario sin /// penalización. /// /// ```rust,no_run /// use pagetop_seaorm::db::*; /// /// let _conn: &DatabaseConnection = dbconn(); /// ``` #[inline] pub fn dbconn() -> &'static DatabaseConnection { 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. /// /// No construye la sentencia (INSERT, UPDATE, DELETE), sino que la recibe como una cadena ya /// formada. Útil para SQL que el sistema de entidades no cubre. El [`ExecResult`] devuelto expone /// [`rows_affected`](ExecResult::rows_affected) y [`last_insert_id`](ExecResult::last_insert_id) /// (fiable en MySQL y SQLite; en PostgreSQL devuelve `0`, usa `RETURNING` con el sistema de /// entidades si necesitas el id insertado). /// /// > **Nota:** no sirve para SELECT porque no devuelve filas. Para leer datos usa [`fetch_all`] o /// > [`fetch_one`]. /// /// > **Advertencia:** nunca interpoles valores externos en la cadena SQL directamente. Para /// > sentencias con parámetros de usuario usa el sistema de entidades. /// /// ```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 conn = dbconn(); let backend = conn.get_database_backend(); conn.execute(sea_orm::Statement::from_string(backend, stmt.into())) .await } /// Ejecuta una consulta para devolver todas las filas resultantes. /// /// Acepta cualquier tipo que implemente [`query::QueryStatementWriter`] (p. ej. /// [`query::SelectStatement`]) y serializa la sentencia para el motor de base de datos usado antes /// de ejecutarla. Cada fila se devuelve como un [`QueryResult`] sin tipar; extrae los valores con /// [`QueryResult::try_get`]. /// /// Usa esta función cuando la consulta SELECT no mapea una entidad concreta (JOINs, agregaciones, /// proyecciones parciales) o cuando necesitas control total sobre el SQL generado. Para sentencias /// que modifican datos (INSERT, UPDATE, DELETE), usa [`execute`]. Para consultas que sí mapean a /// una entidad, es preferible `Entity::find().all(dbconn())`. /// /// Los valores se integran como literales escapados, no como parámetros de base de datos. Para /// datos procedentes del usuario, el sistema de entidades es más robusto. /// /// ```rust,no_run /// use pagetop_seaorm::db::*; /// use pagetop_seaorm::db::query::*; /// /// async fn example() -> Result<(), DbErr> { /// let stmt = Query::select() /// .column(Asterisk) /// .from(Alias::new("users")) /// .to_owned(); /// let rows = fetch_all(&stmt).await?; /// for row in rows { /// let name: String = row.try_get("", "name")?; /// println!("{name}"); /// } /// Ok(()) /// } /// ``` pub async fn fetch_all( stmt: &Q, ) -> Result, DbErr> { let conn = dbconn(); let backend = conn.get_database_backend(); conn.query_all(sea_orm::Statement::from_string( backend, match backend { sea_orm::DatabaseBackend::MySql => stmt.to_string(query::MysqlQueryBuilder), sea_orm::DatabaseBackend::Postgres => stmt.to_string(query::PostgresQueryBuilder), sea_orm::DatabaseBackend::Sqlite => stmt.to_string(query::SqliteQueryBuilder), }, )) .await } /// Ejecuta una consulta y devuelve sólo la primera fila, si existe. /// /// Funciona igual que [`fetch_all`] pero devuelve la primera fila si existe, o `None` si la /// consulta no produce resultados. Está diseñada para sentencias SELECT; para modificar datos sin /// entidad mapeada, usa [`execute`]. /// /// Si la consulta puede devolver varias filas, se recomienda incluir `LIMIT 1` en la sentencia /// para que el motor detenga la búsqueda en cuanto encuentre la primera fila y no recupere /// resultados que se descartarán de todas formas. /// /// Usa esta función cuando la consulta SELECT no mapea una entidad concreta (JOINs, agregaciones, /// proyecciones parciales) o cuando necesitas control total sobre el SQL generado. Para consultas /// que sí mapean a una entidad, es preferible `Entity::find().one(dbconn())`. /// /// Los valores se integran como literales escapados, no como parámetros de base de datos. Para /// datos procedentes del usuario, el sistema de entidades es más robusto. /// /// ```rust,no_run /// use pagetop_seaorm::db::*; /// use pagetop_seaorm::db::query::*; /// /// async fn example() -> Result<(), DbErr> { /// let 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(&stmt).await? { /// let name: String = row.try_get("", "name")?; /// println!("{name}"); /// } /// Ok(()) /// } /// ``` pub async fn fetch_one( stmt: &Q, ) -> Result, DbErr> { let conn = dbconn(); let backend = conn.get_database_backend(); conn.query_one(sea_orm::Statement::from_string( backend, match backend { sea_orm::DatabaseBackend::MySql => stmt.to_string(query::MysqlQueryBuilder), sea_orm::DatabaseBackend::Postgres => stmt.to_string(query::PostgresQueryBuilder), sea_orm::DatabaseBackend::Sqlite => stmt.to_string(query::SqliteQueryBuilder), }, )) .await }