From 88776a28ed5f5cf6f162f5ecaf12aa23a88e171c Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 28 Sep 2021 14:27:19 +0800 Subject: [PATCH 1/4] Rename column name & column enum variant --- sea-orm-macros/src/derives/active_model.rs | 33 +++++++++++++- sea-orm-macros/src/derives/column.rs | 29 +++++++++++-- sea-orm-macros/src/derives/entity_model.rs | 50 +++++++++++++++++++--- sea-orm-macros/src/derives/model.rs | 31 ++++++++++++-- sea-orm-macros/src/lib.rs | 2 +- 5 files changed, 131 insertions(+), 14 deletions(-) diff --git a/sea-orm-macros/src/derives/active_model.rs b/sea-orm-macros/src/derives/active_model.rs index 3a96860e2..2227f09bd 100644 --- a/sea-orm-macros/src/derives/active_model.rs +++ b/sea-orm-macros/src/derives/active_model.rs @@ -2,7 +2,7 @@ use crate::util::field_not_ignored; use heck::CamelCase; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, quote_spanned}; -use syn::{Data, DataStruct, Field, Fields, Type}; +use syn::{punctuated::Punctuated, token::Comma, Data, DataStruct, Field, Fields, Lit, Meta, Type}; pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result { let fields = match data { @@ -28,7 +28,36 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result = fields .clone() .into_iter() - .map(|Field { ident, .. }| format_ident!("{}", ident.unwrap().to_string().to_camel_case())) + .map(|field| { + let mut ident = format_ident!( + "{}", + field.ident.as_ref().unwrap().to_string().to_camel_case() + ); + for attr in field.attrs.iter() { + if let Some(ident) = attr.path.get_ident() { + if ident != "sea_orm" { + continue; + } + } else { + continue; + } + if let Ok(list) = attr.parse_args_with(Punctuated::::parse_terminated) + { + for meta in list.iter() { + if let Meta::NameValue(nv) = meta { + if let Some(name) = nv.path.get_ident() { + if name == "enum_name" { + if let Lit::Str(litstr) = &nv.lit { + ident = syn::parse_str(&litstr.value()).unwrap(); + } + } + } + } + } + } + } + ident + }) .collect(); let ty: Vec = fields.into_iter().map(|Field { ty, .. }| ty).collect(); diff --git a/sea-orm-macros/src/derives/column.rs b/sea-orm-macros/src/derives/column.rs index 16f9bb225..5fc471e95 100644 --- a/sea-orm-macros/src/derives/column.rs +++ b/sea-orm-macros/src/derives/column.rs @@ -1,7 +1,7 @@ use heck::{MixedCase, SnakeCase}; use proc_macro2::{Ident, TokenStream}; use quote::{quote, quote_spanned}; -use syn::{Data, DataEnum, Fields, Variant}; +use syn::{punctuated::Punctuated, token::Comma, Data, DataEnum, Fields, Lit, Meta, Variant}; pub fn impl_default_as_str(ident: &Ident, data: &Data) -> syn::Result { let variants = match data { @@ -25,8 +25,31 @@ pub fn impl_default_as_str(ident: &Ident, data: &Data) -> syn::Result = variants .iter() .map(|v| { - let ident = v.ident.to_string().to_snake_case(); - quote! { #ident } + let mut column_name = v.ident.to_string().to_snake_case(); + for attr in v.attrs.iter() { + if let Some(ident) = attr.path.get_ident() { + if ident != "sea_orm" { + continue; + } + } else { + continue; + } + if let Ok(list) = attr.parse_args_with(Punctuated::::parse_terminated) + { + for meta in list.iter() { + if let Meta::NameValue(nv) = meta { + if let Some(name) = nv.path.get_ident() { + if name == "column_name" { + if let Lit::Str(litstr) = &nv.lit { + column_name = litstr.value(); + } + } + } + } + } + } + } + quote! { #column_name } }) .collect(); diff --git a/sea-orm-macros/src/derives/entity_model.rs b/sea-orm-macros/src/derives/entity_model.rs index f07727632..e7a6a23af 100644 --- a/sea-orm-macros/src/derives/entity_model.rs +++ b/sea-orm-macros/src/derives/entity_model.rs @@ -60,9 +60,8 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res if let Fields::Named(fields) = item_struct.fields { for field in fields.named { if let Some(ident) = &field.ident { - let field_name = + let mut field_name = Ident::new(&ident.to_string().to_case(Case::Pascal), Span::call_site()); - columns_enum.push(quote! { #field_name }); let mut nullable = false; let mut default_value = None; @@ -71,7 +70,10 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res let mut ignore = false; let mut unique = false; let mut sql_type = None; - // search for #[sea_orm(primary_key, auto_increment = false, column_type = "String(Some(255))", default_value = "new user", default_expr = "gen_random_uuid()", nullable, indexed, unique)] + let mut column_name = None; + let mut enum_name = None; + let mut is_primary_key = false; + // search for #[sea_orm(primary_key, auto_increment = false, column_type = "String(Some(255))", default_value = "new user", default_expr = "gen_random_uuid()", column_name = "name", enum_name = "Name", nullable, indexed, unique)] for attr in field.attrs.iter() { if let Some(ident) = attr.path.get_ident() { if ident != "sea_orm" { @@ -116,6 +118,26 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res default_value = Some(nv.lit.to_owned()); } else if name == "default_expr" { default_expr = Some(nv.lit.to_owned()); + } else if name == "column_name" { + if let Lit::Str(litstr) = &nv.lit { + column_name = Some(litstr.value()); + } else { + return Err(Error::new( + field.span(), + format!("Invalid column_name {:?}", nv.lit), + )); + } + } else if name == "enum_name" { + if let Lit::Str(litstr) = &nv.lit { + let ty: Ident = + syn::parse_str(&litstr.value())?; + enum_name = Some(ty); + } else { + return Err(Error::new( + field.span(), + format!("Invalid enum_name {:?}", nv.lit), + )); + } } } } @@ -125,7 +147,7 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res ignore = true; break; } else if name == "primary_key" { - primary_keys.push(quote! { #field_name }); + is_primary_key = true; primary_key_types.push(field.ty.clone()); } else if name == "nullable" { nullable = true; @@ -142,9 +164,27 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res } } + if let Some(enum_name) = enum_name { + field_name = enum_name; + } + if ignore { - columns_enum.pop(); continue; + } else { + let variant_attrs = match &column_name { + Some(column_name) => quote! { + #[sea_orm(column_name = #column_name)] + }, + None => quote! {}, + }; + columns_enum.push(quote! { + #variant_attrs + #field_name + }); + } + + if is_primary_key { + primary_keys.push(quote! { #field_name }); } let field_type = match sql_type { diff --git a/sea-orm-macros/src/derives/model.rs b/sea-orm-macros/src/derives/model.rs index 9d619991c..a43b487f9 100644 --- a/sea-orm-macros/src/derives/model.rs +++ b/sea-orm-macros/src/derives/model.rs @@ -3,7 +3,7 @@ use heck::CamelCase; use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned}; use std::iter::FromIterator; -use syn::Ident; +use syn::{punctuated::Punctuated, token::Comma, Ident, Lit, Meta}; enum Error { InputNotStruct, @@ -43,10 +43,35 @@ impl DeriveModel { let column_idents = fields .iter() .map(|field| { - format_ident!( + let mut ident = format_ident!( "{}", field.ident.as_ref().unwrap().to_string().to_camel_case() - ) + ); + for attr in field.attrs.iter() { + if let Some(ident) = attr.path.get_ident() { + if ident != "sea_orm" { + continue; + } + } else { + continue; + } + if let Ok(list) = + attr.parse_args_with(Punctuated::::parse_terminated) + { + for meta in list.iter() { + if let Meta::NameValue(nv) = meta { + if let Some(name) = nv.path.get_ident() { + if name == "enum_name" { + if let Lit::Str(litstr) = &nv.lit { + ident = syn::parse_str(&litstr.value()).unwrap(); + } + } + } + } + } + } + } + ident }) .collect(); diff --git a/sea-orm-macros/src/lib.rs b/sea-orm-macros/src/lib.rs index 629c5c18d..c3f8a50ff 100644 --- a/sea-orm-macros/src/lib.rs +++ b/sea-orm-macros/src/lib.rs @@ -46,7 +46,7 @@ pub fn derive_primary_key(input: TokenStream) -> TokenStream { } } -#[proc_macro_derive(DeriveColumn)] +#[proc_macro_derive(DeriveColumn, attributes(sea_orm))] pub fn derive_column(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); From 89806ef5223463bbb75c7907311905d0568307c4 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 28 Sep 2021 14:27:30 +0800 Subject: [PATCH 2/4] Add test cases --- src/tests_cfg/cake.rs | 1 + tests/common/bakery_chain/metadata.rs | 2 ++ tests/common/setup/schema.rs | 1 + tests/parallel_tests.rs | 3 +++ tests/uuid_tests.rs | 1 + 5 files changed, 8 insertions(+) diff --git a/src/tests_cfg/cake.rs b/src/tests_cfg/cake.rs index 920e1feac..90f529a5c 100644 --- a/src/tests_cfg/cake.rs +++ b/src/tests_cfg/cake.rs @@ -6,6 +6,7 @@ use crate::entity::prelude::*; pub struct Model { #[sea_orm(primary_key)] pub id: i32, + #[sea_orm(column_name = "name", enum_name = "Name")] pub name: String, } diff --git a/tests/common/bakery_chain/metadata.rs b/tests/common/bakery_chain/metadata.rs index 95a7a48bd..0811ce399 100644 --- a/tests/common/bakery_chain/metadata.rs +++ b/tests/common/bakery_chain/metadata.rs @@ -5,6 +5,8 @@ use sea_orm::entity::prelude::*; pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub uuid: Uuid, + #[sea_orm(column_name = "type", enum_name = "Type")] + pub ty: String, pub key: String, pub value: String, pub bytes: Vec, diff --git a/tests/common/setup/schema.rs b/tests/common/setup/schema.rs index 64f31dfe7..176cfc707 100644 --- a/tests/common/setup/schema.rs +++ b/tests/common/setup/schema.rs @@ -283,6 +283,7 @@ pub async fn create_metadata_table(db: &DbConn) -> Result { .not_null() .primary_key(), ) + .col(ColumnDef::new(metadata::Column::Type).string().not_null()) .col(ColumnDef::new(metadata::Column::Key).string().not_null()) .col(ColumnDef::new(metadata::Column::Value).string().not_null()) .col(ColumnDef::new(metadata::Column::Bytes).binary().not_null()) diff --git a/tests/parallel_tests.rs b/tests/parallel_tests.rs index 0ac09fd6f..7b313d0c3 100644 --- a/tests/parallel_tests.rs +++ b/tests/parallel_tests.rs @@ -22,18 +22,21 @@ pub async fn crud_in_parallel(db: &DatabaseConnection) -> Result<(), DbErr> { let metadata = vec![ metadata::Model { uuid: Uuid::new_v4(), + ty: "Type".to_owned(), key: "markup".to_owned(), value: "1.18".to_owned(), bytes: vec![1, 2, 3], }, metadata::Model { uuid: Uuid::new_v4(), + ty: "Type".to_owned(), key: "exchange_rate".to_owned(), value: "0.78".to_owned(), bytes: vec![1, 2, 3], }, metadata::Model { uuid: Uuid::new_v4(), + ty: "Type".to_owned(), key: "service_charge".to_owned(), value: "1.1".to_owned(), bytes: vec![1, 2, 3], diff --git a/tests/uuid_tests.rs b/tests/uuid_tests.rs index e58daca41..2bd83473a 100644 --- a/tests/uuid_tests.rs +++ b/tests/uuid_tests.rs @@ -20,6 +20,7 @@ async fn main() -> Result<(), DbErr> { pub async fn create_metadata(db: &DatabaseConnection) -> Result<(), DbErr> { let metadata = metadata::Model { uuid: Uuid::new_v4(), + ty: "Type".to_owned(), key: "markup".to_owned(), value: "1.18".to_owned(), bytes: vec![1, 2, 3], From 088bfd01e79e1e9da1ecbf29e21067bc9087c555 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 30 Sep 2021 11:19:17 +0800 Subject: [PATCH 3/4] Add unit tests --- src/entity/column.rs | 321 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 321 insertions(+) diff --git a/src/entity/column.rs b/src/entity/column.rs index 32500630d..04cb835e5 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -453,4 +453,325 @@ mod tests { ColumnType::Integer.def().unique().indexed().nullable() ); } + + #[test] + #[cfg(feature = "macros")] + fn column_name_1() { + use sea_query::Iden; + + mod hello { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "hello")] + pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(column_name = "ONE")] + pub one: i32, + pub two: i32, + #[sea_orm(column_name = "3")] + pub three: i32, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + assert_eq!(hello::Column::One.to_string().as_str(), "ONE"); + assert_eq!(hello::Column::Two.to_string().as_str(), "two"); + assert_eq!(hello::Column::Three.to_string().as_str(), "3"); + } + + #[test] + #[cfg(feature = "macros")] + fn column_name_2() { + use sea_query::Iden; + + mod hello { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Copy, Clone, Default, Debug, DeriveEntity)] + pub struct Entity; + + impl EntityName for Entity { + fn table_name(&self) -> &str { + "hello" + } + } + + #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] + pub struct Model { + pub id: i32, + pub one: i32, + pub two: i32, + pub three: i32, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] + pub enum Column { + Id, + #[sea_orm(column_name = "ONE")] + One, + Two, + #[sea_orm(column_name = "3")] + Three, + } + + impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Column::Id => ColumnType::Integer.def(), + Column::One => ColumnType::Integer.def(), + Column::Two => ColumnType::Integer.def(), + Column::Three => ColumnType::Integer.def(), + } + } + } + + #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] + pub enum PrimaryKey { + Id, + } + + impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + + fn auto_increment() -> bool { + true + } + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + assert_eq!(hello::Column::One.to_string().as_str(), "ONE"); + assert_eq!(hello::Column::Two.to_string().as_str(), "two"); + assert_eq!(hello::Column::Three.to_string().as_str(), "3"); + } + + #[test] + #[cfg(feature = "macros")] + fn enum_name_1() { + use sea_query::Iden; + + mod hello { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "hello")] + pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(enum_name = "One1")] + pub one: i32, + pub two: i32, + #[sea_orm(enum_name = "Three3")] + pub three: i32, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + assert_eq!(hello::Column::One1.to_string().as_str(), "one1"); + assert_eq!(hello::Column::Two.to_string().as_str(), "two"); + assert_eq!(hello::Column::Three3.to_string().as_str(), "three3"); + } + + #[test] + #[cfg(feature = "macros")] + fn enum_name_2() { + use sea_query::Iden; + + mod hello { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Copy, Clone, Default, Debug, DeriveEntity)] + pub struct Entity; + + impl EntityName for Entity { + fn table_name(&self) -> &str { + "hello" + } + } + + #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] + pub struct Model { + pub id: i32, + #[sea_orm(enum_name = "One1")] + pub one: i32, + pub two: i32, + #[sea_orm(enum_name = "Three3")] + pub three: i32, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] + pub enum Column { + Id, + One1, + Two, + Three3, + } + + impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Column::Id => ColumnType::Integer.def(), + Column::One1 => ColumnType::Integer.def(), + Column::Two => ColumnType::Integer.def(), + Column::Three3 => ColumnType::Integer.def(), + } + } + } + + #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] + pub enum PrimaryKey { + Id, + } + + impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + + fn auto_increment() -> bool { + true + } + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + assert_eq!(hello::Column::One1.to_string().as_str(), "one1"); + assert_eq!(hello::Column::Two.to_string().as_str(), "two"); + assert_eq!(hello::Column::Three3.to_string().as_str(), "three3"); + } + + #[test] + #[cfg(feature = "macros")] + fn column_name_enum_name_1() { + use sea_query::Iden; + + mod hello { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "hello")] + pub struct Model { + #[sea_orm(primary_key, column_name = "ID", enum_name = "IdentityColumn")] + pub id: i32, + #[sea_orm(column_name = "ONE", enum_name = "One1")] + pub one: i32, + pub two: i32, + #[sea_orm(column_name = "THREE", enum_name = "Three3")] + pub three: i32, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + assert_eq!(hello::Column::IdentityColumn.to_string().as_str(), "ID"); + assert_eq!(hello::Column::One1.to_string().as_str(), "ONE"); + assert_eq!(hello::Column::Two.to_string().as_str(), "two"); + assert_eq!(hello::Column::Three3.to_string().as_str(), "THREE"); + } + + #[test] + #[cfg(feature = "macros")] + fn column_name_enum_name_2() { + use sea_query::Iden; + + mod hello { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Copy, Clone, Default, Debug, DeriveEntity)] + pub struct Entity; + + impl EntityName for Entity { + fn table_name(&self) -> &str { + "hello" + } + } + + #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] + pub struct Model { + #[sea_orm(enum_name = "IdentityCol")] + pub id: i32, + #[sea_orm(enum_name = "One1")] + pub one: i32, + pub two: i32, + #[sea_orm(enum_name = "Three3")] + pub three: i32, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] + pub enum Column { + #[sea_orm(column_name = "ID")] + IdentityCol, + #[sea_orm(column_name = "ONE")] + One1, + Two, + #[sea_orm(column_name = "THREE")] + Three3, + } + + impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Column::IdentityCol => ColumnType::Integer.def(), + Column::One1 => ColumnType::Integer.def(), + Column::Two => ColumnType::Integer.def(), + Column::Three3 => ColumnType::Integer.def(), + } + } + } + + #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] + pub enum PrimaryKey { + IdentityCol, + } + + impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + + fn auto_increment() -> bool { + true + } + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + assert_eq!(hello::Column::IdentityCol.to_string().as_str(), "ID"); + assert_eq!(hello::Column::One1.to_string().as_str(), "ONE"); + assert_eq!(hello::Column::Two.to_string().as_str(), "two"); + assert_eq!(hello::Column::Three3.to_string().as_str(), "THREE"); + } } From b716c9ed57ceae85dad4ca5b2ac1addef869ff7f Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 30 Sep 2021 11:19:26 +0800 Subject: [PATCH 4/4] cargo fmt --- src/executor/insert.rs | 4 ++-- src/query/helper.rs | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/executor/insert.rs b/src/executor/insert.rs index d580f110e..a44867f78 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,6 +1,6 @@ use crate::{ - error::*, ActiveModelTrait, DatabaseConnection, DbBackend, EntityTrait, Insert, PrimaryKeyTrait, - Statement, TryFromU64, + error::*, ActiveModelTrait, DatabaseConnection, DbBackend, EntityTrait, Insert, + PrimaryKeyTrait, Statement, TryFromU64, }; use sea_query::InsertStatement; use std::{future::Future, marker::PhantomData}; diff --git a/src/query/helper.rs b/src/query/helper.rs index 43fed28ab..5b8053ded 100644 --- a/src/query/helper.rs +++ b/src/query/helper.rs @@ -276,7 +276,9 @@ pub trait QueryFilter: Sized { /// struct Input { /// name: Option, /// } - /// let input = Input { name: Some("cheese".to_owned()) }; + /// let input = Input { + /// name: Some("cheese".to_owned()), + /// }; /// /// let mut conditions = Condition::all(); /// if let Some(name) = input.name { @@ -298,13 +300,14 @@ pub trait QueryFilter: Sized { /// struct Input { /// name: Option, /// } - /// let input = Input { name: Some("cheese".to_owned()) }; + /// let input = Input { + /// name: Some("cheese".to_owned()), + /// }; /// /// assert_eq!( /// cake::Entity::find() /// .filter( - /// Condition::all() - /// .add_option(input.name.map(|n| cake::Column::Name.contains(&n))) + /// Condition::all().add_option(input.name.map(|n| cake::Column::Name.contains(&n))) /// ) /// .build(DbBackend::MySql) /// .to_string(),