agrum 0.4.0

This is an OMM database layer.
Documentation
use std::{any::Any, marker::PhantomData};

use agrum::{
    Projection, QueryBook, ReadQueryBook, SqlEntity, SqlQuery, Structure, Structured, Transaction,
    WhereCondition,
};
use futures_util::stream::StreamExt;
use uuid::Uuid;

mod pool;
use pool::get_pool;

pub struct CompanyShort {
    pub company_id: Uuid,
    pub name: String,
    pub contacts_nb: i64,
}

impl Structured for CompanyShort {
    fn get_structure() -> Structure {
        Structure::new(&[
            ("company_id", "uuid"),
            ("name", "text"),
            ("contacts_nb", "integer"),
        ])
    }
}

impl SqlEntity for CompanyShort {
    fn get_projection() -> Projection<CompanyShort> {
        Projection::<CompanyShort>::new("company")
            .set_definition("contacts_nb", "count(contact.company_id)")
    }

    fn hydrate(row: &tokio_postgres::Row) -> Result<Self, agrum::HydrationError> {
        Ok(Self {
            company_id: row.get("company_id"),
            name: row.get::<_, String>("name"),
            contacts_nb: row.get("contacts_nb"),
        })
    }
}

pub struct ContactQueryBook<T: SqlEntity> {
    _phantom: PhantomData<T>,
}

impl<T: SqlEntity> QueryBook<T> for ContactQueryBook<T> {
    fn get_sql_source(&self) -> &'static str {
        "pommr.contact"
    }
}

impl<T: SqlEntity> ReadQueryBook<T> for ContactQueryBook<T> {}

impl<T: SqlEntity> Default for ContactQueryBook<T> {
    fn default() -> Self {
        Self {
            _phantom: PhantomData,
        }
    }
}

struct CompanyWithContactsCountQueryBook<T: SqlEntity> {
    _phantom: PhantomData<T>,
}

impl<T: SqlEntity> QueryBook<T> for CompanyWithContactsCountQueryBook<T> {
    fn get_sql_source(&self) -> &'static str {
        "pommr.company"
    }
}

impl<T: SqlEntity> ReadQueryBook<T> for CompanyWithContactsCountQueryBook<T> {}

impl<T: SqlEntity> CompanyWithContactsCountQueryBook<T> {
    pub fn new() -> Self {
        Self {
            _phantom: PhantomData,
        }
    }

    fn get_sql_definition(&self) -> &'static str {
        r#"select {:projection:}
  from {:source:} as company
    left join {:contact_source:} as contact
      on company.company_id = contact.company_id
  where {:condition:}
  group by company.company_id"#
    }

    fn select<'a>(&self, conditions: WhereCondition<'a>) -> SqlQuery<'a, T> {
        let mut query = SqlQuery::new(self.get_sql_definition());
        let (conditions, parameters) = conditions.expand();
        query
            .set_parameters(parameters)
            .set_variable("projection", &T::get_projection().to_string())
            .set_variable("source", self.get_sql_source())
            .set_variable(
                "contact_source",
                ContactQueryBook::<T>::default().get_sql_source(),
            )
            .set_variable("condition", &conditions);
        query
    }

    pub fn select_by_id<'a>(&self, id: &'a Uuid) -> SqlQuery<'a, T> {
        self.select(WhereCondition::new("company.company_id = $?", vec![id]))
    }
}

#[tokio::test]
#[ignore = "skipping database tests"]
async fn test_select_by_id() {
    let pool = get_pool().await;
    let mut connection = pool.get().await.unwrap();
    let transaction = Transaction::start(connection.transaction().await.unwrap()).await;

    let company_id = Uuid::parse_str("a7b5f2c8-8816-4c40-86bf-64e066a8db7a").unwrap();
    let query = CompanyWithContactsCountQueryBook::<CompanyShort>::new().select_by_id(&company_id);
    assert!(query.get_parameters().len() == 1);
    assert_eq!(
        (query.get_parameters()[0] as &dyn Any)
            .downcast_ref::<Uuid>()
            .unwrap(),
        &company_id
    );
    assert_eq!(
        query.to_string(),
        r#"select company.company_id as company_id, company.name as name, count(contact.company_id) as contacts_nb
  from pommr.company as company
    left join pommr.contact as contact
      on company.company_id = contact.company_id
  where company.company_id = $1
  group by company.company_id"#
    );

    let company_short = transaction
        .query(query)
        .await
        .unwrap()
        .next()
        .await
        .unwrap()
        .unwrap();
    assert_eq!(company_short.company_id, company_id);
    assert_eq!(company_short.name, "first");
    assert_eq!(company_short.contacts_nb, 1);
    transaction.rollback().await.unwrap();
}