@@ -3,6 +3,7 @@ use super::{
33 util:: { escape_rust_keyword, field_not_ignored, trim_starting_raw_identifier} ,
44} ;
55use heck:: ToUpperCamelCase ;
6+ use itertools:: izip;
67use proc_macro2:: TokenStream ;
78use quote:: { format_ident, quote} ;
89use std:: iter:: FromIterator ;
@@ -103,28 +104,84 @@ impl DeriveModel {
103104 let ident = & self . ident ;
104105 let field_idents = & self . field_idents ;
105106 let column_idents = & self . column_idents ;
106- let field_values: Vec < TokenStream > = column_idents
107- . iter ( )
108- . zip ( & self . ignore_attrs )
109- . map ( |( column_ident, ignore) | {
110- if * ignore {
111- quote ! {
112- Default :: default ( )
113- }
114- } 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( ) ) ?
107+ let field_types = & self . field_types ;
108+ let ignore_attrs = & self . ignore_attrs ;
109+
110+ let ( field_readers, field_values) : ( Vec < TokenStream > , Vec < TokenStream > ) = izip ! (
111+ field_idents. iter( ) ,
112+ column_idents,
113+ field_types,
114+ ignore_attrs,
115+ )
116+ . map ( |( field_ident, column_ident, field_type, & ignore) | {
117+ if ignore {
118+ let reader = quote ! {
119+ let #field_ident: Option <( ) > = None ;
120+ } ;
121+ let unwrapper = quote ! {
122+ #field_ident: Default :: default ( )
123+ } ;
124+ ( reader, unwrapper)
125+ } else {
126+ let reader = quote ! {
127+ let #field_ident =
128+ row. try_get_nullable:: <Option <#field_type>>(
129+ pre,
130+ sea_orm:: IdenStatic :: as_str(
131+ & <<Self as sea_orm:: ModelTrait >:: Entity
132+ as sea_orm:: entity:: EntityTrait >:: Column :: #column_ident
133+ ) . into( )
134+ ) ?;
135+ } ;
136+ let unwrapper = quote ! {
137+ #field_ident: #field_ident. ok_or_else( || sea_orm:: DbErr :: Type (
138+ format!(
139+ "Missing value for column '{}'" ,
140+ sea_orm:: IdenStatic :: as_str(
141+ & <<Self as sea_orm:: ModelTrait >:: Entity
142+ as sea_orm:: entity:: EntityTrait >:: Column :: #column_ident
143+ )
144+ )
145+ ) ) ?
146+ } ;
147+ ( reader, unwrapper)
148+ }
149+ } )
150+ . unzip ( ) ;
151+
152+ // When a nested model is loaded via LEFT JOIN, all its fields may be NULL.
153+ // In that case we interpret it as "no nested row" (i.e., Option::None).
154+ // This check detects that condition by testing if all non-ignored fields are NULL.
155+ let all_null_check = {
156+ let checks: Vec < _ > = izip ! ( field_idents, ignore_attrs)
157+ . filter_map ( |( field_ident, & ignore) | {
158+ if ignore {
159+ None
160+ } else {
161+ Some ( quote ! { #field_ident. is_none( ) } )
117162 }
118- }
119- } )
120- . collect ( ) ;
163+ } )
164+ . collect ( ) ;
165+
166+ quote ! { true #( && #checks ) * }
167+ } ;
121168
122169 quote ! (
123170 #[ automatically_derived]
124171 impl sea_orm:: FromQueryResult for #ident {
125172 fn from_query_result( row: & sea_orm:: QueryResult , pre: & str ) -> std:: result:: Result <Self , sea_orm:: DbErr > {
173+ Self :: from_query_result_nullable( row, pre) . map_err( Into :: into)
174+ }
175+
176+ fn from_query_result_nullable( row: & sea_orm:: QueryResult , pre: & str ) -> std:: result:: Result <Self , sea_orm:: TryGetError > {
177+ #( #field_readers) *
178+
179+ if #all_null_check {
180+ return Err ( sea_orm:: TryGetError :: Null ( "All fields of nested model are null" . into( ) ) ) ;
181+ }
182+
126183 Ok ( Self {
127- #( #field_idents : # field_values) , *
184+ #( #field_values) , *
128185 } )
129186 }
130187 }
0 commit comments