Skip to content

Commit

Permalink
Merge pull request #208 from MuhannadAlrusayni/master
Browse files Browse the repository at this point in the history
Support feteching T, (T, U), (T, U, P)..etc where they impl TryGetableMany
  • Loading branch information
tyt2y3 authored Sep 30, 2021
2 parents d4d2625 + 5379a23 commit 5728ac4
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 2 deletions.
84 changes: 83 additions & 1 deletion src/executor/query.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#[cfg(feature = "mock")]
use crate::debug_print;
use crate::DbErr;
use crate::{DbErr, SelectGetableValue, SelectorRaw, Statement};
use std::fmt;

#[derive(Debug)]
Expand Down Expand Up @@ -302,6 +302,79 @@ try_getable_all!(uuid::Uuid);

pub trait TryGetableMany: Sized {
fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError>;

/// ```
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![vec![
/// # maplit::btreemap! {
/// # "name" => Into::<Value>::into("Chocolate Forest"),
/// # "num_of_cakes" => Into::<Value>::into(1),
/// # },
/// # maplit::btreemap! {
/// # "name" => Into::<Value>::into("New York Cheese"),
/// # "num_of_cakes" => Into::<Value>::into(1),
/// # },
/// # ]])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake, EnumIter, TryGetableMany};
///
/// #[derive(EnumIter)]
/// enum ResultCol {
/// Name,
/// NumOfCakes,
/// }
///
/// // this can be derived using derive_more crate
/// impl std::fmt::Display for ResultCol {
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
/// match self {
/// Self::Name => write!(f, "name"),
/// Self::NumOfCakes => write!(f, "num_of_cakes"),
/// }
/// }
/// }
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let res: Vec<(String, i32)> =
/// <(String, i32)>::find_by_statement::<ResultCol>(Statement::from_sql_and_values(
/// DbBackend::Postgres,
/// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#,
/// vec![],
/// ))
/// .all(&db)
/// .await?;
///
/// assert_eq!(
/// res,
/// vec![
/// ("Chocolate Forest".to_owned(), 1),
/// ("New York Cheese".to_owned(), 1),
/// ]
/// );
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres,
/// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#,
/// vec![]
/// ),]
/// );
/// ```
fn find_by_statement<C>(stmt: Statement) -> SelectorRaw<SelectGetableValue<Self, C>>
where
C: sea_strum::IntoEnumIterator + ToString,
{
SelectorRaw::<SelectGetableValue<Self, C>>::with_columns(stmt)
}
}

impl<T> TryGetableMany for T
Expand All @@ -314,6 +387,15 @@ where
}
}

impl<T> TryGetableMany for (T,)
where
T: TryGetableMany,
{
fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError> {
T::try_get_many(res, pre, cols).map(|r| (r,))
}
}

impl<A, B> TryGetableMany for (A, B)
where
A: TryGetable,
Expand Down
124 changes: 123 additions & 1 deletion src/executor/select.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
error::*, DatabaseConnection, EntityTrait, FromQueryResult, IdenStatic, Iterable, JsonValue,
ModelTrait, Paginator, PrimaryKeyToColumn, QueryResult, Select, SelectA, SelectB, SelectTwo,
SelectTwoMany, Statement,
SelectTwoMany, Statement, TryGetableMany,
};
use sea_query::SelectStatement;
use std::marker::PhantomData;
Expand Down Expand Up @@ -30,6 +30,16 @@ pub trait SelectorTrait {
fn from_raw_query_result(res: QueryResult) -> Result<Self::Item, DbErr>;
}

#[derive(Debug)]
pub struct SelectGetableValue<T, C>
where
T: TryGetableMany,
C: sea_strum::IntoEnumIterator + ToString,
{
columns: PhantomData<C>,
model: PhantomData<T>,
}

#[derive(Debug)]
pub struct SelectModel<M>
where
Expand All @@ -47,6 +57,19 @@ where
model: PhantomData<(M, N)>,
}

impl<T, C> SelectorTrait for SelectGetableValue<T, C>
where
T: TryGetableMany,
C: sea_strum::IntoEnumIterator + ToString,
{
type Item = T;

fn from_raw_query_result(res: QueryResult) -> Result<Self::Item, DbErr> {
let cols: Vec<String> = C::iter().map(|col| col.to_string()).collect();
T::try_get_many(&res, "", &cols).map_err(Into::into)
}
}

impl<M> SelectorTrait for SelectModel<M>
where
M: FromQueryResult + Sized,
Expand Down Expand Up @@ -103,6 +126,73 @@ where
}
}

/// ```
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![vec![
/// # maplit::btreemap! {
/// # "name" => Into::<Value>::into("Chocolate Forest"),
/// # "num_of_cakes" => Into::<Value>::into(1),
/// # },
/// # maplit::btreemap! {
/// # "name" => Into::<Value>::into("New York Cheese"),
/// # "num_of_cakes" => Into::<Value>::into(1),
/// # },
/// # ]])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake, EnumIter, TryGetableMany};
///
/// #[derive(EnumIter)]
/// enum ResultCol {
/// Name,
/// }
///
/// // this can be derived using derive_more crate
/// impl std::fmt::Display for ResultCol {
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
/// match self {
/// Self::Name => write!(f, "name"),
/// }
/// }
/// }
///
/// # let _: Result<(), DbErr> = smol::block_on(async {
/// #
/// let res: Vec<String> = cake::Entity::find()
/// .select_only()
/// .column(cake::Column::Name)
/// .into_values::<_, ResultCol>()
/// .all(&db)
/// .await?;
///
/// assert_eq!(
/// res,
/// vec!["Chocolate Forest".to_owned(), "New York Cheese".to_owned(),]
/// );
/// #
/// # Ok(())
/// # });
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres,
/// r#"SELECT "cake"."name" FROM "cake""#,
/// vec![]
/// ),]
/// );
/// ```
pub fn into_values<T, C>(self) -> Selector<SelectGetableValue<T, C>>
where
T: TryGetableMany,
C: sea_strum::IntoEnumIterator + ToString,
{
Selector::<SelectGetableValue<T, C>>::with_columns(self.query)
}

pub async fn one(self, db: &DatabaseConnection) -> Result<Option<E::Model>, DbErr> {
self.into_model().one(db).await
}
Expand Down Expand Up @@ -228,6 +318,22 @@ impl<S> Selector<S>
where
S: SelectorTrait,
{
/// Create `Selector` from Statment and columns. Executing this `Selector`
/// will return a type `T` which implement `TryGetableMany`.
pub fn with_columns<T, C>(query: SelectStatement) -> Selector<SelectGetableValue<T, C>>
where
T: TryGetableMany,
C: sea_strum::IntoEnumIterator + ToString,
{
Selector {
query,
selector: SelectGetableValue {
columns: PhantomData,
model: PhantomData,
},
}
}

pub async fn one(mut self, db: &DatabaseConnection) -> Result<Option<S::Item>, DbErr> {
let builder = db.get_database_backend();
self.query.limit(1);
Expand Down Expand Up @@ -275,6 +381,22 @@ where
}
}

/// Create `SelectorRaw` from Statment and columns. Executing this `SelectorRaw` will
/// return a type `T` which implement `TryGetableMany`.
pub fn with_columns<T, C>(stmt: Statement) -> SelectorRaw<SelectGetableValue<T, C>>
where
T: TryGetableMany,
C: sea_strum::IntoEnumIterator + ToString,
{
SelectorRaw {
stmt,
selector: SelectGetableValue {
columns: PhantomData,
model: PhantomData,
},
}
}

/// ```
/// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
Expand Down

0 comments on commit 5728ac4

Please sign in to comment.