From a31170821bd37ed001ae8db1a03245e3bf9904d6 Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Thu, 5 Mar 2026 22:32:13 +0100 Subject: [PATCH 1/4] feat: Allow hiding SeaORM specific traits behind feature flags --- sea-orm-cli/src/cli.rs | 6 + sea-orm-cli/src/commands/generate.rs | 2 + sea-orm-codegen/src/entity/active_enum.rs | 75 +++++--- sea-orm-codegen/src/entity/base_entity.rs | 8 +- sea-orm-codegen/src/entity/relation.rs | 27 ++- sea-orm-codegen/src/entity/transformer.rs | 1 + sea-orm-codegen/src/entity/writer.rs | 178 ++++++++++++++++-- sea-orm-codegen/src/entity/writer/compact.rs | 70 +++++-- sea-orm-codegen/src/entity/writer/dense.rs | 61 +++++- sea-orm-codegen/src/entity/writer/expanded.rs | 57 ++++-- sea-orm-codegen/src/entity/writer/frontend.rs | 3 + 11 files changed, 400 insertions(+), 88 deletions(-) diff --git a/sea-orm-cli/src/cli.rs b/sea-orm-cli/src/cli.rs index 8ce70f7c26..b7a05c6512 100644 --- a/sea-orm-cli/src/cli.rs +++ b/sea-orm-cli/src/cli.rs @@ -384,6 +384,12 @@ pub enum GenerateSubcommands { help = "Control how the codegen version is displayed in the top banner of the generated file." )] banner_version: BannerVersion, + + #[arg( + long, + help = "Make generated code require a specific crate feature to enable SeaORM-related code. Without that feature enabled, your entities will be available without SeaORM-specific traits and types." + )] + sea_orm_feature: Option, }, } diff --git a/sea-orm-cli/src/commands/generate.rs b/sea-orm-cli/src/commands/generate.rs index 8cb4c7ba19..d63da71037 100644 --- a/sea-orm-cli/src/commands/generate.rs +++ b/sea-orm-cli/src/commands/generate.rs @@ -44,6 +44,7 @@ pub async fn run_generate_command( impl_active_model_behavior, preserve_user_modifications, banner_version, + sea_orm_feature, } => { if verbose { let _ = tracing_subscriber::fmt() @@ -249,6 +250,7 @@ pub async fn run_generate_command( seaography, impl_active_model_behavior, banner_version.into(), + sea_orm_feature, ); let output = EntityTransformer::transform(table_stmts)?.generate(&writer_context); diff --git a/sea-orm-codegen/src/entity/active_enum.rs b/sea-orm-codegen/src/entity/active_enum.rs index 86c0efe26d..69a0a09b09 100644 --- a/sea-orm-codegen/src/entity/active_enum.rs +++ b/sea-orm-codegen/src/entity/active_enum.rs @@ -3,7 +3,7 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; use sea_query::DynIden; -use crate::{EntityFormat, WithSerde}; +use crate::{EntityFormat, WithSerde, entity::writer::feature_flag_derives}; #[derive(Clone, Debug)] pub struct ActiveEnum { @@ -19,6 +19,7 @@ impl ActiveEnum { extra_derives: &TokenStream, extra_attributes: &TokenStream, entity_format: EntityFormat, + sea_orm_feature: &Option, ) -> TokenStream { let enum_name = &self.enum_name.to_string(); let enum_iden = format_ident!("{}", enum_name.to_upper_camel_case()); @@ -72,27 +73,51 @@ impl ActiveEnum { quote! {} }; - if entity_format == EntityFormat::Frontend { - quote! { - #[derive(Debug, Clone, PartialEq, Eq #copy_derive #serde_derive #extra_derives)] - #extra_attributes - pub enum #enum_iden { - #( - #variants, - )* - } - } - } else { - quote! { - #[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum #copy_derive #serde_derive #extra_derives)] - #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = #enum_name)] - #extra_attributes - pub enum #enum_iden { - #( - #[sea_orm(string_value = #values)] - #variants, - )* - } + let is_frontend = entity_format == EntityFormat::Frontend; + + let (additional_derives, additional_attributes, sea_orm_enum_attr, variant_sea_orm_attrs) = + if is_frontend { + let variant_attrs = values.iter().map(|_| quote! {}).collect(); + (quote! {}, quote! {}, quote! {}, variant_attrs) + } else { + let (add_derives, add_attrs) = + feature_flag_derives(sea_orm_feature, quote! { EnumIter, DeriveActiveEnum }); + let add_derives = if !add_derives.is_empty() { + quote! { , #add_derives } + } else { + quote! {} + }; + + let enum_attr = if let Some(feature) = sea_orm_feature { + quote! { #[cfg_attr(feature = #feature, sea_orm(rs_type = "String", db_type = "Enum", enum_name = #enum_name))] } + } else { + quote! { #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = #enum_name)] } + }; + + let variant_attrs: Vec = values + .iter() + .map(|v| { + if let Some(feature) = sea_orm_feature { + quote! { #[cfg_attr(feature = #feature, sea_orm(string_value = #v))] } + } else { + quote! { #[sea_orm(string_value = #v)] } + } + }) + .collect(); + + (add_derives, add_attrs, enum_attr, variant_attrs) + }; + + quote! { + #[derive(Debug, Clone, PartialEq, Eq #additional_derives #copy_derive #serde_derive #extra_derives)] + #additional_attributes + #sea_orm_enum_attr + #extra_attributes + pub enum #enum_iden { + #( + #variant_sea_orm_attrs + #variants, + )* } } } @@ -121,6 +146,7 @@ mod tests { &TokenStream::new(), &TokenStream::new(), EntityFormat::Compact, + &None, ) .to_string(), quote!( @@ -164,6 +190,7 @@ mod tests { &TokenStream::new(), &TokenStream::new(), EntityFormat::Compact, + &None, ) .to_string(), quote!( @@ -216,6 +243,7 @@ mod tests { &bonus_derive(["specta::Type", "ts_rs::TS"]), &TokenStream::new(), EntityFormat::Compact, + &None, ) .to_string(), build_generated_enum(), @@ -253,6 +281,7 @@ mod tests { &TokenStream::new(), &bonus_attributes([r#"serde(rename_all = "camelCase")"#]), EntityFormat::Compact, + &None, ) .to_string(), quote!( @@ -286,6 +315,7 @@ mod tests { &TokenStream::new(), &bonus_attributes([r#"serde(rename_all = "camelCase")"#, "ts(export)"]), EntityFormat::Compact, + &None, ) .to_string(), quote!( @@ -337,6 +367,7 @@ mod tests { &TokenStream::new(), &TokenStream::new(), EntityFormat::Compact, + &None, ) .to_string(), quote!( diff --git a/sea-orm-codegen/src/entity/base_entity.rs b/sea-orm-codegen/src/entity/base_entity.rs index 865130dc68..1d4415cba7 100644 --- a/sea-orm-codegen/src/entity/base_entity.rs +++ b/sea-orm-codegen/src/entity/base_entity.rs @@ -116,8 +116,8 @@ impl Entity { self.relations.iter().map(|rel| rel.get_def()).collect() } - pub fn get_relation_attrs(&self) -> Vec { - self.relations.iter().map(|rel| rel.get_attrs()).collect() + pub fn get_relation_attrs(&self, sea_orm_feature: &Option) -> Vec { + self.relations.iter().map(|rel| rel.get_attrs(sea_orm_feature)).collect() } /// Trimmed get_related_entity_attrs down to just the entity module @@ -488,10 +488,10 @@ mod tests { fn test_get_relation_attrs() { let entity = setup(); - for (i, elem) in entity.get_relation_attrs().into_iter().enumerate() { + for (i, elem) in entity.get_relation_attrs(&None).into_iter().enumerate() { assert_eq!( elem.to_string(), - entity.relations[i].get_attrs().to_string() + entity.relations[i].get_attrs(&None).to_string() ); } } diff --git a/sea-orm-codegen/src/entity/relation.rs b/sea-orm-codegen/src/entity/relation.rs index 88a198e418..473df92a0a 100644 --- a/sea-orm-codegen/src/entity/relation.rs +++ b/sea-orm-codegen/src/entity/relation.rs @@ -93,7 +93,7 @@ impl Relation { } } - pub fn get_attrs(&self) -> TokenStream { + pub fn get_attrs(&self, sea_orm_feature: &Option) -> TokenStream { let rel_type = self.get_rel_type(); let module_name = if let Some(module_name) = self.get_module_name() { format!("super::{module_name}::") @@ -103,8 +103,14 @@ impl Relation { let ref_entity = format!("{module_name}Entity"); match self.rel_type { RelationType::HasOne | RelationType::HasMany => { - quote! { - #[sea_orm(#rel_type = #ref_entity)] + if let Some(feature) = sea_orm_feature { + quote! { + #[cfg_attr(feature = #feature, sea_orm(#rel_type = #ref_entity))] + } + } else { + quote! { + #[sea_orm(#rel_type = #ref_entity)] + } } } RelationType::BelongsTo => { @@ -138,14 +144,23 @@ impl Relation { } else { quote! {} }; - quote! { - #[sea_orm( + let sea_orm_attr_inner = quote! { + sea_orm( #rel_type = #ref_entity, from = #from, to = #to, #on_update #on_delete - )] + ) + }; + if let Some(feature) = sea_orm_feature { + quote! { + #[cfg_attr(feature = #feature, #sea_orm_attr_inner)] + } + } else { + quote! { + #[#sea_orm_attr_inner] + } } } } diff --git a/sea-orm-codegen/src/entity/transformer.rs b/sea-orm-codegen/src/entity/transformer.rs index fcb782bdaf..397f1564ed 100644 --- a/sea-orm-codegen/src/entity/transformer.rs +++ b/sea-orm-codegen/src/entity/transformer.rs @@ -582,6 +582,7 @@ mod tests { &Default::default(), false, true, + &None, ) .into_iter() .skip(1) diff --git a/sea-orm-codegen/src/entity/writer.rs b/sea-orm-codegen/src/entity/writer.rs index ba1d3ce7e6..0c3f34cdba 100644 --- a/sea-orm-codegen/src/entity/writer.rs +++ b/sea-orm-codegen/src/entity/writer.rs @@ -95,6 +95,7 @@ pub struct EntityWriterContext { pub(crate) seaography: bool, pub(crate) impl_active_model_behavior: bool, pub(crate) banner_version: BannerVersion, + pub(crate) sea_orm_feature: Option, } impl WithSerde { @@ -159,6 +160,20 @@ where ) } +pub(crate) fn feature_flag_derives( + sea_orm_feature: &Option, + derives: TokenStream, +) -> (TokenStream, TokenStream) { + if let Some(feature) = sea_orm_feature { + ( + quote! {}, + quote! { #[cfg_attr(feature = #feature, derive(#derives))] }, + ) + } else { + (derives, quote! {}) + } +} + impl FromStr for WithPrelude { type Err = crate::Error; @@ -233,6 +248,7 @@ impl EntityWriterContext { seaography: bool, impl_active_model_behavior: bool, banner_version: BannerVersion, + sea_orm_feature: Option, ) -> Self { Self { entity_format, @@ -253,6 +269,7 @@ impl EntityWriterContext { seaography, impl_active_model_behavior, banner_version, + sea_orm_feature, } } @@ -280,6 +297,7 @@ impl EntityWriter { context.with_prelude, context.entity_format, context.banner_version, + &context.sea_orm_feature, )); } if !self.enums.is_empty() { @@ -290,6 +308,7 @@ impl EntityWriter { &context.enum_extra_attributes, context.entity_format, context.banner_version, + &context.sea_orm_feature, )); } WriterOutput { files } @@ -335,6 +354,7 @@ impl EntityWriter { &context.column_extra_derives, context.seaography, context.impl_active_model_behavior, + &context.sea_orm_feature, ) } else if context.entity_format == EntityFormat::Expanded { Self::gen_expanded_code_blocks( @@ -349,6 +369,7 @@ impl EntityWriter { &context.column_extra_derives, context.seaography, context.impl_active_model_behavior, + &context.sea_orm_feature, ) } else if context.entity_format == EntityFormat::Dense { Self::gen_dense_code_blocks( @@ -363,6 +384,7 @@ impl EntityWriter { &context.column_extra_derives, context.seaography, context.impl_active_model_behavior, + &context.sea_orm_feature, ) } else { Self::gen_compact_code_blocks( @@ -377,6 +399,7 @@ impl EntityWriter { &context.column_extra_derives, context.seaography, context.impl_active_model_behavior, + &context.sea_orm_feature, ) }; Self::write(&mut lines, code_blocks); @@ -439,6 +462,7 @@ impl EntityWriter { with_prelude: WithPrelude, entity_format: EntityFormat, banner_version: BannerVersion, + sea_orm_feature: &Option, ) -> OutputFile { let mut lines = Vec::new(); Self::write_doc_comment(&mut lines, banner_version); @@ -448,11 +472,20 @@ impl EntityWriter { let code_blocks = self .entities .iter() - .map({ + .map(|entity| { if entity_format == EntityFormat::Frontend { - Self::gen_prelude_use_model + Self::gen_prelude_use_model(entity) + } else if entity_format == EntityFormat::Expanded || sea_orm_feature.is_none() { + // The expanded format always has an Entity struct, so we can also export it with feature flags + Self::gen_prelude_use(entity) + } else if let Some(feature) = sea_orm_feature { + // This prelude does nothing if the feature is not enabled. This is not identical behaviour + // to frontend mode, but making prelude::Thing reference an Entity if the feature is enabled and + // a Model otherwise could cause confusion. + Self::gen_prelude_use_feature_flag(entity, feature) } else { - Self::gen_prelude_use + // This should be unreachable, but let's handle it gracefully just in case + Self::gen_prelude_use(entity) } }) .collect(); @@ -471,13 +504,17 @@ impl EntityWriter { extra_attributes: &TokenStream, entity_format: EntityFormat, banner_version: BannerVersion, + sea_orm_feature: &Option, ) -> OutputFile { let mut lines = Vec::new(); Self::write_doc_comment(&mut lines, banner_version); if entity_format == EntityFormat::Frontend { Self::write(&mut lines, vec![Self::gen_import_serde(with_serde)]); } else { - Self::write(&mut lines, vec![Self::gen_import(with_serde)]); + Self::write( + &mut lines, + vec![Self::gen_import(with_serde, sea_orm_feature)], + ); } lines.push("".to_owned()); let code_blocks = self @@ -490,6 +527,7 @@ impl EntityWriter { extra_derives, extra_attributes, entity_format, + sea_orm_feature, ) }) .collect(); @@ -543,9 +581,14 @@ impl EntityWriter { lines.push("".to_owned()); } - pub fn gen_import(with_serde: &WithSerde) -> TokenStream { + pub fn gen_import(with_serde: &WithSerde, sea_orm_feature: &Option) -> TokenStream { let serde_import = Self::gen_import_serde(with_serde); + let feature_flag = sea_orm_feature + .as_ref() + .map(|f| quote! { #[cfg(feature = #f)] }) + .unwrap_or_default(); quote! { + #feature_flag use sea_orm::entity::prelude::*; #serde_import } @@ -572,10 +615,18 @@ impl EntityWriter { } } - pub fn gen_entity_struct() -> TokenStream { - quote! { - #[derive(Copy, Clone, Default, Debug, DeriveEntity)] - pub struct Entity; + pub fn gen_entity_struct(sea_orm_feature: &Option) -> TokenStream { + if let Some(feature) = sea_orm_feature { + quote! { + #[derive(Copy, Clone, Default, Debug)] + #[cfg_attr(feature = #feature, derive(DeriveEntity))] + pub struct Entity; + } + } else { + quote! { + #[derive(Copy, Clone, Default, Debug, DeriveEntity)] + pub struct Entity; + } } } @@ -625,31 +676,46 @@ impl EntityWriter { .0 } - pub fn gen_column_enum(entity: &Entity, column_extra_derives: &TokenStream) -> TokenStream { + pub fn gen_column_enum( + entity: &Entity, + column_extra_derives: &TokenStream, + sea_orm_feature: &Option, + ) -> TokenStream { let column_variants = entity.columns.iter().map(|col| { let variant = col.get_name_camel_case(); let mut variant = quote! { #variant }; if !col.is_snake_case_name() { let column_name = &col.name; + let attr = if let Some(feature) = sea_orm_feature { + quote! { #[cfg_attr(feature = #feature, sea_orm(column_name = #column_name))] } + } else { + quote! { #[sea_orm(column_name = #column_name)] } + }; variant = quote! { - #[sea_orm(column_name = #column_name)] + #attr #variant }; } variant }); + let (additional_derives, additional_attributes) = + feature_flag_derives(sea_orm_feature, quote! { EnumIter, DeriveColumn }); quote! { - #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn #column_extra_derives)] + #[derive(Copy, Clone, Debug, #additional_derives #column_extra_derives)] + #additional_attributes pub enum Column { #(#column_variants,)* } } } - pub fn gen_primary_key_enum(entity: &Entity) -> TokenStream { + pub fn gen_primary_key_enum(entity: &Entity, sea_orm_feature: &Option) -> TokenStream { let primary_key_names_camel_case = entity.get_primary_key_names_camel_case(); + let (additional_derives, additional_attributes) = + feature_flag_derives(sea_orm_feature, quote! { EnumIter, DerivePrimaryKey }); quote! { - #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] + #[derive(Copy, Clone, Debug, #additional_derives)] + #additional_attributes pub enum PrimaryKey { #(#primary_key_names_camel_case,)* } @@ -670,10 +736,13 @@ impl EntityWriter { } } - pub fn gen_relation_enum(entity: &Entity) -> TokenStream { + pub fn gen_relation_enum(entity: &Entity, sea_orm_feature: &Option) -> TokenStream { let relation_enum_name = entity.get_relation_enum_name(); + let (additional_derives, additional_attributes) = + feature_flag_derives(sea_orm_feature, quote! { EnumIter }); quote! { - #[derive(Copy, Clone, Debug, EnumIter)] + #[derive(Copy, Clone, Debug, #additional_derives)] + #additional_attributes pub enum Relation { #(#relation_enum_name,)* } @@ -746,12 +815,16 @@ impl EntityWriter { } /// Used to generate `enum RelatedEntity` that is useful to the Seaography project - pub fn gen_related_entity(entity: &Entity) -> TokenStream { + pub fn gen_related_entity(entity: &Entity, sea_orm_feature: &Option) -> TokenStream { let related_enum_name = entity.get_related_entity_enum_name(); let related_attrs = entity.get_related_entity_attrs(); + let (additional_derives, additional_attributes) = + feature_flag_derives(sea_orm_feature, quote! { EnumIter, DeriveRelatedEntity }); + quote! { - #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] + #[derive(Copy, Clone, Debug, #additional_derives)] + #additional_attributes pub enum RelatedEntity { #( #related_attrs @@ -860,11 +933,34 @@ impl EntityWriter { } } + pub fn gen_prelude_use_feature_flag(entity: &Entity, sea_orm_feature: &str) -> TokenStream { + let table_name_snake_case_ident = entity.get_table_name_snake_case_ident(); + let table_name_camel_case_ident = entity.get_table_name_camel_case_ident(); + quote! { + #[cfg(feature = #sea_orm_feature)] + pub use super::#table_name_snake_case_ident::Entity as #table_name_camel_case_ident; + } + } + pub fn gen_schema_name(schema_name: &Option) -> Option { schema_name .as_ref() .map(|schema_name| quote! { #schema_name }) } + + pub(crate) fn wrap_impl_feature_gate( + code: TokenStream, + sea_orm_feature: &Option, + ) -> TokenStream { + if let Some(feature) = sea_orm_feature { + quote! { + #[cfg(feature = #feature)] + #code + } + } else { + code + } + } } #[cfg(test)] @@ -1665,6 +1761,7 @@ mod tests { &TokenStream::new(), false, true, + &None, ) .into_iter() .skip(1) @@ -1688,6 +1785,7 @@ mod tests { &TokenStream::new(), false, true, + &None, ) .into_iter() .skip(1) @@ -1753,6 +1851,7 @@ mod tests { &TokenStream::new(), false, true, + &None, ) .into_iter() .skip(1) @@ -1776,6 +1875,7 @@ mod tests { &TokenStream::new(), false, true, + &None, ) .into_iter() .skip(1) @@ -1841,6 +1941,7 @@ mod tests { &TokenStream::new(), false, true, + &None, ) .into_iter() .skip(1) @@ -1864,6 +1965,7 @@ mod tests { &TokenStream::new(), false, true, + &None, ) .into_iter() .skip(1) @@ -1899,6 +2001,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -1917,6 +2020,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -1935,6 +2039,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -1951,6 +2056,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); @@ -1969,6 +2075,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -1987,6 +2094,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2005,6 +2113,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2021,6 +2130,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); @@ -2039,6 +2149,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2057,6 +2168,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2075,6 +2187,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2091,6 +2204,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); @@ -2177,6 +2291,7 @@ mod tests { &TokenStream::new(), true, true, + &None, )) ); @@ -2195,6 +2310,7 @@ mod tests { &TokenStream::new(), true, true, + &None, )) ); @@ -2213,6 +2329,7 @@ mod tests { &TokenStream::new(), true, true, + &None, )) ); @@ -2296,6 +2413,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2312,6 +2430,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2330,6 +2449,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); @@ -2350,6 +2470,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2368,6 +2489,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2386,6 +2508,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); @@ -2406,6 +2529,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2424,6 +2548,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2442,6 +2567,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); @@ -2505,6 +2631,7 @@ mod tests { &bonus_derive(["async_graphql::Enum"]), false, true, + &None, )) ); assert_eq!( @@ -2523,6 +2650,7 @@ mod tests { &bonus_derive(["async_graphql::Enum", "Eq", "PartialEq"]), false, true, + &None, )) ); @@ -2546,6 +2674,7 @@ mod tests { &TokenStream, bool, bool, + &Option, ) -> Vec, >, ) -> io::Result<()> { @@ -2579,6 +2708,7 @@ mod tests { &TokenStream::new(), false, true, + &None, ) .into_iter() .fold(TokenStream::new(), |mut acc, tok| { @@ -2613,6 +2743,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2631,6 +2762,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2649,6 +2781,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); @@ -2669,6 +2802,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2687,6 +2821,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2705,6 +2840,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); @@ -2725,6 +2861,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2743,6 +2880,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); assert_eq!( @@ -2761,6 +2899,7 @@ mod tests { &TokenStream::new(), false, true, + &None, )) ); @@ -2859,6 +2998,7 @@ mod tests { &TokenStream::new(), false, true, + &None, ) .into_iter() .skip(1) @@ -2882,6 +3022,7 @@ mod tests { &TokenStream::new(), false, true, + &None, ) .into_iter() .skip(1) @@ -3064,6 +3205,7 @@ mod tests { &TokenStream::new(), false, true, + &None, ) .into_iter() .skip(1) diff --git a/sea-orm-codegen/src/entity/writer/compact.rs b/sea-orm-codegen/src/entity/writer/compact.rs index 6f9e0c423c..b73dc6c367 100644 --- a/sea-orm-codegen/src/entity/writer/compact.rs +++ b/sea-orm-codegen/src/entity/writer/compact.rs @@ -14,8 +14,9 @@ impl EntityWriter { _column_extra_derives: &TokenStream, seaography: bool, impl_active_model_behavior: bool, + sea_orm_feature: &Option, ) -> Vec { - let mut imports = Self::gen_import(with_serde); + let mut imports = Self::gen_import(with_serde, sea_orm_feature); imports.extend(Self::gen_import_active_enum(entity)); let mut code_blocks = vec![ imports, @@ -28,16 +29,28 @@ impl EntityWriter { serde_skip_hidden_column, model_extra_derives, model_extra_attributes, + sea_orm_feature, ), - Self::gen_compact_relation_enum(entity), + Self::gen_compact_relation_enum(entity, sea_orm_feature) ]; - code_blocks.extend(Self::gen_impl_related(entity)); - code_blocks.extend(Self::gen_impl_conjunct_related(entity)); + let mut sea_orm_codeblocks = Vec::new(); + sea_orm_codeblocks.extend(Self::gen_impl_related(entity)); + sea_orm_codeblocks.extend(Self::gen_impl_conjunct_related(entity)); if impl_active_model_behavior { - code_blocks.extend([Self::impl_active_model_behavior()]); + sea_orm_codeblocks.push(Self::wrap_impl_feature_gate(Self::impl_active_model_behavior(), sea_orm_feature)); } if seaography { - code_blocks.extend([Self::gen_related_entity(entity)]); + sea_orm_codeblocks.push(Self::gen_related_entity(entity, sea_orm_feature)); + }; + if let Some(feature) = sea_orm_feature { + for code_block in sea_orm_codeblocks { + code_blocks.push(quote! { + #[cfg(feature = #feature)] + #code_block + }); + } + } else { + code_blocks.extend(sea_orm_codeblocks); } code_blocks } @@ -52,6 +65,7 @@ impl EntityWriter { serde_skip_hidden_column: bool, model_extra_derives: &TokenStream, model_extra_attributes: &TokenStream, + sea_orm_feature: &Option, ) -> TokenStream { let table_name = entity.table_name.as_str(); let column_names_snake_case = entity.get_column_names_snake_case(); @@ -97,7 +111,11 @@ impl EntityWriter { } ts = quote! { #ts #attr }; } - ts = quote! { #[sea_orm(#ts)] }; + ts = if let Some(feature) = sea_orm_feature { + quote! { #[cfg_attr(feature = #feature, sea_orm(#ts))] } + } else { + quote! { #[sea_orm(#ts)] } + }; } let serde_attribute = col.get_serde_attribute( is_primary_key, @@ -119,12 +137,27 @@ impl EntityWriter { }; let extra_derive = with_serde.extra_derive(); - quote! { - #[derive(Clone, Debug, PartialEq #if_eq_needed, DeriveEntityModel #extra_derive #model_extra_derives)] - #[sea_orm( + let sea_orm_attr_inner = quote! { + sea_orm( #schema_name table_name = #table_name - )] + ) + }; + let (sea_orm_attr, builtin_derives) = if let Some(feature) = sea_orm_feature { + ( + quote! { #[cfg_attr(feature = #feature, derive(DeriveEntityModel))] #[cfg_attr(feature = #feature, #sea_orm_attr_inner)] }, + quote! {}, + ) + } else { + ( + quote! { #[#sea_orm_attr_inner] }, + quote! { , DeriveEntityModel }, + ) + }; + + quote! { + #[derive(Clone, Debug, PartialEq #if_eq_needed #builtin_derives #extra_derive #model_extra_derives)] + #sea_orm_attr #model_extra_attributes pub struct Model { #( @@ -135,11 +168,20 @@ impl EntityWriter { } } - pub fn gen_compact_relation_enum(entity: &Entity) -> TokenStream { - let attrs = entity.get_relation_attrs(); + pub fn gen_compact_relation_enum(entity: &Entity, sea_orm_feature: &Option) -> TokenStream { + let attrs = entity.get_relation_attrs(sea_orm_feature); let relation_enum_name = entity.get_relation_enum_name(); + let (additional_derives, additional_attributes) = if let Some(feature) = sea_orm_feature { + ( + quote! {}, + quote! { #[cfg_attr(feature = #feature, derive(EnumIter, DeriveRelation))] }, + ) + } else { + (quote! { EnumIter, DeriveRelation }, quote! {}) + }; quote! { - #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + #[derive(Copy, Clone, Debug, #additional_derives)] + #additional_attributes pub enum Relation { #( #attrs diff --git a/sea-orm-codegen/src/entity/writer/dense.rs b/sea-orm-codegen/src/entity/writer/dense.rs index 4338071417..ded0a9210c 100644 --- a/sea-orm-codegen/src/entity/writer/dense.rs +++ b/sea-orm-codegen/src/entity/writer/dense.rs @@ -16,8 +16,9 @@ impl EntityWriter { _column_extra_derives: &TokenStream, _seaography: bool, impl_active_model_behavior: bool, + sea_orm_feature: &Option, ) -> Vec { - let mut imports = Self::gen_import(with_serde); + let mut imports = Self::gen_import(with_serde, sea_orm_feature); imports.extend(Self::gen_import_active_enum(entity)); let mut code_blocks = vec![ imports, @@ -30,10 +31,21 @@ impl EntityWriter { serde_skip_hidden_column, model_extra_derives, model_extra_attributes, + sea_orm_feature, ), ]; if impl_active_model_behavior { - code_blocks.push(Self::impl_active_model_behavior()); + let impl_block = Self::impl_active_model_behavior(); + if let Some(feature) = sea_orm_feature { + code_blocks.push(quote! { + #[cfg(feature = #feature)] + { + #impl_block + } + }); + } else { + code_blocks.push(impl_block); + } } code_blocks } @@ -48,6 +60,7 @@ impl EntityWriter { serde_skip_hidden_column: bool, model_extra_derives: &TokenStream, model_extra_attributes: &TokenStream, + sea_orm_feature: &Option, ) -> TokenStream { let table_name = entity.table_name.as_str(); let column_names_snake_case = entity.get_column_names_snake_case(); @@ -93,7 +106,11 @@ impl EntityWriter { } ts = quote! { #ts #attr }; } - ts = quote! { #[sea_orm(#ts)] }; + ts = if let Some(feature) = sea_orm_feature { + quote! { #[cfg_attr(feature = #feature, sea_orm(#ts))] } + } else { + quote! { #[sea_orm(#ts)] } + }; } let serde_attribute = col.get_serde_attribute( is_primary_key, @@ -164,9 +181,16 @@ impl EntityWriter { } else { quote!() }; + let sea_orm_attr_inner = quote! { + sea_orm(belongs_to, #relation_enum from = #from, to = #to #on_update #on_delete) + }; ( format_ident!("HasOne"), - quote!(#[sea_orm(belongs_to, #relation_enum from = #from, to = #to #on_update #on_delete)]), + if let Some(feature) = sea_orm_feature { + quote! { #[cfg_attr(feature = #feature, #sea_orm_attr_inner)] } + } else { + quote! { #[#sea_orm_attr_inner] } + }, ) } }; @@ -244,13 +268,30 @@ impl EntityWriter { compound_objects.push_punct(::default()); } - quote! { - #[sea_orm::model] - #[derive(Clone, Debug, PartialEq #if_eq_needed, DeriveEntityModel #extra_derive #model_extra_derives)] - #[sea_orm( - #schema_name + let sea_orm_attr_inner = quote! { + sea_orm( table_name = #table_name - )] + #schema_name + ) + }; + let (model_attr, sea_orm_attr, builtin_derives) = if let Some(feature) = sea_orm_feature { + ( + quote! {}, + quote! { #[cfg_attr(feature = #feature, derive(#sea_orm_attr_inner))] #[cfg_attr(feature = #feature, sea_orm(#sea_orm_attr_inner))] }, + quote! {}, + ) + } else { + ( + quote! { #[sea_orm::model] }, + quote! { #[#sea_orm_attr_inner] }, + quote! { DeriveEntityModel }, + ) + }; + + quote! { + #model_attr + #[derive(Clone, Debug, PartialEq #if_eq_needed, #builtin_derives #extra_derive #model_extra_derives)] + #sea_orm_attr #model_extra_attributes pub struct Model { #( diff --git a/sea-orm-codegen/src/entity/writer/expanded.rs b/sea-orm-codegen/src/entity/writer/expanded.rs index 76b81ee843..82dfe2ffe7 100644 --- a/sea-orm-codegen/src/entity/writer/expanded.rs +++ b/sea-orm-codegen/src/entity/writer/expanded.rs @@ -14,13 +14,17 @@ impl EntityWriter { column_extra_derives: &TokenStream, seaography: bool, impl_active_model_behavior: bool, + sea_orm_feature: &Option, ) -> Vec { - let mut imports = Self::gen_import(with_serde); + let mut imports = Self::gen_import(with_serde, sea_orm_feature); imports.extend(Self::gen_import_active_enum(entity)); let mut code_blocks = vec![ imports, - Self::gen_entity_struct(), - Self::gen_impl_entity_name(entity, schema_name), + Self::gen_entity_struct(sea_orm_feature), + Self::wrap_impl_feature_gate( + Self::gen_impl_entity_name(entity, schema_name), + sea_orm_feature, + ), Self::gen_expanded_model_struct( entity, with_serde, @@ -29,21 +33,35 @@ impl EntityWriter { serde_skip_hidden_column, model_extra_derives, model_extra_attributes, + sea_orm_feature, + ), + Self::gen_column_enum(entity, column_extra_derives, sea_orm_feature), + Self::gen_primary_key_enum(entity, sea_orm_feature), + Self::wrap_impl_feature_gate( + Self::gen_impl_primary_key(entity, column_option), + sea_orm_feature, ), - Self::gen_column_enum(entity, column_extra_derives), - Self::gen_primary_key_enum(entity), - Self::gen_impl_primary_key(entity, column_option), - Self::gen_relation_enum(entity), - Self::gen_impl_column_trait(entity), - Self::gen_impl_relation_trait(entity), + Self::gen_relation_enum(entity, sea_orm_feature), + Self::wrap_impl_feature_gate(Self::gen_impl_column_trait(entity), sea_orm_feature), + Self::wrap_impl_feature_gate(Self::gen_impl_relation_trait(entity), sea_orm_feature), ]; - code_blocks.extend(Self::gen_impl_related(entity)); - code_blocks.extend(Self::gen_impl_conjunct_related(entity)); + + code_blocks.extend( + Self::gen_impl_related(entity) + .into_iter() + .map(|code| Self::wrap_impl_feature_gate(code, sea_orm_feature)), + ); + code_blocks.extend( + Self::gen_impl_conjunct_related(entity) + .into_iter() + .map(|code| Self::wrap_impl_feature_gate(code, sea_orm_feature)), + ); + if impl_active_model_behavior { - code_blocks.extend([Self::impl_active_model_behavior()]); + code_blocks.push(Self::wrap_impl_feature_gate(Self::impl_active_model_behavior(), sea_orm_feature)); } if seaography { - code_blocks.extend([Self::gen_related_entity(entity)]); + code_blocks.push(Self::gen_related_entity(entity, sea_orm_feature)); } code_blocks } @@ -56,6 +74,7 @@ impl EntityWriter { serde_skip_hidden_column: bool, model_extra_derives: &TokenStream, model_extra_attributes: &TokenStream, + sea_orm_feature: &Option, ) -> TokenStream { let column_names_snake_case = entity.get_column_names_snake_case(); let column_rs_types = entity.get_column_rs_types(column_option); @@ -66,8 +85,18 @@ impl EntityWriter { ); let extra_derive = with_serde.extra_derive(); + let (additional_derives, additional_attributes) = if let Some(feature) = sea_orm_feature { + ( + quote! {}, + quote! { #[cfg_attr(feature = #feature, derive(DeriveModel, DeriveActiveModel))] }, + ) + } else { + (quote! { , DeriveModel, DeriveActiveModel }, quote! {}) + }; + quote! { - #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel #if_eq_needed #extra_derive #model_extra_derives)] + #[derive(Clone, Debug, PartialEq #additional_derives #if_eq_needed #extra_derive #model_extra_derives)] + #additional_attributes #model_extra_attributes pub struct Model { #( diff --git a/sea-orm-codegen/src/entity/writer/frontend.rs b/sea-orm-codegen/src/entity/writer/frontend.rs index 6b2fa2507c..fbcafb0855 100644 --- a/sea-orm-codegen/src/entity/writer/frontend.rs +++ b/sea-orm-codegen/src/entity/writer/frontend.rs @@ -14,6 +14,7 @@ impl EntityWriter { _column_extra_derives: &TokenStream, _seaography: bool, _impl_active_model_behavior: bool, + sea_orm_feature: &Option, ) -> Vec { let mut imports = Self::gen_import_serde(with_serde); imports.extend(Self::gen_import_active_enum(entity)); @@ -28,6 +29,7 @@ impl EntityWriter { serde_skip_hidden_column, model_extra_derives, model_extra_attributes, + sea_orm_feature, ), ]; code_blocks @@ -43,6 +45,7 @@ impl EntityWriter { serde_skip_hidden_column: bool, model_extra_derives: &TokenStream, model_extra_attributes: &TokenStream, + _sea_orm_feature: &Option, ) -> TokenStream { let column_names_snake_case = entity.get_column_names_snake_case(); let column_rs_types = entity.get_column_rs_types(column_option); From ac5d96b9b1039a01ded0fb705b8fa18f7b8aa760 Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Fri, 6 Mar 2026 10:27:22 +0100 Subject: [PATCH 2/4] fix: Remove duplicate feature gate --- sea-orm-codegen/src/entity/writer/compact.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sea-orm-codegen/src/entity/writer/compact.rs b/sea-orm-codegen/src/entity/writer/compact.rs index b73dc6c367..6018f06d13 100644 --- a/sea-orm-codegen/src/entity/writer/compact.rs +++ b/sea-orm-codegen/src/entity/writer/compact.rs @@ -37,7 +37,7 @@ impl EntityWriter { sea_orm_codeblocks.extend(Self::gen_impl_related(entity)); sea_orm_codeblocks.extend(Self::gen_impl_conjunct_related(entity)); if impl_active_model_behavior { - sea_orm_codeblocks.push(Self::wrap_impl_feature_gate(Self::impl_active_model_behavior(), sea_orm_feature)); + sea_orm_codeblocks.push(Self::impl_active_model_behavior()); } if seaography { sea_orm_codeblocks.push(Self::gen_related_entity(entity, sea_orm_feature)); From 57133f89939eed09ab2d80a0280eb6c16708ee7b Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Fri, 6 Mar 2026 10:27:43 +0100 Subject: [PATCH 3/4] chore: Format --- sea-orm-codegen/src/entity/base_entity.rs | 5 ++++- sea-orm-codegen/src/entity/writer/compact.rs | 7 +++++-- sea-orm-codegen/src/entity/writer/expanded.rs | 5 ++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/sea-orm-codegen/src/entity/base_entity.rs b/sea-orm-codegen/src/entity/base_entity.rs index 1d4415cba7..9ffd322b7b 100644 --- a/sea-orm-codegen/src/entity/base_entity.rs +++ b/sea-orm-codegen/src/entity/base_entity.rs @@ -117,7 +117,10 @@ impl Entity { } pub fn get_relation_attrs(&self, sea_orm_feature: &Option) -> Vec { - self.relations.iter().map(|rel| rel.get_attrs(sea_orm_feature)).collect() + self.relations + .iter() + .map(|rel| rel.get_attrs(sea_orm_feature)) + .collect() } /// Trimmed get_related_entity_attrs down to just the entity module diff --git a/sea-orm-codegen/src/entity/writer/compact.rs b/sea-orm-codegen/src/entity/writer/compact.rs index 6018f06d13..a37d35ce92 100644 --- a/sea-orm-codegen/src/entity/writer/compact.rs +++ b/sea-orm-codegen/src/entity/writer/compact.rs @@ -31,7 +31,7 @@ impl EntityWriter { model_extra_attributes, sea_orm_feature, ), - Self::gen_compact_relation_enum(entity, sea_orm_feature) + Self::gen_compact_relation_enum(entity, sea_orm_feature), ]; let mut sea_orm_codeblocks = Vec::new(); sea_orm_codeblocks.extend(Self::gen_impl_related(entity)); @@ -168,7 +168,10 @@ impl EntityWriter { } } - pub fn gen_compact_relation_enum(entity: &Entity, sea_orm_feature: &Option) -> TokenStream { + pub fn gen_compact_relation_enum( + entity: &Entity, + sea_orm_feature: &Option, + ) -> TokenStream { let attrs = entity.get_relation_attrs(sea_orm_feature); let relation_enum_name = entity.get_relation_enum_name(); let (additional_derives, additional_attributes) = if let Some(feature) = sea_orm_feature { diff --git a/sea-orm-codegen/src/entity/writer/expanded.rs b/sea-orm-codegen/src/entity/writer/expanded.rs index 82dfe2ffe7..62aaed7fd1 100644 --- a/sea-orm-codegen/src/entity/writer/expanded.rs +++ b/sea-orm-codegen/src/entity/writer/expanded.rs @@ -58,7 +58,10 @@ impl EntityWriter { ); if impl_active_model_behavior { - code_blocks.push(Self::wrap_impl_feature_gate(Self::impl_active_model_behavior(), sea_orm_feature)); + code_blocks.push(Self::wrap_impl_feature_gate( + Self::impl_active_model_behavior(), + sea_orm_feature, + )); } if seaography { code_blocks.push(Self::gen_related_entity(entity, sea_orm_feature)); From 473847d0449eb78d09452cb3164d0da528bb94d3 Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Fri, 6 Mar 2026 10:28:14 +0100 Subject: [PATCH 4/4] allow too many arguments --- sea-orm-codegen/src/entity/writer.rs | 1 + sea-orm-codegen/src/entity/writer/expanded.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/sea-orm-codegen/src/entity/writer.rs b/sea-orm-codegen/src/entity/writer.rs index 0c3f34cdba..42a8d9d13f 100644 --- a/sea-orm-codegen/src/entity/writer.rs +++ b/sea-orm-codegen/src/entity/writer.rs @@ -496,6 +496,7 @@ impl EntityWriter { } } + #[allow(clippy::too_many_arguments)] pub fn write_sea_orm_active_enums( &self, with_serde: &WithSerde, diff --git a/sea-orm-codegen/src/entity/writer/expanded.rs b/sea-orm-codegen/src/entity/writer/expanded.rs index 62aaed7fd1..f40c6fc2db 100644 --- a/sea-orm-codegen/src/entity/writer/expanded.rs +++ b/sea-orm-codegen/src/entity/writer/expanded.rs @@ -69,6 +69,7 @@ impl EntityWriter { code_blocks } + #[allow(clippy::too_many_arguments)] pub fn gen_expanded_model_struct( entity: &Entity, with_serde: &WithSerde,