@@ -11,11 +11,25 @@ use super::{
1111 detect_circular_fields, generate_inline_struct_construction,
1212 generate_inline_type_construction, has_fk_relations, is_circular_relation_required,
1313 } ,
14- file_lookup:: find_struct_from_schema_path,
14+ file_lookup:: { find_fk_column_from_target_entity , find_struct_from_schema_path} ,
1515 seaorm:: RelationFieldInfo ,
1616} ;
1717use crate :: metadata:: StructMetadata ;
1818
19+ /// Convert snake_case to PascalCase for Column enum names.
20+ /// e.g., "target_user_id" -> "TargetUserId"
21+ fn snake_to_pascal_case ( s : & str ) -> String {
22+ s. split ( '_' )
23+ . map ( |part| {
24+ let mut chars = part. chars ( ) ;
25+ match chars. next ( ) {
26+ None => String :: new ( ) ,
27+ Some ( first) => first. to_uppercase ( ) . chain ( chars) . collect ( ) ,
28+ }
29+ } )
30+ . collect ( )
31+ }
32+
1933/// Build Entity path from Schema path.
2034/// e.g., `crate::models::user::Schema` -> `crate::models::user::Entity`
2135pub fn build_entity_path_from_schema_path (
@@ -89,15 +103,143 @@ pub fn generate_from_model_with_relations(
89103
90104 match rel. relation_type . as_str ( ) {
91105 "HasOne" | "BelongsTo" => {
92- // Load single related entity
93- quote ! {
94- let #field_name = model. find_related( #entity_path) . one( db) . await ?;
106+ // When relation_enum is specified, use the specific Relation variant
107+ // This handles cases where multiple relations point to the same Entity type
108+ if let Some ( ref relation_enum_name) = rel. relation_enum {
109+ let relation_variant =
110+ syn:: Ident :: new ( relation_enum_name, proc_macro2:: Span :: call_site ( ) ) ;
111+
112+ if rel. is_optional {
113+ // Optional FK: load only if FK value exists
114+ if let Some ( ref fk_col) = rel. fk_column {
115+ let fk_ident =
116+ syn:: Ident :: new ( fk_col, proc_macro2:: Span :: call_site ( ) ) ;
117+ quote ! {
118+ let #field_name = match & model. #fk_ident {
119+ Some ( fk_value) => #entity_path:: find_by_id( fk_value. clone( ) ) . one( db) . await ?,
120+ None => None ,
121+ } ;
122+ }
123+ } else {
124+ // Fallback: use find_related with Relation enum
125+ quote ! {
126+ let #field_name = Entity :: find_related( Relation :: #relation_variant)
127+ . filter( <Entity as sea_orm:: EntityTrait >:: PrimaryKey :: eq( & model) )
128+ . one( db)
129+ . await ?;
130+ }
131+ }
132+ } else {
133+ // Required FK: directly query by FK value
134+ if let Some ( ref fk_col) = rel. fk_column {
135+ let fk_ident =
136+ syn:: Ident :: new ( fk_col, proc_macro2:: Span :: call_site ( ) ) ;
137+ quote ! {
138+ let #field_name = #entity_path:: find_by_id( model. #fk_ident. clone( ) ) . one( db) . await ?;
139+ }
140+ } else {
141+ // Fallback: use find_related with Relation enum
142+ quote ! {
143+ let #field_name = Entity :: find_related( Relation :: #relation_variant)
144+ . filter( <Entity as sea_orm:: EntityTrait >:: PrimaryKey :: eq( & model) )
145+ . one( db)
146+ . await ?;
147+ }
148+ }
149+ }
150+ } else {
151+ // Standard case: single relation to target entity, use find_related
152+ quote ! {
153+ let #field_name = model. find_related( #entity_path) . one( db) . await ?;
154+ }
95155 }
96156 }
97157 "HasMany" => {
98- // Load multiple related entities
99- quote ! {
100- let #field_name = model. find_related( #entity_path) . all( db) . await ?;
158+ // HasMany with relation_enum: use FK-based query on target entity
159+ // HasMany without relation_enum: use standard find_related
160+ if let Some ( ref via_rel_value) = rel. via_rel {
161+ // Look up the FK column from the target entity
162+ let schema_path_str = rel. schema_path . to_string ( ) . replace ( ' ' , "" ) ;
163+ if let Some ( fk_col_name) =
164+ find_fk_column_from_target_entity ( & schema_path_str, via_rel_value)
165+ {
166+ // Convert snake_case FK column to PascalCase for Column enum
167+ let fk_col_pascal = snake_to_pascal_case ( & fk_col_name) ;
168+ let fk_col_ident =
169+ syn:: Ident :: new ( & fk_col_pascal, proc_macro2:: Span :: call_site ( ) ) ;
170+
171+ // Build the Column path: entity_path without ::Entity, then ::Column::FkCol
172+ // e.g., crate::models::notification::Entity -> crate::models::notification::Column::TargetUserId
173+ let entity_path_str = entity_path. to_string ( ) . replace ( ' ' , "" ) ;
174+ let column_path_str =
175+ entity_path_str. replace ( ":: Entity" , ":: Column" ) ;
176+ let column_path_idents: Vec < syn:: Ident > = column_path_str
177+ . split ( "::" )
178+ . map ( |s| s. trim ( ) )
179+ . filter ( |s| !s. is_empty ( ) )
180+ . map ( |s| syn:: Ident :: new ( s, proc_macro2:: Span :: call_site ( ) ) )
181+ . collect ( ) ;
182+
183+ quote ! {
184+ let #field_name = #( #column_path_idents) :: * :: #fk_col_ident
185+ . into_column( )
186+ . eq( model. id. clone( ) )
187+ . into_condition( ) ;
188+ let #field_name = #entity_path:: find( )
189+ . filter( #field_name)
190+ . all( db)
191+ . await ?;
192+ }
193+ } else {
194+ // FK column not found - fall back to empty vec with warning comment
195+ quote ! {
196+ // WARNING: Could not find FK column for relation_enum, using empty vec
197+ let #field_name: Vec <_> = vec![ ] ;
198+ }
199+ }
200+ } else if rel. relation_enum . is_some ( ) {
201+ // Has relation_enum but no via_rel - try using relation_enum as via_rel
202+ let via_rel_value = rel. relation_enum . as_ref ( ) . unwrap ( ) ;
203+ let schema_path_str = rel. schema_path . to_string ( ) . replace ( ' ' , "" ) ;
204+ if let Some ( fk_col_name) =
205+ find_fk_column_from_target_entity ( & schema_path_str, via_rel_value)
206+ {
207+ let fk_col_pascal = snake_to_pascal_case ( & fk_col_name) ;
208+ let fk_col_ident =
209+ syn:: Ident :: new ( & fk_col_pascal, proc_macro2:: Span :: call_site ( ) ) ;
210+
211+ let entity_path_str = entity_path. to_string ( ) . replace ( ' ' , "" ) ;
212+ let column_path_str =
213+ entity_path_str. replace ( ":: Entity" , ":: Column" ) ;
214+ let column_path_idents: Vec < syn:: Ident > = column_path_str
215+ . split ( "::" )
216+ . map ( |s| s. trim ( ) )
217+ . filter ( |s| !s. is_empty ( ) )
218+ . map ( |s| syn:: Ident :: new ( s, proc_macro2:: Span :: call_site ( ) ) )
219+ . collect ( ) ;
220+
221+ quote ! {
222+ let #field_name = #( #column_path_idents) :: * :: #fk_col_ident
223+ . into_column( )
224+ . eq( model. id. clone( ) )
225+ . into_condition( ) ;
226+ let #field_name = #entity_path:: find( )
227+ . filter( #field_name)
228+ . all( db)
229+ . await ?;
230+ }
231+ } else {
232+ // FK column not found - fall back to empty vec
233+ quote ! {
234+ // WARNING: Could not find FK column for relation_enum, using empty vec
235+ let #field_name: Vec <_> = vec![ ] ;
236+ }
237+ }
238+ } else {
239+ // Standard HasMany - use find_related
240+ quote ! {
241+ let #field_name = model. find_related( #entity_path) . all( db) . await ?;
242+ }
101243 }
102244 }
103245 _ => quote ! { } ,
@@ -432,6 +574,9 @@ mod tests {
432574 schema_path,
433575 is_optional,
434576 inline_type_info : None ,
577+ relation_enum : None ,
578+ fk_column : None ,
579+ via_rel : None ,
435580 }
436581 }
437582
0 commit comments