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