rustorm-core 0.1.0

Core traits, types and utilities for RustORM
Documentation
use crate::error::OrmResult;
use crate::query::QueryBuilder;
use async_trait::async_trait;
use sqlx::{PgPool, Row};

/// Главный трейт, который реализует каждая модель.
/// Генерируется `#[derive(Model)]`.
#[async_trait]
pub trait Model: Sized + Send + Sync + Unpin + 'static {
    /// Имя таблицы в БД.
    fn table_name() -> &'static str;

    /// Имя первичного ключа.
    fn primary_key() -> &'static str {
        "id"
    }

    /// Список всех колонок таблицы (в порядке SELECT *).
    fn columns() -> &'static [&'static str];

    /// Создаёт QueryBuilder для данной модели.
    fn query() -> QueryBuilder<Self> {
        QueryBuilder::new(Self::table_name(), Self::primary_key())
    }

    /// `SELECT * FROM table WHERE pk = id`
    async fn find(id: i64, pool: &PgPool) -> OrmResult<Option<Self>>
    where
        Self: for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>,
    {
        let sql = format!(
            "SELECT * FROM \"{}\" WHERE \"{}\" = $1",
            Self::table_name(),
            Self::primary_key(),
        );
        let row = sqlx::query_as::<_, Self>(&sql)
            .bind(id)
            .fetch_optional(pool)
            .await
            .map_err(crate::error::OrmError::from_sqlx)?;
        Ok(row)
    }

    /// Как `find`, но возвращает `OrmError::NotFound` если запись не найдена.
    async fn find_or_fail(id: i64, pool: &PgPool) -> OrmResult<Self>
    where
        Self: for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>,
    {
        Self::find(id, pool)
            .await?
            .ok_or(crate::error::OrmError::NotFound)
    }

    /// Находит несколько записей по списку id.
    async fn find_many(ids: &[i64], pool: &PgPool) -> OrmResult<Vec<Self>>
    where
        Self: for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>,
    {
        if ids.is_empty() {
            return Ok(vec![]);
        }
        let placeholders: Vec<String> = (1..=ids.len()).map(|i| format!("${}", i)).collect();
        let sql = format!(
            "SELECT * FROM \"{}\" WHERE \"{}\" IN ({})",
            Self::table_name(),
            Self::primary_key(),
            placeholders.join(", "),
        );
        let mut q = sqlx::query_as::<_, Self>(&sql);
        for id in ids {
            q = q.bind(id);
        }
        let rows = q
            .fetch_all(pool)
            .await
            .map_err(crate::error::OrmError::from_sqlx)?;
        Ok(rows)
    }

    /// Все записи таблицы.
    async fn all(pool: &PgPool) -> OrmResult<Vec<Self>>
    where
        Self: for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>,
    {
        let sql = format!("SELECT * FROM \"{}\"", Self::table_name());
        let rows = sqlx::query_as::<_, Self>(&sql)
            .fetch_all(pool)
            .await
            .map_err(crate::error::OrmError::from_sqlx)?;
        Ok(rows)
    }

    /// Первая запись (по PK ASC).
    async fn first(pool: &PgPool) -> OrmResult<Option<Self>>
    where
        Self: for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>,
    {
        let sql = format!(
            "SELECT * FROM \"{}\" ORDER BY \"{}\" ASC LIMIT 1",
            Self::table_name(),
            Self::primary_key(),
        );
        let row = sqlx::query_as::<_, Self>(&sql)
            .fetch_optional(pool)
            .await
            .map_err(crate::error::OrmError::from_sqlx)?;
        Ok(row)
    }

    /// Последняя запись (по PK DESC).
    async fn last(pool: &PgPool) -> OrmResult<Option<Self>>
    where
        Self: for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>,
    {
        let sql = format!(
            "SELECT * FROM \"{}\" ORDER BY \"{}\" DESC LIMIT 1",
            Self::table_name(),
            Self::primary_key(),
        );
        let row = sqlx::query_as::<_, Self>(&sql)
            .fetch_optional(pool)
            .await
            .map_err(crate::error::OrmError::from_sqlx)?;
        Ok(row)
    }

    /// Количество записей.
    async fn count(pool: &PgPool) -> OrmResult<i64> {
        let sql = format!("SELECT COUNT(*) FROM \"{}\"", Self::table_name());
        let row: (i64,) = sqlx::query_as(&sql)
            .fetch_one(pool)
            .await
            .map_err(crate::error::OrmError::from_sqlx)?;
        Ok(row.0)
    }

    /// Есть ли хоть одна запись.
    async fn any(pool: &PgPool) -> OrmResult<bool> {
        Ok(Self::count(pool).await? > 0)
    }

    /// Существует ли запись с данным id.
    async fn exists(id: i64, pool: &PgPool) -> OrmResult<bool> {
        let sql = format!(
            "SELECT EXISTS(SELECT 1 FROM \"{}\" WHERE \"{}\" = $1)",
            Self::table_name(),
            Self::primary_key(),
        );
        let row: (bool,) = sqlx::query_as(&sql)
            .bind(id)
            .fetch_one(pool)
            .await
            .map_err(crate::error::OrmError::from_sqlx)?;
        Ok(row.0)
    }
}