From 193cd5a29dd736fe9cf18ec7b6e2b4271b81da38 Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Sun, 5 Sep 2021 12:41:54 +0200 Subject: [PATCH 01/10] Start working on EntityModel macro --- sea-orm-macros/Cargo.toml | 1 + sea-orm-macros/src/lib.rs | 176 +++++++++++++++++++++++++++++++++++++- 2 files changed, 176 insertions(+), 1 deletion(-) diff --git a/sea-orm-macros/Cargo.toml b/sea-orm-macros/Cargo.toml index 312adeaa..64dcd0af 100644 --- a/sea-orm-macros/Cargo.toml +++ b/sea-orm-macros/Cargo.toml @@ -20,3 +20,4 @@ syn = { version = "^1", default-features = false, features = [ "full", "derive", quote = "^1" heck = "^0.3" proc-macro2 = "^1" +convert_case = "0.4" diff --git a/sea-orm-macros/src/lib.rs b/sea-orm-macros/src/lib.rs index a2217aa8..61551173 100644 --- a/sea-orm-macros/src/lib.rs +++ b/sea-orm-macros/src/lib.rs @@ -1,7 +1,12 @@ extern crate proc_macro; use proc_macro::TokenStream; -use syn::{parse_macro_input, DeriveInput}; + +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{Data, DeriveInput, Fields, Lit, Meta, parse_macro_input, punctuated::Punctuated, token::Comma}; + +use convert_case::{Case, Casing}; mod derives; @@ -104,3 +109,172 @@ pub fn test(_: TokenStream, input: TokenStream) -> TokenStream { ) .into() } + +#[proc_macro_derive(EntityModel, attributes(sea_orm))] +pub fn derive_entity_model(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + if input.ident != "Model" { + panic!("Struct name must be Model"); + } + + // if #[sea_orm(table_name = "foo")] specified, create Entity struct + let table_name = input.attrs.iter().filter_map(|attr| { + if attr.path.get_ident()? != "sea_orm" { + return None; + } + + let list = attr.parse_args_with(Punctuated::::parse_terminated).ok()?; + for meta in list.iter() { + if let Meta::NameValue(nv) = meta { + if nv.path.get_ident()? == "table_name" { + let table_name = &nv.lit; + return Some(quote! { +#[derive(Copy, Clone, Default, Debug, sea_orm::prelude::DeriveEntity)] +pub struct Entity; + +impl sea_orm::prelude::EntityName for Entity { + fn table_name(&self) -> &str { + #table_name + } +} + }); + } + } + } + + None + }).next().unwrap_or_default(); + + // generate Column enum and it's ColumnTrait impl + let mut columns_enum: Punctuated<_, Comma> = Punctuated::new(); + let mut columns_trait: Punctuated<_, Comma> = Punctuated::new(); + let mut primary_keys: Punctuated<_, Comma> = Punctuated::new(); + if let Data::Struct(item_struct) = input.data { + if let Fields::Named(fields) = item_struct.fields { + for field in fields.named { + if let Some(ident) = &field.ident { + let 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 sql_type = None; + // search for #[sea_orm(primary_key, type = "String", nullable)] + field.attrs.iter().for_each(|attr| { + if let Some(ident) = attr.path.get_ident() { + if ident != "sea_orm" { + return; + } + } + else { + return; + } + + // single param + if let Ok(list) = attr.parse_args_with(Punctuated::::parse_terminated) { + for meta in list.iter() { + match meta { + Meta::NameValue(nv) => { + if let Some(name) = nv.path.get_ident() { + if name == "type" { + if let Lit::Str(litstr) = &nv.lit { + let ty: TokenStream2 = syn::parse_str(&litstr.value()).unwrap(); + sql_type = Some(ty); + } + } + } + }, + Meta::Path(p) => { + if let Some(name) = p.get_ident() { + if name == "primary_key" { + primary_keys.push(quote! { #field_name }); + } + else if name == "nullable" { + nullable = true; + } + } + }, + _ => {}, + } + } + } + }); + let field_type = sql_type.unwrap_or_else(|| { + let field_type = &field.ty; + let temp = quote! { #field_type } + .to_string()//Example: "Option < String >" + .replace(" ", ""); + let temp = if temp.starts_with("Option<") { + nullable = true; + &temp[7..(temp.len() - 1)] + } + else { + temp.as_str() + }; + match temp { + "char" => quote! { Char(None) }, + "String" | "&str" => quote! { String(None) }, + "u8" | "i8" => quote! { TinyInteger }, + "u16" | "i16" => quote! { SmallInteger }, + "u32" | "u64" | "i32" | "i64" => quote! { Integer }, + "u128" | "i128" => quote! { BigInteger }, + "f32" => quote! { Float }, + "f64" => quote! { Double }, + "bool" => quote! { Boolean }, + "NaiveDate" => quote! { Date }, + "NaiveTime" => quote! { Time }, + "NaiveDateTime" => quote! { DateTime }, + "Uuid" => quote! { Uuid }, + "Decimal" => quote! { BigInteger }, + _ => panic!("unrecognized type {}", temp), + } + }); + + if nullable { + columns_trait.push(quote! { Self::#field_name => sea_orm::prelude::ColumnType::#field_type.def().null() }); + } + else { + columns_trait.push(quote! { Self::#field_name => sea_orm::prelude::ColumnType::#field_type.def() }); + } + } + } + } + } + + let primary_key = (!primary_keys.is_empty()).then(|| { + let auto_increment = primary_keys.len() == 1; + quote! { +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + #primary_keys +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + #auto_increment + } +} + } + }).unwrap_or_default(); + + return quote! { +#[derive(Copy, Clone, Debug, sea_orm::prelude::EnumIter, sea_orm::prelude::DeriveColumn)] +pub enum Column { + #columns_enum +} + +impl sea_orm::prelude::ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> sea_orm::prelude::ColumnDef { + match self { + #columns_trait + } + } +} + +#table_name + +#primary_key + }.into(); +} From 8d8b3e78131f6375d66a01739e21d5a286895b70 Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Mon, 6 Sep 2021 11:14:00 +0200 Subject: [PATCH 02/10] Split-up macro --- sea-orm-macros/src/derives/entity_model.rs | 168 ++++++++++++++++++++ sea-orm-macros/src/derives/mod.rs | 2 + sea-orm-macros/src/lib.rs | 174 +-------------------- 3 files changed, 177 insertions(+), 167 deletions(-) create mode 100644 sea-orm-macros/src/derives/entity_model.rs diff --git a/sea-orm-macros/src/derives/entity_model.rs b/sea-orm-macros/src/derives/entity_model.rs new file mode 100644 index 00000000..b29f79f6 --- /dev/null +++ b/sea-orm-macros/src/derives/entity_model.rs @@ -0,0 +1,168 @@ + +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use syn::{Attribute, Data, Fields, Lit, Meta, punctuated::Punctuated, token::Comma}; + +use convert_case::{Case, Casing}; + +pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Result { + // if #[sea_orm(table_name = "foo")] specified, create Entity struct + let table_name = attrs.iter().filter_map(|attr| { + if attr.path.get_ident()? != "sea_orm" { + return None; + } + + let list = attr.parse_args_with(Punctuated::::parse_terminated).ok()?; + for meta in list.iter() { + if let Meta::NameValue(nv) = meta { + if nv.path.get_ident()? == "table_name" { + let table_name = &nv.lit; + return Some(quote! { +#[derive(Copy, Clone, Default, Debug, sea_orm::prelude::DeriveEntity)] +pub struct Entity; + +impl sea_orm::prelude::EntityName for Entity { + fn table_name(&self) -> &str { + #table_name + } +} + }); + } + } + } + + None + }).next().unwrap_or_default(); + + // generate Column enum and it's ColumnTrait impl + let mut columns_enum: Punctuated<_, Comma> = Punctuated::new(); + let mut columns_trait: Punctuated<_, Comma> = Punctuated::new(); + let mut primary_keys: Punctuated<_, Comma> = Punctuated::new(); + if let Data::Struct(item_struct) = data { + if let Fields::Named(fields) = item_struct.fields { + for field in fields.named { + if let Some(ident) = &field.ident { + let 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 sql_type = None; + // search for #[sea_orm(primary_key, type = "String", nullable)] + field.attrs.iter().for_each(|attr| { + if let Some(ident) = attr.path.get_ident() { + if ident != "sea_orm" { + return; + } + } + else { + return; + } + + // single param + if let Ok(list) = attr.parse_args_with(Punctuated::::parse_terminated) { + for meta in list.iter() { + match meta { + Meta::NameValue(nv) => { + if let Some(name) = nv.path.get_ident() { + if name == "type" { + if let Lit::Str(litstr) = &nv.lit { + let ty: TokenStream = syn::parse_str(&litstr.value()).unwrap(); + sql_type = Some(ty); + } + } + } + }, + Meta::Path(p) => { + if let Some(name) = p.get_ident() { + if name == "primary_key" { + primary_keys.push(quote! { #field_name }); + } + else if name == "nullable" { + nullable = true; + } + } + }, + _ => {}, + } + } + } + }); + let field_type = sql_type.unwrap_or_else(|| { + let field_type = &field.ty; + let temp = quote! { #field_type } + .to_string()//Example: "Option < String >" + .replace(" ", ""); + let temp = if temp.starts_with("Option<") { + nullable = true; + &temp[7..(temp.len() - 1)] + } + else { + temp.as_str() + }; + match temp { + "char" => quote! { Char(None) }, + "String" | "&str" => quote! { String(None) }, + "u8" | "i8" => quote! { TinyInteger }, + "u16" | "i16" => quote! { SmallInteger }, + "u32" | "u64" | "i32" | "i64" => quote! { Integer }, + "u128" | "i128" => quote! { BigInteger }, + "f32" => quote! { Float }, + "f64" => quote! { Double }, + "bool" => quote! { Boolean }, + "NaiveDate" => quote! { Date }, + "NaiveTime" => quote! { Time }, + "NaiveDateTime" => quote! { DateTime }, + "Uuid" => quote! { Uuid }, + "Decimal" => quote! { BigInteger }, + _ => panic!("unrecognized type {}", temp), + } + }); + + if nullable { + columns_trait.push(quote! { Self::#field_name => sea_orm::prelude::ColumnType::#field_type.def().null() }); + } + else { + columns_trait.push(quote! { Self::#field_name => sea_orm::prelude::ColumnType::#field_type.def() }); + } + } + } + } + } + + let primary_key = (!primary_keys.is_empty()).then(|| { + let auto_increment = primary_keys.len() == 1; + quote! { +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + #primary_keys +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + #auto_increment + } +} + } + }).unwrap_or_default(); + + return Ok(quote! { +#[derive(Copy, Clone, Debug, sea_orm::prelude::EnumIter, sea_orm::prelude::DeriveColumn)] +pub enum Column { + #columns_enum +} + +impl sea_orm::prelude::ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> sea_orm::prelude::ColumnDef { + match self { + #columns_trait + } + } +} + +#table_name + +#primary_key + }) +} \ No newline at end of file diff --git a/sea-orm-macros/src/derives/mod.rs b/sea-orm-macros/src/derives/mod.rs index 2cf1948a..7d56a730 100644 --- a/sea-orm-macros/src/derives/mod.rs +++ b/sea-orm-macros/src/derives/mod.rs @@ -5,6 +5,7 @@ mod entity; mod from_query_result; mod model; mod primary_key; +mod entity_model; pub use active_model::*; pub use active_model_behavior::*; @@ -13,3 +14,4 @@ pub use entity::*; pub use from_query_result::*; pub use model::*; pub use primary_key::*; +pub use entity_model::*; diff --git a/sea-orm-macros/src/lib.rs b/sea-orm-macros/src/lib.rs index 61551173..f04869b1 100644 --- a/sea-orm-macros/src/lib.rs +++ b/sea-orm-macros/src/lib.rs @@ -1,12 +1,7 @@ extern crate proc_macro; use proc_macro::TokenStream; - -use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; -use quote::quote; -use syn::{Data, DeriveInput, Fields, Lit, Meta, parse_macro_input, punctuated::Punctuated, token::Comma}; - -use convert_case::{Case, Casing}; +use syn::{DeriveInput, parse_macro_input}; mod derives; @@ -93,7 +88,7 @@ pub fn derive_from_query_result(input: TokenStream) -> TokenStream { #[doc(hidden)] #[proc_macro_attribute] pub fn test(_: TokenStream, input: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(input as syn::ItemFn); + let input = parse_macro_input!(input as syn::ItemFn); let ret = &input.sig.output; let name = &input.sig.ident; @@ -112,169 +107,14 @@ pub fn test(_: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_derive(EntityModel, attributes(sea_orm))] pub fn derive_entity_model(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); + let DeriveInput { ident, data, attrs, .. } = parse_macro_input!(input as DeriveInput); - if input.ident != "Model" { + if ident != "Model" { panic!("Struct name must be Model"); } - // if #[sea_orm(table_name = "foo")] specified, create Entity struct - let table_name = input.attrs.iter().filter_map(|attr| { - if attr.path.get_ident()? != "sea_orm" { - return None; - } - - let list = attr.parse_args_with(Punctuated::::parse_terminated).ok()?; - for meta in list.iter() { - if let Meta::NameValue(nv) = meta { - if nv.path.get_ident()? == "table_name" { - let table_name = &nv.lit; - return Some(quote! { -#[derive(Copy, Clone, Default, Debug, sea_orm::prelude::DeriveEntity)] -pub struct Entity; - -impl sea_orm::prelude::EntityName for Entity { - fn table_name(&self) -> &str { - #table_name + match derives::expand_derive_entity_model(data, attrs) { + Ok(ts) => ts.into(), + Err(e) => e.to_compile_error().into(), } -} - }); - } - } - } - - None - }).next().unwrap_or_default(); - - // generate Column enum and it's ColumnTrait impl - let mut columns_enum: Punctuated<_, Comma> = Punctuated::new(); - let mut columns_trait: Punctuated<_, Comma> = Punctuated::new(); - let mut primary_keys: Punctuated<_, Comma> = Punctuated::new(); - if let Data::Struct(item_struct) = input.data { - if let Fields::Named(fields) = item_struct.fields { - for field in fields.named { - if let Some(ident) = &field.ident { - let 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 sql_type = None; - // search for #[sea_orm(primary_key, type = "String", nullable)] - field.attrs.iter().for_each(|attr| { - if let Some(ident) = attr.path.get_ident() { - if ident != "sea_orm" { - return; - } - } - else { - return; - } - - // single param - if let Ok(list) = attr.parse_args_with(Punctuated::::parse_terminated) { - for meta in list.iter() { - match meta { - Meta::NameValue(nv) => { - if let Some(name) = nv.path.get_ident() { - if name == "type" { - if let Lit::Str(litstr) = &nv.lit { - let ty: TokenStream2 = syn::parse_str(&litstr.value()).unwrap(); - sql_type = Some(ty); - } - } - } - }, - Meta::Path(p) => { - if let Some(name) = p.get_ident() { - if name == "primary_key" { - primary_keys.push(quote! { #field_name }); - } - else if name == "nullable" { - nullable = true; - } - } - }, - _ => {}, - } - } - } - }); - let field_type = sql_type.unwrap_or_else(|| { - let field_type = &field.ty; - let temp = quote! { #field_type } - .to_string()//Example: "Option < String >" - .replace(" ", ""); - let temp = if temp.starts_with("Option<") { - nullable = true; - &temp[7..(temp.len() - 1)] - } - else { - temp.as_str() - }; - match temp { - "char" => quote! { Char(None) }, - "String" | "&str" => quote! { String(None) }, - "u8" | "i8" => quote! { TinyInteger }, - "u16" | "i16" => quote! { SmallInteger }, - "u32" | "u64" | "i32" | "i64" => quote! { Integer }, - "u128" | "i128" => quote! { BigInteger }, - "f32" => quote! { Float }, - "f64" => quote! { Double }, - "bool" => quote! { Boolean }, - "NaiveDate" => quote! { Date }, - "NaiveTime" => quote! { Time }, - "NaiveDateTime" => quote! { DateTime }, - "Uuid" => quote! { Uuid }, - "Decimal" => quote! { BigInteger }, - _ => panic!("unrecognized type {}", temp), - } - }); - - if nullable { - columns_trait.push(quote! { Self::#field_name => sea_orm::prelude::ColumnType::#field_type.def().null() }); - } - else { - columns_trait.push(quote! { Self::#field_name => sea_orm::prelude::ColumnType::#field_type.def() }); - } - } - } - } - } - - let primary_key = (!primary_keys.is_empty()).then(|| { - let auto_increment = primary_keys.len() == 1; - quote! { -#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] -pub enum PrimaryKey { - #primary_keys -} - -impl PrimaryKeyTrait for PrimaryKey { - fn auto_increment() -> bool { - #auto_increment - } -} - } - }).unwrap_or_default(); - - return quote! { -#[derive(Copy, Clone, Debug, sea_orm::prelude::EnumIter, sea_orm::prelude::DeriveColumn)] -pub enum Column { - #columns_enum -} - -impl sea_orm::prelude::ColumnTrait for Column { - type EntityName = Entity; - - fn def(&self) -> sea_orm::prelude::ColumnDef { - match self { - #columns_trait - } - } -} - -#table_name - -#primary_key - }.into(); } From 5179fb257ac34d5030fac978d3c008c4834e9aa0 Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Mon, 6 Sep 2021 11:25:05 +0200 Subject: [PATCH 03/10] Add indexed and unique support --- sea-orm-macros/src/derives/entity_model.rs | 29 +++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/sea-orm-macros/src/derives/entity_model.rs b/sea-orm-macros/src/derives/entity_model.rs index b29f79f6..b207f420 100644 --- a/sea-orm-macros/src/derives/entity_model.rs +++ b/sea-orm-macros/src/derives/entity_model.rs @@ -46,8 +46,11 @@ impl sea_orm::prelude::EntityName for Entity { columns_enum.push(quote! { #field_name }); let mut nullable = false; + let mut default_value = None; + let mut indexed = false; + let mut unique = false; let mut sql_type = None; - // search for #[sea_orm(primary_key, type = "String", nullable)] + // search for #[sea_orm(primary_key, column_type = "String", nullable, default_value = "something", indexed, unique)] field.attrs.iter().for_each(|attr| { if let Some(ident) = attr.path.get_ident() { if ident != "sea_orm" { @@ -64,12 +67,15 @@ impl sea_orm::prelude::EntityName for Entity { match meta { Meta::NameValue(nv) => { if let Some(name) = nv.path.get_ident() { - if name == "type" { + if name == "column_type" { if let Lit::Str(litstr) = &nv.lit { let ty: TokenStream = syn::parse_str(&litstr.value()).unwrap(); sql_type = Some(ty); } } + else if name == "default_value" { + default_value = Some(nv.lit.to_owned()); + } } }, Meta::Path(p) => { @@ -80,6 +86,12 @@ impl sea_orm::prelude::EntityName for Entity { else if name == "nullable" { nullable = true; } + else if name == "indexed" { + indexed = true; + } + else if name == "unique" { + unique = true; + } } }, _ => {}, @@ -90,7 +102,7 @@ impl sea_orm::prelude::EntityName for Entity { let field_type = sql_type.unwrap_or_else(|| { let field_type = &field.ty; let temp = quote! { #field_type } - .to_string()//Example: "Option < String >" + .to_string()//E.g.: "Option < String >" .replace(" ", ""); let temp = if temp.starts_with("Option<") { nullable = true; @@ -118,12 +130,17 @@ impl sea_orm::prelude::EntityName for Entity { } }); + let mut match_row = quote! { Self::#field_name => sea_orm::prelude::ColumnType::#field_type.def() }; if nullable { - columns_trait.push(quote! { Self::#field_name => sea_orm::prelude::ColumnType::#field_type.def().null() }); + match_row = quote! { #match_row.null() }; } - else { - columns_trait.push(quote! { Self::#field_name => sea_orm::prelude::ColumnType::#field_type.def() }); + if indexed { + match_row = quote! { #match_row.indexed() }; } + if unique { + match_row = quote! { #match_row.unique() }; + } + columns_trait.push(match_row); } } } From 0eb6902d1b250095ad9774c7b07103bb3a94b902 Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Mon, 6 Sep 2021 12:02:15 +0200 Subject: [PATCH 04/10] default_value and default_expr hypotetical implementation --- sea-orm-macros/src/derives/entity_model.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sea-orm-macros/src/derives/entity_model.rs b/sea-orm-macros/src/derives/entity_model.rs index b207f420..f8e93b75 100644 --- a/sea-orm-macros/src/derives/entity_model.rs +++ b/sea-orm-macros/src/derives/entity_model.rs @@ -47,10 +47,11 @@ impl sea_orm::prelude::EntityName for Entity { let mut nullable = false; let mut default_value = None; + let mut default_expr = None; let mut indexed = false; let mut unique = false; let mut sql_type = None; - // search for #[sea_orm(primary_key, column_type = "String", nullable, default_value = "something", indexed, unique)] + // search for #[sea_orm(primary_key, column_type = "String", default_value = "new user", default_expr = "gen_random_uuid()", nullable, indexed, unique)] field.attrs.iter().for_each(|attr| { if let Some(ident) = attr.path.get_ident() { if ident != "sea_orm" { @@ -76,6 +77,9 @@ impl sea_orm::prelude::EntityName for Entity { else if name == "default_value" { default_value = Some(nv.lit.to_owned()); } + else if name == "default_expr" { + default_expr = Some(nv.lit.to_owned()); + } } }, Meta::Path(p) => { @@ -140,6 +144,12 @@ impl sea_orm::prelude::EntityName for Entity { if unique { match_row = quote! { #match_row.unique() }; } + if let Some(default_value) = default_value { + match_row = quote! { #match_row.default_value(#default_value) }; + } + if let Some(default_expr) = default_expr { + match_row = quote! { #match_row.default_expr(#default_expr) }; + } columns_trait.push(match_row); } } From cc2f491a1eda2a38c795d446cb7853be4e38a2b2 Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Mon, 6 Sep 2021 12:21:21 +0200 Subject: [PATCH 05/10] Remove panic --- sea-orm-macros/src/derives/entity_model.rs | 64 ++++++++++++---------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/sea-orm-macros/src/derives/entity_model.rs b/sea-orm-macros/src/derives/entity_model.rs index f8e93b75..ea228800 100644 --- a/sea-orm-macros/src/derives/entity_model.rs +++ b/sea-orm-macros/src/derives/entity_model.rs @@ -1,7 +1,7 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{Attribute, Data, Fields, Lit, Meta, punctuated::Punctuated, token::Comma}; +use syn::{Attribute, Data, Fields, Lit, Meta, parse::Error, punctuated::Punctuated, spanned::Spanned, token::Comma}; use convert_case::{Case, Casing}; @@ -103,36 +103,40 @@ impl sea_orm::prelude::EntityName for Entity { } } }); - let field_type = sql_type.unwrap_or_else(|| { - let field_type = &field.ty; - let temp = quote! { #field_type } - .to_string()//E.g.: "Option < String >" - .replace(" ", ""); - let temp = if temp.starts_with("Option<") { - nullable = true; - &temp[7..(temp.len() - 1)] + + let field_type = match sql_type { + Some(t) => t, + None => { + let field_type = &field.ty; + let temp = quote! { #field_type } + .to_string()//E.g.: "Option < String >" + .replace(" ", ""); + let temp = if temp.starts_with("Option<") { + nullable = true; + &temp[7..(temp.len() - 1)] + } + else { + temp.as_str() + }; + match temp { + "char" => quote! { Char(None) }, + "String" | "&str" => quote! { String(None) }, + "u8" | "i8" => quote! { TinyInteger }, + "u16" | "i16" => quote! { SmallInteger }, + "u32" | "u64" | "i32" | "i64" => quote! { Integer }, + "u128" | "i128" => quote! { BigInteger }, + "f32" => quote! { Float }, + "f64" => quote! { Double }, + "bool" => quote! { Boolean }, + "NaiveDate" => quote! { Date }, + "NaiveTime" => quote! { Time }, + "NaiveDateTime" => quote! { DateTime }, + "Uuid" => quote! { Uuid }, + "Decimal" => quote! { BigInteger }, + _ => return Err(Error::new(field.span(), format!("unrecognized type {}", temp))), + } } - else { - temp.as_str() - }; - match temp { - "char" => quote! { Char(None) }, - "String" | "&str" => quote! { String(None) }, - "u8" | "i8" => quote! { TinyInteger }, - "u16" | "i16" => quote! { SmallInteger }, - "u32" | "u64" | "i32" | "i64" => quote! { Integer }, - "u128" | "i128" => quote! { BigInteger }, - "f32" => quote! { Float }, - "f64" => quote! { Double }, - "bool" => quote! { Boolean }, - "NaiveDate" => quote! { Date }, - "NaiveTime" => quote! { Time }, - "NaiveDateTime" => quote! { DateTime }, - "Uuid" => quote! { Uuid }, - "Decimal" => quote! { BigInteger }, - _ => panic!("unrecognized type {}", temp), - } - }); + }; let mut match_row = quote! { Self::#field_name => sea_orm::prelude::ColumnType::#field_type.def() }; if nullable { From ab925e8f11e2dbf4197715dc4f11c4590bfca4e0 Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Mon, 6 Sep 2021 12:41:09 +0200 Subject: [PATCH 06/10] Manage auto_increment attribute, call nullable() instead of old null() --- sea-orm-macros/src/derives/entity_model.rs | 34 ++++++++++++++++------ 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/sea-orm-macros/src/derives/entity_model.rs b/sea-orm-macros/src/derives/entity_model.rs index ea228800..21bbd40b 100644 --- a/sea-orm-macros/src/derives/entity_model.rs +++ b/sea-orm-macros/src/derives/entity_model.rs @@ -38,6 +38,7 @@ impl sea_orm::prelude::EntityName for Entity { let mut columns_enum: Punctuated<_, Comma> = Punctuated::new(); let mut columns_trait: Punctuated<_, Comma> = Punctuated::new(); let mut primary_keys: Punctuated<_, Comma> = Punctuated::new(); + let mut auto_increment = true; if let Data::Struct(item_struct) = data { if let Fields::Named(fields) = item_struct.fields { for field in fields.named { @@ -51,15 +52,15 @@ impl sea_orm::prelude::EntityName for Entity { let mut indexed = false; let mut unique = false; let mut sql_type = None; - // search for #[sea_orm(primary_key, column_type = "String", default_value = "new user", default_expr = "gen_random_uuid()", nullable, indexed, unique)] - field.attrs.iter().for_each(|attr| { + // search for #[sea_orm(primary_key, auto_increment = false, column_type = "String", default_value = "new user", default_expr = "gen_random_uuid()", nullable, indexed, unique)] + for attr in field.attrs.iter() { if let Some(ident) = attr.path.get_ident() { if ident != "sea_orm" { - return; + continue; } } else { - return; + continue; } // single param @@ -70,9 +71,24 @@ impl sea_orm::prelude::EntityName for Entity { if let Some(name) = nv.path.get_ident() { if name == "column_type" { if let Lit::Str(litstr) = &nv.lit { - let ty: TokenStream = syn::parse_str(&litstr.value()).unwrap(); + let ty: TokenStream = syn::parse_str(&litstr.value())?; sql_type = Some(ty); } + else { + return Err(Error::new(field.span(), format!("Invalid column_type {:?}", nv.lit))); + } + } + else if name == "auto_increment" { + if let Lit::Str(litstr) = &nv.lit { + auto_increment = match litstr.value().as_str() { + "true" => true, + "false" => false, + _ => return Err(Error::new(field.span(), format!("Invalid auto_increment = {}", litstr.value()))), + }; + } + else { + return Err(Error::new(field.span(), format!("Invalid auto_increment = {:?}", nv.lit))); + } } else if name == "default_value" { default_value = Some(nv.lit.to_owned()); @@ -102,7 +118,7 @@ impl sea_orm::prelude::EntityName for Entity { } } } - }); + } let field_type = match sql_type { Some(t) => t, @@ -140,7 +156,7 @@ impl sea_orm::prelude::EntityName for Entity { let mut match_row = quote! { Self::#field_name => sea_orm::prelude::ColumnType::#field_type.def() }; if nullable { - match_row = quote! { #match_row.null() }; + match_row = quote! { #match_row.nullable() }; } if indexed { match_row = quote! { #match_row.indexed() }; @@ -161,7 +177,7 @@ impl sea_orm::prelude::EntityName for Entity { } let primary_key = (!primary_keys.is_empty()).then(|| { - let auto_increment = primary_keys.len() == 1; + let auto_increment = auto_increment && primary_keys.len() == 1; quote! { #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { @@ -196,4 +212,4 @@ impl sea_orm::prelude::ColumnTrait for Column { #primary_key }) -} \ No newline at end of file +} From a4b4f4925f395877ac70fb234215454270b73728 Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Mon, 6 Sep 2021 12:58:47 +0200 Subject: [PATCH 07/10] Minor comments fixes --- sea-orm-macros/src/derives/entity_model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sea-orm-macros/src/derives/entity_model.rs b/sea-orm-macros/src/derives/entity_model.rs index 21bbd40b..0c1a4f11 100644 --- a/sea-orm-macros/src/derives/entity_model.rs +++ b/sea-orm-macros/src/derives/entity_model.rs @@ -52,7 +52,7 @@ impl sea_orm::prelude::EntityName for Entity { let mut indexed = false; let mut unique = false; let mut sql_type = None; - // search for #[sea_orm(primary_key, auto_increment = false, column_type = "String", default_value = "new user", default_expr = "gen_random_uuid()", nullable, indexed, unique)] + // 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)] for attr in field.attrs.iter() { if let Some(ident) = attr.path.get_ident() { if ident != "sea_orm" { From c273cf0a39152498da74f147c88e95cff41b46b4 Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Tue, 7 Sep 2021 10:04:33 +0200 Subject: [PATCH 08/10] Manage schema_name attribute, add prelude export, update example --- examples/async-std/src/example_cake.rs | 30 ++---------- sea-orm-macros/Cargo.toml | 2 +- sea-orm-macros/src/derives/entity_model.rs | 55 +++++++++++++--------- src/entity/prelude.rs | 2 +- src/lib.rs | 2 +- 5 files changed, 39 insertions(+), 52 deletions(-) diff --git a/examples/async-std/src/example_cake.rs b/examples/async-std/src/example_cake.rs index 475315e8..6820be61 100644 --- a/examples/async-std/src/example_cake.rs +++ b/examples/async-std/src/example_cake.rs @@ -1,37 +1,13 @@ use sea_orm::entity::prelude::*; -#[derive(Copy, Clone, Default, Debug, DeriveEntity)] -pub struct Entity; - -impl EntityName for Entity { - fn table_name(&self) -> &str { - "cake" - } -} - -#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, EntityModel)] +#[sea_orm(table_name = "cake")] pub struct Model { + #[sea_orm(primary_key)] pub id: i32, pub name: String, } -#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] -pub enum Column { - Id, - Name, -} - -#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] -pub enum PrimaryKey { - Id, -} - -impl PrimaryKeyTrait for PrimaryKey { - fn auto_increment() -> bool { - true - } -} - #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, diff --git a/sea-orm-macros/Cargo.toml b/sea-orm-macros/Cargo.toml index 64dcd0af..06a5132b 100644 --- a/sea-orm-macros/Cargo.toml +++ b/sea-orm-macros/Cargo.toml @@ -16,7 +16,7 @@ path = "src/lib.rs" proc-macro = true [dependencies] -syn = { version = "^1", default-features = false, features = [ "full", "derive", "clone-impls", "parsing", "proc-macro", "printing" ] } +syn = { version = "^1", default-features = false, features = [ "full", "derive", "clone-impls", "parsing", "proc-macro", "printing", "extra-traits" ] } quote = "^1" heck = "^0.3" proc-macro2 = "^1" diff --git a/sea-orm-macros/src/derives/entity_model.rs b/sea-orm-macros/src/derives/entity_model.rs index 0c1a4f11..82bedd59 100644 --- a/sea-orm-macros/src/derives/entity_model.rs +++ b/sea-orm-macros/src/derives/entity_model.rs @@ -6,33 +6,44 @@ use syn::{Attribute, Data, Fields, Lit, Meta, parse::Error, punctuated::Punctuat use convert_case::{Case, Casing}; pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Result { - // if #[sea_orm(table_name = "foo")] specified, create Entity struct - let table_name = attrs.iter().filter_map(|attr| { - if attr.path.get_ident()? != "sea_orm" { - return None; + // if #[sea_orm(table_name = "foo", schema_name = "bar")] specified, create Entity struct + let mut table_name = None; + let mut schema_name = quote! { None }; + attrs.iter().for_each(|attr| { + if attr.path.get_ident().map(|i| i == "sea_orm") != Some(true) { + return; } - let list = attr.parse_args_with(Punctuated::::parse_terminated).ok()?; - for meta in list.iter() { - if let Meta::NameValue(nv) = meta { - if nv.path.get_ident()? == "table_name" { - let table_name = &nv.lit; - return Some(quote! { -#[derive(Copy, Clone, Default, Debug, sea_orm::prelude::DeriveEntity)] -pub struct Entity; - -impl sea_orm::prelude::EntityName for Entity { - fn table_name(&self) -> &str { - #table_name - } -} - }); + 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(ident) = nv.path.get_ident() { + if ident == "table_name" { + table_name = Some(nv.lit.clone()); + } + else if ident == "schema_name" { + let name = &nv.lit; + schema_name = quote! { Some(#name) }; + } + } } } } + }); + let entity_def = table_name.map(|table_name| quote! { + #[derive(Copy, Clone, Default, Debug, sea_orm::prelude::DeriveEntity)] + pub struct Entity; + + impl sea_orm::prelude::EntityName for Entity { + fn schema_name(&self) -> &str { + #schema_name + } - None - }).next().unwrap_or_default(); + fn table_name(&self) -> &str { + #table_name + } + } + }).unwrap_or_default(); // generate Column enum and it's ColumnTrait impl let mut columns_enum: Punctuated<_, Comma> = Punctuated::new(); @@ -208,7 +219,7 @@ impl sea_orm::prelude::ColumnTrait for Column { } } -#table_name +#entity_def #primary_key }) diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs index 447117b7..a417f77a 100644 --- a/src/entity/prelude.rs +++ b/src/entity/prelude.rs @@ -1,7 +1,7 @@ pub use crate::{ error::*, ActiveModelBehavior, ActiveModelTrait, ColumnDef, ColumnTrait, ColumnType, DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveCustomColumn, DeriveEntity, - DeriveModel, DerivePrimaryKey, EntityName, EntityTrait, EnumIter, Iden, IdenStatic, ModelTrait, + DeriveModel, DerivePrimaryKey, EntityModel, EntityName, EntityTrait, EnumIter, Iden, IdenStatic, ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult, Related, RelationDef, RelationTrait, Select, Value, }; diff --git a/src/lib.rs b/src/lib.rs index 377bd62b..d2474dc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -219,7 +219,7 @@ pub use query::*; pub use sea_orm_macros::{ DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveCustomColumn, DeriveEntity, - DeriveModel, DerivePrimaryKey, FromQueryResult, + DeriveModel, DerivePrimaryKey, FromQueryResult, EntityModel, }; pub use sea_query; From abcdbf2a48522a1855396f002071f9fee0d77153 Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Tue, 7 Sep 2021 10:18:37 +0200 Subject: [PATCH 09/10] Manage PrimaryKeyTrait::ValueType --- sea-orm-macros/src/derives/entity_model.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sea-orm-macros/src/derives/entity_model.rs b/sea-orm-macros/src/derives/entity_model.rs index 82bedd59..10aabd59 100644 --- a/sea-orm-macros/src/derives/entity_model.rs +++ b/sea-orm-macros/src/derives/entity_model.rs @@ -49,6 +49,7 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res let mut columns_enum: Punctuated<_, Comma> = Punctuated::new(); let mut columns_trait: Punctuated<_, Comma> = Punctuated::new(); let mut primary_keys: Punctuated<_, Comma> = Punctuated::new(); + let mut primary_key_types: Punctuated<_, Comma> = Punctuated::new(); let mut auto_increment = true; if let Data::Struct(item_struct) = data { if let Fields::Named(fields) = item_struct.fields { @@ -113,6 +114,7 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res if let Some(name) = p.get_ident() { if name == "primary_key" { primary_keys.push(quote! { #field_name }); + primary_key_types.push(field.ty.clone()); } else if name == "nullable" { nullable = true; @@ -189,6 +191,13 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res let primary_key = (!primary_keys.is_empty()).then(|| { let auto_increment = auto_increment && primary_keys.len() == 1; + let primary_key_types = if primary_key_types.len() == 1 { + let first = primary_key_types.first(); + quote! { #first } + } + else { + quote! { (#primary_key_types) } + }; quote! { #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] pub enum PrimaryKey { @@ -196,6 +205,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = #primary_key_types; + fn auto_increment() -> bool { #auto_increment } From d4a915616aeae808c3dad480b7637a9cfa08ea20 Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Tue, 7 Sep 2021 10:38:42 +0200 Subject: [PATCH 10/10] Fix example --- examples/async-std/src/example_cake.rs | 11 ----------- sea-orm-macros/src/derives/entity_model.rs | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/examples/async-std/src/example_cake.rs b/examples/async-std/src/example_cake.rs index 6820be61..8f704f3b 100644 --- a/examples/async-std/src/example_cake.rs +++ b/examples/async-std/src/example_cake.rs @@ -13,17 +13,6 @@ pub enum Relation { Fruit, } -impl ColumnTrait for Column { - type EntityName = Entity; - - fn def(&self) -> ColumnDef { - match self { - Self::Id => ColumnType::Integer.def(), - Self::Name => ColumnType::String(None).def(), - } - } -} - impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { diff --git a/sea-orm-macros/src/derives/entity_model.rs b/sea-orm-macros/src/derives/entity_model.rs index 10aabd59..13188495 100644 --- a/sea-orm-macros/src/derives/entity_model.rs +++ b/sea-orm-macros/src/derives/entity_model.rs @@ -35,7 +35,7 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res pub struct Entity; impl sea_orm::prelude::EntityName for Entity { - fn schema_name(&self) -> &str { + fn schema_name(&self) -> Option<&str> { #schema_name }