Skip to content

Commit 388a538

Browse files
committed
fix: allow LEFT JOIN to produce None for nested models
1 parent b9b236d commit 388a538

File tree

2 files changed

+88
-12
lines changed

2 files changed

+88
-12
lines changed

sea-orm-macros/src/derives/model.rs

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -103,28 +103,74 @@ impl DeriveModel {
103103
let ident = &self.ident;
104104
let field_idents = &self.field_idents;
105105
let column_idents = &self.column_idents;
106-
let field_values: Vec<TokenStream> = column_idents
106+
let field_types = &self.field_types;
107+
let ignore_attrs = &self.ignore_attrs;
108+
109+
let (field_readers, field_unwrappers): (Vec<TokenStream>, Vec<TokenStream>) = field_idents
107110
.iter()
108-
.zip(&self.ignore_attrs)
109-
.map(|(column_ident, ignore)| {
110-
if *ignore {
111-
quote! {
112-
Default::default()
113-
}
111+
.zip(column_idents)
112+
.zip(field_types)
113+
.zip(ignore_attrs)
114+
.map(|(((field_ident, column_ident), field_type), &ignore)| {
115+
if ignore {
116+
let reader = quote! {
117+
let #field_ident: Option<()> = None;
118+
};
119+
let unwrapper = quote! {
120+
#field_ident: Default::default()
121+
};
122+
(reader, unwrapper)
114123
} else {
115-
quote! {
116-
row.try_get(pre, sea_orm::IdenStatic::as_str(&<<Self as sea_orm::ModelTrait>::Entity as sea_orm::entity::EntityTrait>::Column::#column_ident).into())?
117-
}
124+
let reader = quote! {
125+
let #field_ident =
126+
row.try_get_nullable::<Option<#field_type>>(
127+
pre,
128+
sea_orm::IdenStatic::as_str(
129+
&<<Self as sea_orm::ModelTrait>::Entity
130+
as sea_orm::entity::EntityTrait>::Column::#column_ident
131+
).into()
132+
)?;
133+
};
134+
let unwrapper = quote! {
135+
#field_ident: #field_ident.ok_or_else(|| sea_orm::DbErr::Type(
136+
format!(
137+
"Missing value for column '{}'",
138+
sea_orm::IdenStatic::as_str(
139+
&<<Self as sea_orm::ModelTrait>::Entity
140+
as sea_orm::entity::EntityTrait>::Column::#column_ident
141+
)
142+
)
143+
))?
144+
};
145+
(reader, unwrapper)
118146
}
119147
})
120-
.collect();
148+
.unzip();
149+
150+
let all_null_check = field_idents
151+
.iter()
152+
.zip(ignore_attrs.iter().cloned())
153+
.filter(|(_, ignore)| !ignore)
154+
.fold(quote! { true }, |acc, (field_ident, _)| {
155+
quote! { #acc && #field_ident.is_none() }
156+
});
121157

122158
quote!(
123159
#[automatically_derived]
124160
impl sea_orm::FromQueryResult for #ident {
125161
fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result<Self, sea_orm::DbErr> {
162+
Self::from_query_result_nullable(row, pre).map_err(|e| e.into())
163+
}
164+
165+
fn from_query_result_nullable(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result<Self, sea_orm::TryGetError> {
166+
#(#field_readers)*
167+
168+
if #all_null_check {
169+
return Err(sea_orm::TryGetError::Null("All fields of nested model are null".into()));
170+
}
171+
126172
Ok(Self {
127-
#(#field_idents: #field_values),*
173+
#(#field_unwrappers),*
128174
})
129175
}
130176
}

tests/from_query_result_tests.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,36 @@ async fn from_query_result_left_join_does_not_exist() {
7777
ctx.delete().await;
7878
}
7979

80+
#[sea_orm_macros::test]
81+
async fn from_query_result_left_join_with_optional_model_does_not_exist() {
82+
let ctx =
83+
TestContext::new("from_query_result_left_join_with_optional_model_does_not_exist").await;
84+
create_tables(&ctx.db).await.unwrap();
85+
86+
seed_data::init_1(&ctx, false).await;
87+
88+
let cake: CakeWithOptionalBakeryModel = cake::Entity::find()
89+
.select_only()
90+
.column(cake::Column::Id)
91+
.column(cake::Column::Name)
92+
.column(bakery::Column::Id)
93+
.column(bakery::Column::Name)
94+
.column(bakery::Column::ProfitMargin)
95+
.left_join(bakery::Entity)
96+
.order_by_asc(cake::Column::Id)
97+
.into_model()
98+
.one(&ctx.db)
99+
.await
100+
.expect("succeeds to get the result")
101+
.expect("exactly one model in DB");
102+
103+
assert_eq!(cake.id, 13);
104+
assert_eq!(cake.name, "Cheesecake");
105+
assert!(cake.bakery.is_none());
106+
107+
ctx.delete().await;
108+
}
109+
80110
#[sea_orm_macros::test]
81111
async fn from_query_result_left_join_exists() {
82112
let ctx = TestContext::new("from_query_result_left_join_exists").await;

0 commit comments

Comments
 (0)