sea_orm_codegen/entity/writer/
dense.rs

1use super::*;
2use crate::{Relation, RelationType};
3use heck::ToSnakeCase;
4
5impl EntityWriter {
6    #[allow(clippy::too_many_arguments)]
7    pub fn gen_dense_code_blocks(
8        entity: &Entity,
9        with_serde: &WithSerde,
10        date_time_crate: &DateTimeCrate,
11        schema_name: &Option<String>,
12        serde_skip_deserializing_primary_key: bool,
13        serde_skip_hidden_column: bool,
14        model_extra_derives: &TokenStream,
15        model_extra_attributes: &TokenStream,
16        _column_extra_derives: &TokenStream,
17        seaography: bool,
18        impl_active_model_behavior: bool,
19    ) -> Vec<TokenStream> {
20        let mut imports = Self::gen_import(with_serde);
21        imports.extend(Self::gen_import_active_enum(entity));
22        let mut code_blocks = vec![
23            imports,
24            Self::gen_dense_model_struct(
25                entity,
26                with_serde,
27                date_time_crate,
28                schema_name,
29                serde_skip_deserializing_primary_key,
30                serde_skip_hidden_column,
31                model_extra_derives,
32                model_extra_attributes,
33            ),
34        ];
35        if impl_active_model_behavior {
36            code_blocks.push(Self::impl_active_model_behavior());
37        }
38        if seaography {
39            code_blocks.push(Self::gen_dense_related_entity(entity));
40        }
41        code_blocks
42    }
43
44    #[allow(clippy::too_many_arguments)]
45    pub fn gen_dense_model_struct(
46        entity: &Entity,
47        with_serde: &WithSerde,
48        date_time_crate: &DateTimeCrate,
49        schema_name: &Option<String>,
50        serde_skip_deserializing_primary_key: bool,
51        serde_skip_hidden_column: bool,
52        model_extra_derives: &TokenStream,
53        model_extra_attributes: &TokenStream,
54    ) -> TokenStream {
55        let table_name = entity.table_name.as_str();
56        let column_names_snake_case = entity.get_column_names_snake_case();
57        let column_rs_types = entity.get_column_rs_types(date_time_crate);
58        let if_eq_needed = entity.get_eq_needed();
59        let primary_keys: Vec<String> = entity
60            .primary_keys
61            .iter()
62            .map(|pk| pk.name.clone())
63            .collect();
64        let attrs: Vec<TokenStream> = entity
65            .columns
66            .iter()
67            .map(|col| {
68                let mut attrs: Punctuated<_, Comma> = Punctuated::new();
69                let is_primary_key = primary_keys.contains(&col.name);
70                if !col.is_snake_case_name() {
71                    let column_name = &col.name;
72                    attrs.push(quote! { column_name = #column_name });
73                }
74                if is_primary_key {
75                    attrs.push(quote! { primary_key });
76                    if !col.auto_increment {
77                        attrs.push(quote! { auto_increment = false });
78                    }
79                }
80                if let Some(ts) = col.get_col_type_attrs() {
81                    attrs.extend([ts]);
82                    if !col.not_null {
83                        attrs.push(quote! { nullable });
84                    }
85                };
86                if col.unique {
87                    attrs.push(quote! { unique });
88                }
89                let mut ts = quote! {};
90                if !attrs.is_empty() {
91                    for (i, attr) in attrs.into_iter().enumerate() {
92                        if i > 0 {
93                            ts = quote! { #ts, };
94                        }
95                        ts = quote! { #ts #attr };
96                    }
97                    ts = quote! { #[sea_orm(#ts)] };
98                }
99                let serde_attribute = col.get_serde_attribute(
100                    is_primary_key,
101                    serde_skip_deserializing_primary_key,
102                    serde_skip_hidden_column,
103                );
104                ts = quote! {
105                    #ts
106                    #serde_attribute
107                };
108                ts
109            })
110            .collect();
111        let schema_name = match Self::gen_schema_name(schema_name) {
112            Some(schema_name) => quote! {
113                schema_name = #schema_name,
114            },
115            None => quote! {},
116        };
117        let extra_derive = with_serde.extra_derive();
118
119        let mut compound_objects: Punctuated<_, Comma> = Punctuated::new();
120
121        let map_col = |a: &syn::Ident| -> String {
122            let a = a.to_string();
123            let b = a.to_snake_case();
124            if a != b.to_upper_camel_case() {
125                // if roundtrip fails, use original
126                a
127            } else {
128                b
129            }
130        };
131        let map_punctuated = |punctuated: Vec<String>| -> String {
132            let len = punctuated.len();
133            let punctuated = punctuated.join(", ");
134            match len {
135                0..=1 => punctuated,
136                _ => format!("({punctuated})"),
137            }
138        };
139
140        let via_entities = entity.get_conjunct_relations_via_snake_case();
141        for rel in entity.relations.iter() {
142            if !rel.self_referencing && rel.impl_related {
143                let (rel_type, sea_orm_attr) = match rel.rel_type {
144                    RelationType::HasOne => (format_ident!("HasOne"), quote!(#[sea_orm(has_one)])),
145                    RelationType::HasMany => {
146                        (format_ident!("HasMany"), quote!(#[sea_orm(has_many)]))
147                    }
148                    RelationType::BelongsTo => {
149                        let (from, to) = rel.get_src_ref_columns(map_col, map_col, map_punctuated);
150                        let on_update = if let Some(action) = &rel.on_update {
151                            let action = Relation::get_foreign_key_action(action);
152                            quote!(, on_update = #action)
153                        } else {
154                            quote!()
155                        };
156                        let on_delete = if let Some(action) = &rel.on_delete {
157                            let action = Relation::get_foreign_key_action(action);
158                            quote!(, on_delete = #action)
159                        } else {
160                            quote!()
161                        };
162                        let relation_enum = if rel.num_suffix > 0 {
163                            let relation_enum = rel.get_enum_name().to_string();
164                            quote!(relation_enum = #relation_enum,)
165                        } else {
166                            quote!()
167                        };
168                        (
169                            format_ident!("HasOne"),
170                            quote!(#[sea_orm(belongs_to, #relation_enum from = #from, to = #to #on_update #on_delete)]),
171                        )
172                    }
173                };
174
175                if let Some(to_entity) = rel.get_module_name() {
176                    if !via_entities.contains(&to_entity) {
177                        // skip junctions
178                        let field = if matches!(rel.rel_type, RelationType::HasMany) {
179                            format_ident!(
180                                "{}",
181                                pluralizer::pluralize(&to_entity.to_string(), 2, false)
182                            )
183                        } else {
184                            to_entity.clone()
185                        };
186                        let field = if rel.num_suffix == 0 {
187                            field
188                        } else {
189                            format_ident!("{field}_{}", rel.num_suffix)
190                        };
191                        compound_objects.push(quote! {
192                            #sea_orm_attr
193                            pub #field: #rel_type<super::#to_entity::Entity>
194                        });
195                    }
196                }
197            } else if rel.self_referencing {
198                let (from, to) = rel.get_src_ref_columns(map_col, map_col, map_punctuated);
199                let on_update = if let Some(action) = &rel.on_update {
200                    let action = Relation::get_foreign_key_action(action);
201                    quote!(, on_update = #action)
202                } else {
203                    quote!()
204                };
205                let on_delete = if let Some(action) = &rel.on_delete {
206                    let action = Relation::get_foreign_key_action(action);
207                    quote!(, on_delete = #action)
208                } else {
209                    quote!()
210                };
211                let relation_enum = rel.get_enum_name().to_string();
212                let field = format_ident!(
213                    "{}{}",
214                    entity.get_table_name_snake_case_ident(),
215                    if rel.num_suffix > 0 {
216                        format!("_{}", rel.num_suffix)
217                    } else {
218                        "".into()
219                    }
220                );
221
222                compound_objects.push(quote! {
223                    #[sea_orm(self_ref, relation_enum = #relation_enum, from = #from, to = #to #on_update #on_delete)]
224                    pub #field: HasOne<Entity>
225                });
226            }
227        }
228        for (to_entity, via_entity) in entity
229            .get_conjunct_relations_to_snake_case()
230            .into_iter()
231            .zip(via_entities)
232        {
233            let field = format_ident!(
234                "{}",
235                pluralizer::pluralize(&to_entity.to_string(), 2, false)
236            );
237            let via_entity = via_entity.to_string();
238            compound_objects.push(quote! {
239                #[sea_orm(has_many, via = #via_entity)]
240                pub #field: HasMany<super::#to_entity::Entity>
241            });
242        }
243
244        if !compound_objects.is_empty() {
245            compound_objects.push_punct(<syn::Token![,]>::default());
246        }
247
248        quote! {
249            #[sea_orm::model]
250            #[derive(Clone, Debug, PartialEq, DeriveEntityModel #if_eq_needed #extra_derive #model_extra_derives)]
251            #[sea_orm(
252                #schema_name
253                table_name = #table_name
254            )]
255            #model_extra_attributes
256            pub struct Model {
257                #(
258                    #attrs
259                    pub #column_names_snake_case: #column_rs_types,
260                )*
261                #compound_objects
262            }
263        }
264    }
265
266    // we will not need this soon
267    fn gen_dense_related_entity(entity: &Entity) -> TokenStream {
268        let via_entities = entity.get_conjunct_relations_via_snake_case();
269
270        let related_modules = entity.get_related_entity_modules();
271        let related_attrs = entity.get_related_entity_attrs();
272        let related_enum_names = entity.get_related_entity_enum_name();
273
274        let items = related_modules
275            .into_iter()
276            .zip(related_attrs)
277            .zip(related_enum_names)
278            .filter_map(|((related_module, related_attr), related_enum_name)| {
279                if !via_entities.contains(&related_module) {
280                    // skip junctions
281                    Some(quote!(#related_attr #related_enum_name))
282                } else {
283                    None
284                }
285            })
286            .collect::<Vec<_>>();
287
288        quote! {
289            #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)]
290            pub enum RelatedEntity {
291                #(#items),*
292            }
293        }
294    }
295}
296
297#[cfg(test)]
298mod test {
299    #[test]
300    #[ignore]
301    fn test_name() {
302        panic!("{}", pluralizer::pluralize("filling", 2, false));
303    }
304}