Skip to main content

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