Merge pull request #1615 from SeaQL/pulls/1597

Pulls/1597
This commit is contained in:
Chris Tsang 2023-04-26 18:33:54 +08:00 committed by GitHub
commit 28a5de09d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 625 additions and 11 deletions

View File

@ -9,6 +9,7 @@ mod from_query_result;
mod into_active_model;
mod migration;
mod model;
mod partial_model;
mod primary_key;
mod relation;
mod try_getable_from_json;
@ -24,6 +25,7 @@ pub use from_query_result::*;
pub use into_active_model::*;
pub use migration::*;
pub use model::*;
pub use partial_model::*;
pub use primary_key::*;
pub use relation::*;
pub use try_getable_from_json::*;

View File

@ -0,0 +1,269 @@
use heck::ToUpperCamelCase;
use proc_macro2::Span;
use proc_macro2::TokenStream;
use quote::format_ident;
use quote::quote;
use quote::quote_spanned;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::Expr;
use syn::Meta;
use self::util::GetAsKVMeta;
#[derive(Debug)]
enum Error {
InputNotStruct,
EntityNotSpecific,
NotSupportGeneric(Span),
BothFromColAndFromExpr(Span),
Syn(syn::Error),
}
#[derive(Debug, PartialEq, Eq)]
enum ColumnAs {
/// column in the model
Col(syn::Ident),
/// alias from a column in model
ColAlias { col: syn::Ident, field: String },
/// from a expr
Expr { expr: syn::Expr, field_name: String },
}
struct DerivePartialModel {
entity_ident: Option<syn::Ident>,
ident: syn::Ident,
fields: Vec<ColumnAs>,
}
impl DerivePartialModel {
fn new(input: syn::DeriveInput) -> Result<Self, Error> {
if !input.generics.params.is_empty() {
return Err(Error::NotSupportGeneric(input.generics.params.span()));
}
let syn::Data::Struct(syn::DataStruct{fields:syn::Fields::Named(syn::FieldsNamed{named:fields,..}),..},..) = input.data else{
return Err(Error::InputNotStruct);
};
let mut entity_ident = None;
for attr in input.attrs.iter() {
if let Some(ident) = attr.path.get_ident() {
if ident != "sea_orm" {
continue;
}
} else {
continue;
}
if let Ok(list) = attr.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated) {
for meta in list {
entity_ident = meta
.get_as_kv("entity")
.map(|s| syn::parse_str::<syn::Ident>(&s).map_err(Error::Syn))
.transpose()?;
}
}
}
let mut column_as_list = Vec::with_capacity(fields.len());
for field in fields {
let field_span = field.span();
let mut from_col = None;
let mut from_expr = None;
for attr in field.attrs.iter() {
if !attr.path.is_ident("sea_orm") {
continue;
}
if let Ok(list) = attr.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)
{
for meta in list.iter() {
from_col = meta
.get_as_kv("from_col")
.map(|s| format_ident!("{}", s.to_upper_camel_case()));
from_expr = meta
.get_as_kv("from_expr")
.map(|s| syn::parse_str::<Expr>(&s).map_err(Error::Syn))
.transpose()?;
}
}
}
let field_name = field.ident.unwrap();
let col_as = match (from_col, from_expr) {
(None, None) => {
if entity_ident.is_none() {
return Err(Error::EntityNotSpecific);
}
ColumnAs::Col(format_ident!(
"{}",
field_name.to_string().to_upper_camel_case()
))
}
(None, Some(expr)) => ColumnAs::Expr {
expr,
field_name: field_name.to_string(),
},
(Some(col), None) => {
if entity_ident.is_none() {
return Err(Error::EntityNotSpecific);
}
let field = field_name.to_string();
ColumnAs::ColAlias { col, field }
}
(Some(_), Some(_)) => return Err(Error::BothFromColAndFromExpr(field_span)),
};
column_as_list.push(col_as);
}
Ok(Self {
entity_ident,
ident: input.ident,
fields: column_as_list,
})
}
fn expand(&self) -> syn::Result<TokenStream> {
Ok(self.impl_partial_model_trait())
}
fn impl_partial_model_trait(&self) -> TokenStream {
let select_ident = format_ident!("select");
let DerivePartialModel {
entity_ident,
ident,
fields,
} = self;
let select_col_code_gen = fields.iter().map(|col_as| match col_as {
ColumnAs::Col(ident) => {
let entity = entity_ident.as_ref().unwrap();
let col_value = quote!( <#entity as sea_orm::EntityTrait>::Column:: #ident);
quote!(let #select_ident = sea_orm::SelectColumns::select_column(#select_ident, #col_value);)
},
ColumnAs::ColAlias { col, field } => {
let entity = entity_ident.as_ref().unwrap();
let col_value = quote!( <#entity as sea_orm::EntityTrait>::Column:: #col);
quote!(let #select_ident = sea_orm::SelectColumns::select_column_as(#select_ident, #col_value, #field);)
},
ColumnAs::Expr { expr, field_name } => {
quote!(let #select_ident = sea_orm::SelectColumns::select_column_as(#select_ident, #expr, #field_name);)
},
});
quote! {
#[automatically_derived]
impl sea_orm::PartialModelTrait for #ident{
fn select_cols<S: sea_orm::SelectColumns>(#select_ident: S) -> S{
#(#select_col_code_gen)*
#select_ident
}
}
}
}
}
pub fn expand_derive_partial_model(input: syn::DeriveInput) -> syn::Result<TokenStream> {
let ident_span = input.ident.span();
match DerivePartialModel::new(input) {
Ok(partial_model) => partial_model.expand(),
Err(Error::NotSupportGeneric(span)) => Ok(quote_spanned! {
span => compile_error!("you can only derive `DerivePartialModel` on named struct");
}),
Err(Error::BothFromColAndFromExpr(span)) => Ok(quote_spanned! {
span => compile_error!("you can only use one of `from_col` or `from_expr`");
}),
Err(Error::EntityNotSpecific) => Ok(quote_spanned! {
ident_span => compile_error!("you need specific which entity you are using")
}),
Err(Error::InputNotStruct) => Ok(quote_spanned! {
ident_span => compile_error!("you can only derive `DerivePartialModel` on named struct");
}),
Err(Error::Syn(err)) => Err(err),
}
}
mod util {
use syn::{Lit, Meta, MetaNameValue};
pub(super) trait GetAsKVMeta {
fn get_as_kv(&self, k: &str) -> Option<String>;
}
impl GetAsKVMeta for Meta {
fn get_as_kv(&self, k: &str) -> Option<String> {
let Meta::NameValue(MetaNameValue{path, lit:Lit::Str(lit), ..}) = self else {
return None;
};
if path.is_ident(k) {
Some(lit.value())
} else {
None
}
}
}
}
#[cfg(test)]
mod test {
use quote::format_ident;
use syn::DeriveInput;
use crate::derives::partial_model::ColumnAs;
use super::DerivePartialModel;
#[cfg(test)]
type StdResult<T> = Result<T, Box<dyn std::error::Error>>;
#[cfg(test)]
const CODE_SNIPPET: &str = r#"
#[sea_orm(entity = "Entity")]
struct PartialModel{
default_field: i32,
#[sea_orm(from_col = "bar")]
alias_field: i32,
#[sea_orm(from_expr = "Expr::val(1).add(1)")]
expr_field : i32
}
"#;
#[test]
fn test_load_macro_input() -> StdResult<()> {
let input = syn::parse_str::<DeriveInput>(CODE_SNIPPET)?;
let middle = DerivePartialModel::new(input).unwrap();
assert_eq!(middle.entity_ident, Some(format_ident!("Entity")));
assert_eq!(middle.ident, format_ident!("PartialModel"));
assert_eq!(middle.fields.len(), 3);
assert_eq!(
middle.fields[0],
ColumnAs::Col(format_ident!("DefaultField"))
);
assert_eq!(
middle.fields[1],
ColumnAs::ColAlias {
col: format_ident!("Bar"),
field: "alias_field".to_string()
},
);
assert_eq!(
middle.fields[2],
ColumnAs::Expr {
expr: syn::parse_str("Expr::val(1).add(1)").unwrap(),
field_name: "expr_field".to_string()
}
);
Ok(())
}
}

View File

@ -1,6 +1,7 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput, Error};
#[cfg(feature = "derive")]
@ -677,6 +678,80 @@ pub fn derive_from_json_query_result(input: TokenStream) -> TokenStream {
}
}
/// The DerivePartialModel derive macro will implement `sea_orm::PartialModelTrait` for simplify partial model queries.
///
/// ## Usage
///
/// ```rust
/// use sea_orm::{entity::prelude::*, sea_query::Expr, DerivePartialModel, FromQueryResult};
/// use serde::{Deserialize, Serialize};
///
/// #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)]
/// #[sea_orm(table_name = "posts")]
/// pub struct Model {
/// #[sea_orm(primary_key)]
/// pub id: i32,
/// pub title: String,
/// #[sea_orm(column_type = "Text")]
/// pub text: String,
/// }
/// # #[derive(Copy, Clone, Debug, EnumIter)]
/// # pub enum Relation {}
/// #
/// # impl RelationTrait for Relation {
/// # fn def(&self) -> RelationDef {
/// # panic!("No Relation");
/// # }
/// # }
/// #
/// # impl ActiveModelBehavior for ActiveModel {}
///
/// #[derive(Debug, FromQueryResult, DerivePartialModel)]
/// #[sea_orm(entity = "Entity")]
/// struct SelectResult {
/// title: String,
/// #[sea_orm(from_col = "text")]
/// content: String,
/// #[sea_orm(from_expr = "Expr::val(1).add(1)")]
/// sum: i32,
/// }
/// ```
///
/// If all fields in the partial model is `from_expr`, the `entity` can be ignore.
/// ```
/// use sea_orm::{entity::prelude::*, sea_query::Expr, DerivePartialModel, FromQueryResult};
///
/// #[derive(Debug, FromQueryResult, DerivePartialModel)]
/// struct SelectResult {
/// #[sea_orm(from_expr = "Expr::val(1).add(1)")]
/// sum: i32,
/// }
/// ```
///
/// A field cannot have attributes `from_col` and `from_expr` at the same time.
/// Or, it will result in a compile error.
///
/// ```compile_fail
/// use sea_orm::{entity::prelude::*, FromQueryResult, DerivePartialModel, sea_query::Expr};
///
/// #[derive(Debug, FromQueryResult, DerivePartialModel)]
/// #[sea_orm(entity = "Entity")]
/// struct SelectResult {
/// #[sea_orm(from_expr = "Expr::val(1).add(1)", from_col = "foo")]
/// sum: i32
/// }
/// ```
#[cfg(feature = "derive")]
#[proc_macro_derive(DerivePartialModel, attributes(sea_orm))]
pub fn derive_partial_model(input: TokenStream) -> TokenStream {
let derive_input = parse_macro_input!(input);
match derives::expand_derive_partial_model(derive_input) {
Ok(token_stream) => token_stream.into(),
Err(e) => e.to_compile_error().into(),
}
}
#[doc(hidden)]
#[cfg(feature = "derive")]
#[proc_macro_attribute]

View File

@ -103,6 +103,7 @@ mod column;
mod identity;
mod link;
mod model;
mod partial_model;
/// Re-export common types from the entity
pub mod prelude;
mod primary_key;
@ -115,6 +116,7 @@ pub use column::*;
pub use identity::*;
pub use link::*;
pub use model::*;
pub use partial_model::*;
// pub use prelude::*;
pub use primary_key::*;
pub use relation::*;

View File

@ -0,0 +1,7 @@
use crate::{FromQueryResult, SelectColumns};
/// A trait for a part of [Model](super::model::ModelTrait)
pub trait PartialModelTrait: FromQueryResult {
/// Select specific columns this [PartialModel] needs
fn select_cols<S: SelectColumns>(select: S) -> S;
}

View File

@ -1,6 +1,6 @@
use crate::{
ConnectionTrait, DbErr, EntityTrait, FromQueryResult, Identity, IntoIdentity, QueryOrder,
Select, SelectModel, SelectorTrait,
ConnectionTrait, DbErr, EntityTrait, FromQueryResult, Identity, IntoIdentity,
PartialModelTrait, QueryOrder, QuerySelect, Select, SelectModel, SelectorTrait,
};
use sea_query::{
Condition, DynIden, Expr, IntoValueTuple, Order, SeaRc, SelectStatement, SimpleExpr, Value,
@ -234,6 +234,14 @@ where
}
}
/// Return a [Selector] from `Self` that wraps a [SelectModel] with a [PartialModel](PartialModelTrait)
pub fn into_partial_model<M>(self) -> Cursor<SelectModel<M>>
where
M: PartialModelTrait,
{
M::select_cols(QuerySelect::select_only(self)).into_model::<M>()
}
/// Construct a [Cursor] that fetch JSON value
#[cfg(feature = "with-json")]
pub fn into_json(self) -> Cursor<SelectModel<JsonValue>> {
@ -247,6 +255,17 @@ where
}
}
impl<S> QuerySelect for Cursor<S>
where
S: SelectorTrait,
{
type QueryStatement = SelectStatement;
fn query(&mut self) -> &mut SelectStatement {
&mut self.query
}
}
impl<S> QueryOrder for Cursor<S>
where
S: SelectorTrait,

View File

@ -1,7 +1,7 @@
use crate::{
error::*, ConnectionTrait, EntityTrait, FromQueryResult, IdenStatic, Iterable, ModelTrait,
PrimaryKeyToColumn, QueryResult, Select, SelectA, SelectB, SelectTwo, SelectTwoMany, Statement,
StreamTrait, TryGetableMany,
PartialModelTrait, PrimaryKeyToColumn, QueryResult, QuerySelect, Select, SelectA, SelectB,
SelectTwo, SelectTwoMany, Statement, StreamTrait, TryGetableMany,
};
use futures::{Stream, TryStreamExt};
use sea_query::SelectStatement;
@ -154,6 +154,14 @@ where
}
}
/// Return a [Selector] from `Self` that wraps a [SelectModel] with a [PartialModel](PartialModelTrait)
pub fn into_partial_model<M>(self) -> Selector<SelectModel<M>>
where
M: PartialModelTrait,
{
M::select_cols(QuerySelect::select_only(self)).into_model::<M>()
}
/// Get a selectable Model as a [JsonValue] for SQL JSON operations
#[cfg(feature = "with-json")]
pub fn into_json(self) -> Selector<SelectModel<JsonValue>> {
@ -397,6 +405,18 @@ where
{
self.into_model().stream(db).await
}
/// Stream the result of the operation with PartialModel
pub async fn stream_partial_model<'a: 'b, 'b, C, M>(
self,
db: &'a C,
) -> Result<impl Stream<Item = Result<M, DbErr>> + 'b + Send, DbErr>
where
C: ConnectionTrait + StreamTrait + Send,
M: PartialModelTrait + Send + 'b,
{
self.into_partial_model().stream(db).await
}
}
impl<E, F> SelectTwo<E, F>
@ -416,6 +436,18 @@ where
}
}
/// Perform a conversion into a [SelectTwoModel] with [PartialModel](PartialModelTrait)
pub fn into_partial_model<M, N>(self) -> Selector<SelectTwoModel<M, N>>
where
M: PartialModelTrait,
N: PartialModelTrait,
{
let select = QuerySelect::select_only(self);
let select = M::select_cols(select);
let select = N::select_cols(select);
select.into_model::<M, N>()
}
/// Convert the Models into JsonValue
#[cfg(feature = "with-json")]
pub fn into_json(self) -> Selector<SelectTwoModel<JsonValue, JsonValue>> {
@ -451,6 +483,19 @@ where
{
self.into_model().stream(db).await
}
/// Stream the result of the operation with PartialModel
pub async fn stream_partial_model<'a: 'b, 'b, C, M, N>(
self,
db: &'a C,
) -> Result<impl Stream<Item = Result<(M, Option<N>), DbErr>> + 'b + Send, DbErr>
where
C: ConnectionTrait + StreamTrait + Send,
M: PartialModelTrait + Send + 'b,
N: PartialModelTrait + Send + 'b,
{
self.into_partial_model().stream(db).await
}
}
impl<E, F> SelectTwoMany<E, F>
@ -470,6 +515,18 @@ where
}
}
/// Performs a conversion to [Selector] with partial model
fn into_partial_model<M, N>(self) -> Selector<SelectTwoModel<M, N>>
where
M: PartialModelTrait,
N: PartialModelTrait,
{
let select = self.select_only();
let select = M::select_cols(select);
let select = N::select_cols(select);
select.into_model()
}
/// Convert the results to JSON
#[cfg(feature = "with-json")]
pub fn into_json(self) -> Selector<SelectTwoModel<JsonValue, JsonValue>> {
@ -490,6 +547,19 @@ where
self.into_model().stream(db).await
}
/// Stream the result of the operation with PartialModel
pub async fn stream_partial_model<'a: 'b, 'b, C, M, N>(
self,
db: &'a C,
) -> Result<impl Stream<Item = Result<(M, Option<N>), DbErr>> + 'b + Send, DbErr>
where
C: ConnectionTrait + StreamTrait + Send,
M: PartialModelTrait + Send + 'b,
N: PartialModelTrait + Send + 'b,
{
self.into_partial_model().stream(db).await
}
/// Get all Models from the select operation
///
/// > `SelectTwoMany::one()` method has been dropped (#486)

View File

@ -350,8 +350,8 @@ pub use schema::*;
pub use sea_orm_macros::{
DeriveActiveEnum, DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn,
DeriveCustomColumn, DeriveEntity, DeriveEntityModel, DeriveIntoActiveModel,
DeriveMigrationName, DeriveModel, DerivePrimaryKey, DeriveRelation, FromJsonQueryResult,
FromQueryResult,
DeriveMigrationName, DeriveModel, DerivePartialModel, DerivePrimaryKey, DeriveRelation,
FromJsonQueryResult, FromQueryResult,
};
pub use sea_query;

View File

@ -1,4 +1,4 @@
use crate::{DbBackend, Statement};
use crate::{ColumnTrait, DbBackend, IntoIdentity, IntoSimpleExpr, QuerySelect, Statement};
use sea_query::QueryStatementBuilder;
/// A Trait for any type performing queries on a Model or ActiveModel
@ -55,3 +55,36 @@ pub trait QueryTrait {
}
}
}
/// Select specific column for partial model queries
pub trait SelectColumns {
/// Add a select column
///
/// For more detail, please visit [QuerySelect::column]
fn select_column<C: ColumnTrait>(self, col: C) -> Self;
/// Add a select column with alias
///
/// For more detail, please visit [QuerySelect::column_as]
fn select_column_as<C, I>(self, col: C, alias: I) -> Self
where
C: IntoSimpleExpr,
I: IntoIdentity;
}
impl<S> SelectColumns for S
where
S: QuerySelect,
{
fn select_column<C: ColumnTrait>(self, col: C) -> Self {
QuerySelect::column(self, col)
}
fn select_column_as<C, I>(self, col: C, alias: I) -> Self
where
C: IntoSimpleExpr,
I: IntoIdentity,
{
QuerySelect::column_as(self, col, alias)
}
}

View File

@ -2,7 +2,9 @@ pub mod common;
pub use common::{features::*, setup::*, TestContext};
use pretty_assertions::assert_eq;
use sea_orm::{entity::prelude::*, entity::*, DatabaseConnection};
use sea_orm::{
entity::prelude::*, entity::*, DatabaseConnection, DerivePartialModel, FromQueryResult,
};
#[sea_orm_macros::test]
#[cfg(all(feature = "sqlx-postgres", feature = "postgres-array"))]
@ -11,6 +13,7 @@ async fn main() -> Result<(), DbErr> {
create_tables(&ctx.db).await?;
insert_collection(&ctx.db).await?;
update_collection(&ctx.db).await?;
select_collection(&ctx.db).await?;
ctx.delete().await;
Ok(())
@ -149,3 +152,27 @@ pub async fn update_collection(db: &DatabaseConnection) -> Result<(), DbErr> {
Ok(())
}
pub async fn select_collection(db: &DatabaseConnection) -> Result<(), DbErr> {
use collection::*;
#[derive(DerivePartialModel, FromQueryResult, Debug, PartialEq)]
#[sea_orm(entity = "Entity")]
struct PartialSelectResult {
name: String,
}
let result = Entity::find_by_id(1)
.into_partial_model::<PartialSelectResult>()
.one(db)
.await?;
assert_eq!(
result,
Some(PartialSelectResult {
name: "Collection 1".into(),
})
);
Ok(())
}

View File

@ -2,7 +2,7 @@ pub mod common;
pub use common::{features::*, setup::*, TestContext};
use pretty_assertions::assert_eq;
use sea_orm::{entity::prelude::*, FromQueryResult};
use sea_orm::{entity::prelude::*, DerivePartialModel, FromQueryResult};
use serde_json::json;
#[sea_orm_macros::test]
@ -202,7 +202,7 @@ pub async fn cursor_pagination(db: &DatabaseConnection) -> Result<(), DbErr> {
// Fetch custom struct
#[derive(FromQueryResult, Debug, PartialEq)]
#[derive(FromQueryResult, Debug, PartialEq, Clone)]
struct Row {
id: i32,
}
@ -233,5 +233,44 @@ pub async fn cursor_pagination(db: &DatabaseConnection) -> Result<(), DbErr> {
[json!({ "id": 6 }), json!({ "id": 7 })]
);
#[derive(DerivePartialModel, FromQueryResult, Debug, PartialEq, Clone)]
#[sea_orm(entity = "Entity")]
struct PartialRow {
#[sea_orm(from_col = "id")]
id: i32,
#[sea_orm(from_expr = "sea_query::Expr::col(Column::Id).add(1000)")]
id_shifted: i32,
}
let mut cursor = cursor.into_partial_model::<PartialRow>();
assert_eq!(
cursor.first(2).all(db).await?,
[
PartialRow {
id: 6,
id_shifted: 1006,
},
PartialRow {
id: 7,
id_shifted: 1007,
}
]
);
assert_eq!(
cursor.first(3).all(db).await?,
[
PartialRow {
id: 6,
id_shifted: 1006,
},
PartialRow {
id: 7,
id_shifted: 1007,
}
]
);
Ok(())
}

View File

@ -0,0 +1,47 @@
use entity::{Column, Entity};
use sea_orm::{ColumnTrait, DerivePartialModel, FromQueryResult};
use sea_query::Expr;
mod entity {
use sea_orm::prelude::*;
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "foo_table")]
pub struct Model {
#[sea_orm(primary_key)]
id: i32,
foo: i32,
bar: String,
foo2: bool,
bar2: f64,
}
#[derive(Debug, DeriveRelation, EnumIter)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
}
#[derive(FromQueryResult, DerivePartialModel)]
#[sea_orm(entity = "Entity")]
struct SimpleTest {
_foo: i32,
_bar: String,
}
#[derive(FromQueryResult, DerivePartialModel)]
#[sea_orm(entity = "Entity")]
struct FieldFromDiffNameColumnTest {
#[sea_orm(from_col = "foo2")]
_foo: i32,
#[sea_orm(from_col = "bar2")]
_bar: String,
}
#[derive(FromQueryResult, DerivePartialModel)]
struct FieldFromExpr {
#[sea_orm(from_expr = "Column::Bar2.sum()")]
_foo: f64,
#[sea_orm(from_expr = "Expr::col(Column::Id).equals(Column::Foo)")]
_bar: bool,
}

View File

@ -4,7 +4,8 @@ pub use chrono::offset::Utc;
pub use common::{bakery_chain::*, setup::*, TestContext};
pub use rust_decimal::prelude::*;
pub use rust_decimal_macros::dec;
pub use sea_orm::{entity::*, query::*, DbErr, FromQueryResult};
pub use sea_orm::{entity::*, query::*, DbErr, DerivePartialModel, FromQueryResult};
pub use sea_query::{Alias, Expr, Func, SimpleExpr};
pub use uuid::Uuid;
// Run the test locally:
@ -66,6 +67,7 @@ pub async fn left_join() {
.filter(baker::Column::Name.contains("Baker 1"));
let result = select
.clone()
.into_model::<SelectResult>()
.one(&ctx.db)
.await
@ -74,6 +76,28 @@ pub async fn left_join() {
assert_eq!(result.name.as_str(), "Baker 1");
assert_eq!(result.bakery_name, Some("SeaSide Bakery".to_string()));
#[derive(DerivePartialModel, FromQueryResult, Debug, PartialEq)]
#[sea_orm(entity = "Baker")]
struct PartialSelectResult {
name: String,
#[sea_orm(from_expr = "Expr::col((bakery::Entity, bakery::Column::Name))")]
bakery_name: Option<String>,
#[sea_orm(
from_expr = r#"SimpleExpr::FunctionCall(Func::upper(Expr::col((bakery::Entity, bakery::Column::Name))))"#
)]
bakery_name_upper: Option<String>,
}
let result = select
.into_partial_model::<PartialSelectResult>()
.one(&ctx.db)
.await
.unwrap()
.unwrap();
assert_eq!(result.name.as_str(), "Baker 1");
assert_eq!(result.bakery_name, Some("SeaSide Bakery".to_string()));
assert_eq!(result.bakery_name_upper, Some("SEASIDE BAKERY".to_string()));
let select = baker::Entity::find()
.left_join(bakery::Entity)
.select_only()