From ebb994894c6420e7c4831370d009b90a33790979 Mon Sep 17 00:00:00 2001 From: Ziggy K Date: Wed, 5 Aug 2026 07:38:05 +0000 Subject: [PATCH 1/3] Fix generated entity and macro edge cases --- sea-orm-cli/src/cli.rs | 7 +++ sea-orm-cli/src/commands/generate.rs | 61 +++++++++++++++++-- sea-orm-macros/src/derives/active_enum.rs | 2 +- .../src/derives/from_query_result.rs | 16 ++--- sea-orm-macros/src/derives/model.rs | 10 +-- .../tests/derive_active_enum_test.rs | 15 +++++ .../derive_entity_model_column_name_test.rs | 26 ++++++++ 7 files changed, 118 insertions(+), 19 deletions(-) diff --git a/sea-orm-cli/src/cli.rs b/sea-orm-cli/src/cli.rs index 4f0baf8323..47a63165e7 100644 --- a/sea-orm-cli/src/cli.rs +++ b/sea-orm-cli/src/cli.rs @@ -272,6 +272,13 @@ pub enum GenerateSubcommands { )] serde_skip_hidden_column: bool, + #[arg( + long, + default_value = "false", + help = "Include generated columns in entity files. These fields must remain unset when inserting or updating rows" + )] + include_generated_columns: bool, + #[arg( long, default_value = "false", diff --git a/sea-orm-cli/src/commands/generate.rs b/sea-orm-cli/src/commands/generate.rs index 5de5ad6305..677ff19e2e 100644 --- a/sea-orm-cli/src/commands/generate.rs +++ b/sea-orm-cli/src/commands/generate.rs @@ -91,6 +91,13 @@ fn sqlite_column_is_generated(col: &sea_schema::sqlite::def::ColumnInfo) -> bool ) } +#[cfg(feature = "sqlx-postgres")] +fn remove_postgres_partial_unique_constraints(table: &mut sea_schema::postgres::def::TableDef) { + table + .unique_constraints + .retain(|constraint| !constraint.is_partial); +} + pub async fn run_generate_command( command: GenerateSubcommands, verbose: bool, @@ -113,6 +120,7 @@ pub async fn run_generate_command( with_serde, serde_skip_deserializing_primary_key, serde_skip_hidden_column, + include_generated_columns, with_copy_enums, date_time_crate, big_integer_type, @@ -230,7 +238,9 @@ pub async fn run_generate_command( .filter(|schema| filter_skip_tables(&schema.info.name)) .map(|mut schema| { // Skip generated columns (see #3094). - schema.columns.retain(|col| !col.extra.generated); + if !include_generated_columns { + schema.columns.retain(|col| !col.extra.generated); + } schema.write() }) .collect(); @@ -271,9 +281,11 @@ pub async fn run_generate_command( // Skip generated columns: codegen can't round-trip them, and // emitting them as ordinary fields makes INSERT/UPDATE fail // ("cannot INSERT/UPDATE a generated column"). See #3094. - schema - .columns - .retain(|col| !sqlite_column_is_generated(col)); + if !include_generated_columns { + schema + .columns + .retain(|col| !sqlite_column_is_generated(col)); + } schema.write() }) .collect(); @@ -310,7 +322,10 @@ pub async fn run_generate_command( .filter(|schema| filter_skip_tables(&schema.info.name)) .map(|mut schema| { // Skip generated columns (see #3094). - schema.columns.retain(|col| col.generated.is_none()); + if !include_generated_columns { + schema.columns.retain(|col| col.generated.is_none()); + } + remove_postgres_partial_unique_constraints(&mut schema); schema.write() }) .collect(); @@ -816,4 +831,40 @@ mod tests { .collect(); assert_eq!(kept, ["id", "w", "h"]); } + + #[cfg(feature = "sqlx-postgres")] + #[test] + fn test_generate_entity_skips_postgres_partial_unique_constraints() { + use sea_schema::postgres::def::{TableDef, TableInfo, Unique}; + + let mut table = TableDef { + info: TableInfo { + name: "login".to_owned(), + of_type: None, + }, + columns: vec![], + check_constraints: vec![], + not_null_constraints: vec![], + unique_constraints: vec![ + Unique { + name: "login_email_key".to_owned(), + columns: vec!["email".to_owned()], + is_partial: false, + }, + Unique { + name: "login_human_login_id_key".to_owned(), + columns: vec!["human_login_id".to_owned()], + is_partial: true, + }, + ], + primary_key_constraints: vec![], + reference_constraints: vec![], + exclusion_constraints: vec![], + }; + + super::remove_postgres_partial_unique_constraints(&mut table); + + assert_eq!(table.unique_constraints.len(), 1); + assert_eq!(table.unique_constraints[0].name, "login_email_key"); + } } diff --git a/sea-orm-macros/src/derives/active_enum.rs b/sea-orm-macros/src/derives/active_enum.rs index ceebbd0601..2d3ec1adb8 100644 --- a/sea-orm-macros/src/derives/active_enum.rs +++ b/sea-orm-macros/src/derives/active_enum.rs @@ -556,7 +556,7 @@ impl ActiveEnum { impl std::convert::TryFrom<&str> for #ident { type Error = sea_orm::DbErr; - fn try_from(source: &str) -> std::result::Result { + fn try_from(source: &str) -> std::result::Result>::Error> { match source { #( #variant_values => Ok(Self::#variant_idents), )* _ => Err(sea_orm::DbErr::Type(format!( diff --git a/sea-orm-macros/src/derives/from_query_result.rs b/sea-orm-macros/src/derives/from_query_result.rs index 80d0f686d9..88e732ecc3 100644 --- a/sea-orm-macros/src/derives/from_query_result.rs +++ b/sea-orm-macros/src/derives/from_query_result.rs @@ -47,7 +47,7 @@ impl ToTokens for TryFromQueryResultCheck<'_> { .to_owned() .unwrap_or_else(|| ident.unraw().to_string()); tokens.extend(quote! { - let #ident = match row.try_get_nullable(pre, #name) { + let #ident = match __sea_orm_row.try_get_nullable(__sea_orm_pre, #name) { Err(v @ sea_orm::TryGetError::DbErr(_)) => { return Err(v); } @@ -62,16 +62,16 @@ impl ToTokens for TryFromQueryResultCheck<'_> { } ItemType::Nested { prefix } => { let prefix = match (self.0, prefix) { - (_, Some(p)) => quote! { &format!("{pre}{}", #p) }, + (_, Some(p)) => quote! { &format!("{}{}", __sea_orm_pre, #p) }, (true, None) => { let name = ident.unraw().to_string(); - quote! { &format!("{pre}{}_", #name) } + quote! { &format!("{}{}_", __sea_orm_pre, #name) } } - (false, None) => quote! { pre }, + (false, None) => quote! { __sea_orm_pre }, }; tokens.extend(quote! { - let #ident = match sea_orm::FromQueryResult::from_query_result_nullable(row, #prefix) { + let #ident = match sea_orm::FromQueryResult::from_query_result_nullable(__sea_orm_row, #prefix) { Err(v @ sea_orm::TryGetError::DbErr(_)) => { return Err(v); } @@ -223,11 +223,11 @@ impl DeriveFromQueryResult { quote!( #[automatically_derived] impl #impl_generics sea_orm::FromQueryResult for #ident #ty_generics #where_clause { - fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result { - Ok(Self::from_query_result_nullable(row, pre)?) + fn from_query_result(__sea_orm_row: &sea_orm::QueryResult, __sea_orm_pre: &str) -> std::result::Result { + Ok(Self::from_query_result_nullable(__sea_orm_row, __sea_orm_pre)?) } - fn from_query_result_nullable(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result { + fn from_query_result_nullable(__sea_orm_row: &sea_orm::QueryResult, __sea_orm_pre: &str) -> std::result::Result { #(#ident_try_init)* Ok(Self { diff --git a/sea-orm-macros/src/derives/model.rs b/sea-orm-macros/src/derives/model.rs index d29a69b062..f4386e4c37 100644 --- a/sea-orm-macros/src/derives/model.rs +++ b/sea-orm-macros/src/derives/model.rs @@ -125,8 +125,8 @@ impl DeriveModel { } else { let reader = quote! { let #field_ident = - row.try_get_nullable::>( - pre, + __sea_orm_row.try_get_nullable::>( + __sea_orm_pre, sea_orm::IdenStatic::as_str( &<::Entity as sea_orm::entity::EntityTrait>::Column::#column_ident @@ -169,11 +169,11 @@ impl DeriveModel { quote!( #[automatically_derived] impl sea_orm::FromQueryResult for #ident { - fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result { - Self::from_query_result_nullable(row, pre).map_err(Into::into) + fn from_query_result(__sea_orm_row: &sea_orm::QueryResult, __sea_orm_pre: &str) -> std::result::Result { + Self::from_query_result_nullable(__sea_orm_row, __sea_orm_pre).map_err(Into::into) } - fn from_query_result_nullable(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result { + fn from_query_result_nullable(__sea_orm_row: &sea_orm::QueryResult, __sea_orm_pre: &str) -> std::result::Result { #(#field_readers)* if #all_null_check { diff --git a/sea-orm-macros/tests/derive_active_enum_test.rs b/sea-orm-macros/tests/derive_active_enum_test.rs index cdc5d13583..d650c40f50 100644 --- a/sea-orm-macros/tests/derive_active_enum_test.rs +++ b/sea-orm-macros/tests/derive_active_enum_test.rs @@ -74,6 +74,13 @@ pub enum TestEnum3 { HelloWorld, } +#[derive(Debug, EnumIter, DeriveActiveEnum, Eq, PartialEq)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "error_variant")] +enum ErrorVariantEnum { + #[sea_orm(string_value = "error")] + Error, +} + #[test] fn derive_active_enum_value() { assert_eq!(TestEnum::DefaultVariant.to_value(), "defaultVariant"); @@ -96,6 +103,14 @@ fn derive_active_enum_value() { assert_eq!(TestEnum::CustomStringValue.to_value(), "CuStOmStRiNgVaLuE"); } +#[test] +fn derive_active_enum_with_error_variant() { + assert_eq!( + >::try_from("error"), + Ok(ErrorVariantEnum::Error) + ); +} + #[test] fn derive_active_enum_from_value() { assert_eq!( diff --git a/sea-orm-macros/tests/derive_entity_model_column_name_test.rs b/sea-orm-macros/tests/derive_entity_model_column_name_test.rs index bc821e472c..da2433073e 100644 --- a/sea-orm-macros/tests/derive_entity_model_column_name_test.rs +++ b/sea-orm-macros/tests/derive_entity_model_column_name_test.rs @@ -56,3 +56,29 @@ fn test_column_names() { Column::from_str("lAsTnAmE").expect("column from str should recognize column_name attr"); assert!(matches!(col, Column::LastName)); } + +#[allow(dead_code)] +mod query_parameter_name_collisions { + use sea_orm::entity::prelude::*; + use sea_orm_macros::{DeriveEntityModel, FromQueryResult}; + + #[derive(FromQueryResult)] + struct QueryResultProjection { + row: String, + pre: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] + #[sea_orm(table_name = "query_parameter_name_collision")] + pub struct Model { + #[sea_orm(primary_key)] + id: i32, + row: String, + pre: String, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} +} From 62b5411e5510a1dc7a182a2d7c065250d0726e74 Mon Sep 17 00:00:00 2001 From: Ziggy K Date: Thu, 6 Aug 2026 08:30:04 +0000 Subject: [PATCH 2/3] Address review feedback in active enum derive --- sea-orm-macros/src/derives/active_enum.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sea-orm-macros/src/derives/active_enum.rs b/sea-orm-macros/src/derives/active_enum.rs index 2d3ec1adb8..58207d40d5 100644 --- a/sea-orm-macros/src/derives/active_enum.rs +++ b/sea-orm-macros/src/derives/active_enum.rs @@ -556,7 +556,7 @@ impl ActiveEnum { impl std::convert::TryFrom<&str> for #ident { type Error = sea_orm::DbErr; - fn try_from(source: &str) -> std::result::Result>::Error> { + fn try_from(source: &str) -> std::result::Result { match source { #( #variant_values => Ok(Self::#variant_idents), )* _ => Err(sea_orm::DbErr::Type(format!( From d8386ae6d84fbbba00df7527b99479e11885577d Mon Sep 17 00:00:00 2001 From: Ziggy K Date: Sat, 8 Aug 2026 10:31:05 +0000 Subject: [PATCH 3/3] Split PR: remove CLI flag changes to separate branch --- sea-orm-cli/src/cli.rs | 7 ---- sea-orm-cli/src/commands/generate.rs | 61 +++------------------------- 2 files changed, 5 insertions(+), 63 deletions(-) diff --git a/sea-orm-cli/src/cli.rs b/sea-orm-cli/src/cli.rs index 47a63165e7..4f0baf8323 100644 --- a/sea-orm-cli/src/cli.rs +++ b/sea-orm-cli/src/cli.rs @@ -272,13 +272,6 @@ pub enum GenerateSubcommands { )] serde_skip_hidden_column: bool, - #[arg( - long, - default_value = "false", - help = "Include generated columns in entity files. These fields must remain unset when inserting or updating rows" - )] - include_generated_columns: bool, - #[arg( long, default_value = "false", diff --git a/sea-orm-cli/src/commands/generate.rs b/sea-orm-cli/src/commands/generate.rs index 677ff19e2e..5de5ad6305 100644 --- a/sea-orm-cli/src/commands/generate.rs +++ b/sea-orm-cli/src/commands/generate.rs @@ -91,13 +91,6 @@ fn sqlite_column_is_generated(col: &sea_schema::sqlite::def::ColumnInfo) -> bool ) } -#[cfg(feature = "sqlx-postgres")] -fn remove_postgres_partial_unique_constraints(table: &mut sea_schema::postgres::def::TableDef) { - table - .unique_constraints - .retain(|constraint| !constraint.is_partial); -} - pub async fn run_generate_command( command: GenerateSubcommands, verbose: bool, @@ -120,7 +113,6 @@ pub async fn run_generate_command( with_serde, serde_skip_deserializing_primary_key, serde_skip_hidden_column, - include_generated_columns, with_copy_enums, date_time_crate, big_integer_type, @@ -238,9 +230,7 @@ pub async fn run_generate_command( .filter(|schema| filter_skip_tables(&schema.info.name)) .map(|mut schema| { // Skip generated columns (see #3094). - if !include_generated_columns { - schema.columns.retain(|col| !col.extra.generated); - } + schema.columns.retain(|col| !col.extra.generated); schema.write() }) .collect(); @@ -281,11 +271,9 @@ pub async fn run_generate_command( // Skip generated columns: codegen can't round-trip them, and // emitting them as ordinary fields makes INSERT/UPDATE fail // ("cannot INSERT/UPDATE a generated column"). See #3094. - if !include_generated_columns { - schema - .columns - .retain(|col| !sqlite_column_is_generated(col)); - } + schema + .columns + .retain(|col| !sqlite_column_is_generated(col)); schema.write() }) .collect(); @@ -322,10 +310,7 @@ pub async fn run_generate_command( .filter(|schema| filter_skip_tables(&schema.info.name)) .map(|mut schema| { // Skip generated columns (see #3094). - if !include_generated_columns { - schema.columns.retain(|col| col.generated.is_none()); - } - remove_postgres_partial_unique_constraints(&mut schema); + schema.columns.retain(|col| col.generated.is_none()); schema.write() }) .collect(); @@ -831,40 +816,4 @@ mod tests { .collect(); assert_eq!(kept, ["id", "w", "h"]); } - - #[cfg(feature = "sqlx-postgres")] - #[test] - fn test_generate_entity_skips_postgres_partial_unique_constraints() { - use sea_schema::postgres::def::{TableDef, TableInfo, Unique}; - - let mut table = TableDef { - info: TableInfo { - name: "login".to_owned(), - of_type: None, - }, - columns: vec![], - check_constraints: vec![], - not_null_constraints: vec![], - unique_constraints: vec![ - Unique { - name: "login_email_key".to_owned(), - columns: vec!["email".to_owned()], - is_partial: false, - }, - Unique { - name: "login_human_login_id_key".to_owned(), - columns: vec!["human_login_id".to_owned()], - is_partial: true, - }, - ], - primary_key_constraints: vec![], - reference_constraints: vec![], - exclusion_constraints: vec![], - }; - - super::remove_postgres_partial_unique_constraints(&mut table); - - assert_eq!(table.unique_constraints.len(), 1); - assert_eq!(table.unique_constraints[0].name, "login_email_key"); - } }