use crate::{ error::*, ConnectionTrait, EntityTrait, FromQueryResult, IdenStatic, Iterable, ModelTrait, PrimaryKeyToColumn, QueryResult, Select, SelectA, SelectB, SelectTwo, SelectTwoMany, Statement, StreamTrait, TryGetableMany, }; use futures::{Stream, TryStreamExt}; use sea_query::SelectStatement; use std::marker::PhantomData; use std::pin::Pin; #[cfg(feature = "with-json")] use crate::JsonValue; /// Defines a type to do `SELECT` operations through a [SelectStatement] on a Model #[derive(Clone, Debug)] pub struct Selector where S: SelectorTrait, { pub(crate) query: SelectStatement, selector: S, } /// Performs a raw `SELECT` operation on a model #[derive(Clone, Debug)] pub struct SelectorRaw where S: SelectorTrait, { pub(crate) stmt: Statement, #[allow(dead_code)] selector: S, } /// A Trait for any type that can perform SELECT queries pub trait SelectorTrait { #[allow(missing_docs)] type Item: Sized; /// The method to perform a query on a Model fn from_raw_query_result(res: QueryResult) -> Result; } /// Perform an operation on an entity that can yield a Value #[derive(Debug)] pub struct SelectGetableValue where T: TryGetableMany, C: strum::IntoEnumIterator + sea_query::Iden, { columns: PhantomData, model: PhantomData, } /// Defines a type to get a Model #[derive(Debug)] pub struct SelectModel where M: FromQueryResult, { model: PhantomData, } /// Defines a type to get two Modelss #[derive(Clone, Debug)] pub struct SelectTwoModel where M: FromQueryResult, N: FromQueryResult, { model: PhantomData<(M, N)>, } impl SelectorTrait for SelectGetableValue where T: TryGetableMany, C: strum::IntoEnumIterator + sea_query::Iden, { type Item = T; fn from_raw_query_result(res: QueryResult) -> Result { let cols: Vec = C::iter().map(|col| col.to_string()).collect(); T::try_get_many(&res, "", &cols).map_err(Into::into) } } impl SelectorTrait for SelectModel where M: FromQueryResult + Sized, { type Item = M; fn from_raw_query_result(res: QueryResult) -> Result { M::from_query_result(&res, "") } } impl SelectorTrait for SelectTwoModel where M: FromQueryResult + Sized, N: FromQueryResult + Sized, { type Item = (M, Option); fn from_raw_query_result(res: QueryResult) -> Result { Ok(( M::from_query_result(&res, SelectA.as_str())?, N::from_query_result_optional(&res, SelectB.as_str())?, )) } } impl Select where E: EntityTrait, { /// Perform a Select operation on a Model using a [Statement] #[allow(clippy::wrong_self_convention)] pub fn from_raw_sql(self, stmt: Statement) -> SelectorRaw> { SelectorRaw { stmt, selector: SelectModel { model: PhantomData }, } } /// Return a [Selector] from `Self` that wraps a [SelectModel] pub fn into_model(self) -> Selector> where M: FromQueryResult, { Selector { query: self.query, selector: SelectModel { model: PhantomData }, } } /// Get a selectable Model as a [JsonValue] for SQL JSON operations #[cfg(feature = "with-json")] pub fn into_json(self) -> Selector> { Selector { query: self.query, selector: SelectModel { model: PhantomData }, } } /// ``` /// # use sea_orm::{error::*, tests_cfg::*, *}; /// # /// # #[smol_potat::main] /// # #[cfg(all(feature = "mock", feature = "macros"))] /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![vec![ /// # maplit::btreemap! { /// # "cake_name" => Into::::into("Chocolate Forest"), /// # }, /// # maplit::btreemap! { /// # "cake_name" => Into::::into("New York Cheese"), /// # }, /// # ]]) /// # .into_connection(); /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DeriveColumn, EnumIter}; /// /// #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] /// enum QueryAs { /// CakeName, /// } /// /// let res: Vec = cake::Entity::find() /// .select_only() /// .column_as(cake::Column::Name, QueryAs::CakeName) /// .into_values::<_, QueryAs>() /// .all(&db) /// .await?; /// /// assert_eq!( /// res, /// vec!["Chocolate Forest".to_owned(), "New York Cheese".to_owned()] /// ); /// /// assert_eq!( /// db.into_transaction_log(), /// vec![Transaction::from_sql_and_values( /// DbBackend::Postgres, /// r#"SELECT "cake"."name" AS "cake_name" FROM "cake""#, /// vec![] /// )] /// ); /// # /// # Ok(()) /// # } /// ``` /// /// ``` /// # use sea_orm::{error::*, tests_cfg::*, *}; /// # /// # #[smol_potat::main] /// # #[cfg(all(feature = "mock", feature = "macros"))] /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![vec![ /// # maplit::btreemap! { /// # "cake_name" => Into::::into("Chocolate Forest"), /// # "num_of_cakes" => Into::::into(2i64), /// # }, /// # ]]) /// # .into_connection(); /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DeriveColumn, EnumIter}; /// /// #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] /// enum QueryAs { /// CakeName, /// NumOfCakes, /// } /// /// let res: Vec<(String, i64)> = cake::Entity::find() /// .select_only() /// .column_as(cake::Column::Name, QueryAs::CakeName) /// .column_as(cake::Column::Id.count(), QueryAs::NumOfCakes) /// .group_by(cake::Column::Name) /// .into_values::<_, QueryAs>() /// .all(&db) /// .await?; /// /// assert_eq!(res, vec![("Chocolate Forest".to_owned(), 2i64)]); /// /// assert_eq!( /// db.into_transaction_log(), /// vec![Transaction::from_sql_and_values( /// DbBackend::Postgres, /// vec![ /// r#"SELECT "cake"."name" AS "cake_name", COUNT("cake"."id") AS "num_of_cakes""#, /// r#"FROM "cake" GROUP BY "cake"."name""#, /// ] /// .join(" ") /// .as_str(), /// vec![] /// )] /// ); /// # /// # Ok(()) /// # } /// ``` pub fn into_values(self) -> Selector> where T: TryGetableMany, C: strum::IntoEnumIterator + sea_query::Iden, { Selector::>::with_columns(self.query) } /// Get one Model from the SELECT query pub async fn one<'a, C>(self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { self.into_model().one(db).await } /// Get all Models from the SELECT query pub async fn all<'a, C>(self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { self.into_model().all(db).await } /// Stream the results of a SELECT operation on a Model pub async fn stream<'a: 'b, 'b, C>( self, db: &'a C, ) -> Result> + 'b + Send, DbErr> where C: ConnectionTrait + StreamTrait<'a> + Send, { self.into_model().stream(db).await } } impl SelectTwo where E: EntityTrait, F: EntityTrait, { /// Perform a conversion into a [SelectTwoModel] pub fn into_model(self) -> Selector> where M: FromQueryResult, N: FromQueryResult, { Selector { query: self.query, selector: SelectTwoModel { model: PhantomData }, } } /// Convert the Models into JsonValue #[cfg(feature = "with-json")] pub fn into_json(self) -> Selector> { Selector { query: self.query, selector: SelectTwoModel { model: PhantomData }, } } /// Get one Model from the Select query pub async fn one<'a, C>(self, db: &C) -> Result)>, DbErr> where C: ConnectionTrait, { self.into_model().one(db).await } /// Get all Models from the Select query pub async fn all<'a, C>(self, db: &C) -> Result)>, DbErr> where C: ConnectionTrait, { self.into_model().all(db).await } /// Stream the results of a Select operation on a Model pub async fn stream<'a: 'b, 'b, C>( self, db: &'a C, ) -> Result), DbErr>> + 'b, DbErr> where C: ConnectionTrait + StreamTrait<'a> + Send, { self.into_model().stream(db).await } } impl SelectTwoMany where E: EntityTrait, F: EntityTrait, { /// Performs a conversion to [Selector] fn into_model(self) -> Selector> where M: FromQueryResult, N: FromQueryResult, { Selector { query: self.query, selector: SelectTwoModel { model: PhantomData }, } } /// Convert the results to JSON #[cfg(feature = "with-json")] pub fn into_json(self) -> Selector> { Selector { query: self.query, selector: SelectTwoModel { model: PhantomData }, } } /// Stream the result of the operation pub async fn stream<'a: 'b, 'b, C>( self, db: &'a C, ) -> Result), DbErr>> + 'b + Send, DbErr> where C: ConnectionTrait + StreamTrait<'a> + Send, { self.into_model().stream(db).await } /// Get all Models from the select operation /// /// > `SelectTwoMany::one()` method has been dropped (#486) /// > /// > You can get `(Entity, Vec)` by first querying a single model from Entity, /// > then use [`ModelTrait::find_related`] on the model. /// > /// > See https://www.sea-ql.org/SeaORM/docs/basic-crud/select#lazy-loading for details. pub async fn all<'a, C>(self, db: &C) -> Result)>, DbErr> where C: ConnectionTrait, { let rows = self.into_model().all(db).await?; Ok(consolidate_query_result::(rows)) } // pub fn paginate() // we could not implement paginate easily, if the number of children for a // parent is larger than one page, then we will end up splitting it in two pages // so the correct way is actually perform query in two stages // paginate the parent model and then populate the children // pub fn count() // we should only count the number of items of the parent model } impl Selector where S: SelectorTrait, { /// Create `Selector` from Statment and columns. Executing this `Selector` /// will return a type `T` which implement `TryGetableMany`. pub fn with_columns(query: SelectStatement) -> Selector> where T: TryGetableMany, C: strum::IntoEnumIterator + sea_query::Iden, { Selector { query, selector: SelectGetableValue { columns: PhantomData, model: PhantomData, }, } } fn into_selector_raw(self, db: &C) -> SelectorRaw where C: ConnectionTrait, { let builder = db.get_database_backend(); let stmt = builder.build(&self.query); SelectorRaw { stmt, selector: self.selector, } } /// Get an item from the Select query pub async fn one<'a, C>(mut self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { self.query.limit(1); self.into_selector_raw(db).one(db).await } /// Get all items from the Select query pub async fn all<'a, C>(self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { self.into_selector_raw(db).all(db).await } /// Stream the results of the Select operation pub async fn stream<'a: 'b, 'b, C>( self, db: &'a C, ) -> Result> + 'b + Send>>, DbErr> where C: ConnectionTrait + StreamTrait<'a> + Send, S: 'b, S::Item: Send, { self.into_selector_raw(db).stream(db).await } } impl SelectorRaw where S: SelectorTrait, { /// Select a custom Model from a raw SQL [Statement]. pub fn from_statement(stmt: Statement) -> SelectorRaw> where M: FromQueryResult, { SelectorRaw { stmt, selector: SelectModel { model: PhantomData }, } } /// Create `SelectorRaw` from Statement and columns. Executing this `SelectorRaw` will /// return a type `T` which implement `TryGetableMany`. pub fn with_columns(stmt: Statement) -> SelectorRaw> where T: TryGetableMany, C: strum::IntoEnumIterator + sea_query::Iden, { SelectorRaw { stmt, selector: SelectGetableValue { columns: PhantomData, model: PhantomData, }, } } /// ``` /// # use sea_orm::{error::*, tests_cfg::*, *}; /// # /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![vec![ /// # maplit::btreemap! { /// # "name" => Into::::into("Chocolate Forest"), /// # "num_of_cakes" => Into::::into(1), /// # }, /// # maplit::btreemap! { /// # "name" => Into::::into("New York Cheese"), /// # "num_of_cakes" => Into::::into(1), /// # }, /// # ]]) /// # .into_connection(); /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake, FromQueryResult}; /// /// #[derive(Debug, PartialEq, FromQueryResult)] /// struct SelectResult { /// name: String, /// num_of_cakes: i32, /// } /// /// let res: Vec = cake::Entity::find() /// .from_raw_sql(Statement::from_sql_and_values( /// DbBackend::Postgres, /// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#, /// vec![], /// )) /// .into_model::() /// .all(&db) /// .await?; /// /// assert_eq!( /// res, /// vec![ /// SelectResult { /// name: "Chocolate Forest".to_owned(), /// num_of_cakes: 1, /// }, /// SelectResult { /// name: "New York Cheese".to_owned(), /// num_of_cakes: 1, /// }, /// ] /// ); /// /// 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![] /// ),] /// ); /// # /// # Ok(()) /// # } /// ``` pub fn into_model(self) -> SelectorRaw> where M: FromQueryResult, { SelectorRaw { stmt: self.stmt, selector: SelectModel { model: PhantomData }, } } /// ``` /// # use sea_orm::{error::*, tests_cfg::*, *}; /// # /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![vec![ /// # maplit::btreemap! { /// # "name" => Into::::into("Chocolate Forest"), /// # "num_of_cakes" => Into::::into(1), /// # }, /// # maplit::btreemap! { /// # "name" => Into::::into("New York Cheese"), /// # "num_of_cakes" => Into::::into(1), /// # }, /// # ]]) /// # .into_connection(); /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// /// let res: Vec = cake::Entity::find().from_raw_sql( /// Statement::from_sql_and_values( /// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, vec![] /// ) /// ) /// .into_json() /// .all(&db) /// .await?; /// /// assert_eq!( /// res, /// vec![ /// serde_json::json!({ /// "name": "Chocolate Forest", /// "num_of_cakes": 1, /// }), /// serde_json::json!({ /// "name": "New York Cheese", /// "num_of_cakes": 1, /// }), /// ] /// ); /// /// assert_eq!( /// db.into_transaction_log(), /// vec![ /// Transaction::from_sql_and_values( /// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, vec![] /// ), /// ]); /// # /// # Ok(()) /// # } /// ``` #[cfg(feature = "with-json")] pub fn into_json(self) -> SelectorRaw> { SelectorRaw { stmt: self.stmt, selector: SelectModel { model: PhantomData }, } } /// Get an item from the Select query /// ``` /// # use sea_orm::{error::*, tests_cfg::*, *}; /// # /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![ /// # vec![cake::Model { /// # id: 1, /// # name: "Cake".to_owned(), /// # }], /// # ]) /// # .into_connection(); /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// /// let _: Option = cake::Entity::find() /// .from_raw_sql(Statement::from_sql_and_values( /// DbBackend::Postgres, /// r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = $1"#, /// vec![1.into()], /// )) /// .one(&db) /// .await?; /// /// assert_eq!( /// db.into_transaction_log(), /// vec![Transaction::from_sql_and_values( /// DbBackend::Postgres, /// r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = $1"#, /// vec![1.into()] /// ),] /// ); /// # /// # Ok(()) /// # } /// ``` pub async fn one<'a, C>(self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { let row = db.query_one(self.stmt).await?; match row { Some(row) => Ok(Some(S::from_raw_query_result(row)?)), None => Ok(None), } } /// Get all items from the Select query /// ``` /// # use sea_orm::{error::*, tests_cfg::*, *}; /// # /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![ /// # vec![cake::Model { /// # id: 1, /// # name: "Cake".to_owned(), /// # }], /// # ]) /// # .into_connection(); /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// /// let _: Vec = cake::Entity::find() /// .from_raw_sql(Statement::from_sql_and_values( /// DbBackend::Postgres, /// r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, /// vec![], /// )) /// .all(&db) /// .await?; /// /// assert_eq!( /// db.into_transaction_log(), /// vec![Transaction::from_sql_and_values( /// DbBackend::Postgres, /// r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, /// vec![] /// ),] /// ); /// # /// # Ok(()) /// # } /// ``` pub async fn all<'a, C>(self, db: &C) -> Result, DbErr> where C: ConnectionTrait, { let rows = db.query_all(self.stmt).await?; let mut models = Vec::new(); for row in rows.into_iter() { models.push(S::from_raw_query_result(row)?); } Ok(models) } /// Stream the results of the Select operation pub async fn stream<'a: 'b, 'b, C>( self, db: &'a C, ) -> Result> + 'b + Send>>, DbErr> where C: ConnectionTrait + StreamTrait<'a> + Send, S: 'b, S::Item: Send, { let stream = db.stream(self.stmt).await?; Ok(Box::pin(stream.and_then(|row| { futures::future::ready(S::from_raw_query_result(row)) }))) } } fn consolidate_query_result( rows: Vec<(L::Model, Option)>, ) -> Vec<(L::Model, Vec)> where L: EntityTrait, R: EntityTrait, { let mut acc: Vec<(L::Model, Vec)> = Vec::new(); for (l, r) in rows { if let Some((last_l, last_r)) = acc.last_mut() { let mut same_l = true; for pk_col in ::iter() { let col = pk_col.into_column(); let val = l.get(col); let last_val = last_l.get(col); if !val.eq(&last_val) { same_l = false; break; } } if same_l { if let Some(r) = r { last_r.push(r); continue; } } } if r.is_some() { acc.push((l, vec![r.unwrap()])); } else { acc.push((l, vec![])); } } acc }