From 8627c8d961f5c53c720b8aa16046d887a0d1c589 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 19 Oct 2021 19:08:02 +0800 Subject: [PATCH 01/39] Draft ActiveEnum --- sea-orm-macros/src/derives/active_enum.rs | 219 +++++++++++++++++++++ sea-orm-macros/src/derives/entity_model.rs | 26 ++- sea-orm-macros/src/derives/mod.rs | 2 + sea-orm-macros/src/lib.rs | 9 + src/entity/active_enum.rs | 184 +++++++++++++++++ src/entity/column.rs | 4 + src/entity/mod.rs | 2 + src/entity/prelude.rs | 13 +- src/lib.rs | 6 +- tests/active_enum_tests.rs | 39 ++++ tests/common/features/active_enum.rs | 87 ++++++++ tests/common/features/mod.rs | 2 + tests/common/features/schema.rs | 22 +++ 13 files changed, 597 insertions(+), 18 deletions(-) create mode 100644 sea-orm-macros/src/derives/active_enum.rs create mode 100644 src/entity/active_enum.rs create mode 100644 tests/active_enum_tests.rs create mode 100644 tests/common/features/active_enum.rs diff --git a/sea-orm-macros/src/derives/active_enum.rs b/sea-orm-macros/src/derives/active_enum.rs new file mode 100644 index 000000000..6f6b7fbc7 --- /dev/null +++ b/sea-orm-macros/src/derives/active_enum.rs @@ -0,0 +1,219 @@ +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::{punctuated::Punctuated, token::Comma, Lit, Meta}; + +enum Error { + InputNotEnum, + Syn(syn::Error), +} + +struct ActiveEnum { + ident: syn::Ident, + rs_type: TokenStream, + db_type: TokenStream, + variants: syn::punctuated::Punctuated, +} + +impl ActiveEnum { + fn new(input: syn::DeriveInput) -> Result { + let ident = input.ident; + + let mut rs_type = None; + let mut db_type = None; + for attr in input.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 == "rs_type" { + if let Lit::Str(litstr) = &nv.lit { + rs_type = syn::parse_str::(&litstr.value()).ok(); + } + } else if name == "db_type" { + if let Lit::Str(litstr) = &nv.lit { + db_type = syn::parse_str::(&litstr.value()).ok(); + } + } + } + } + } + } + } + let rs_type = rs_type.expect("Missing rs_type"); + let db_type = db_type.expect("Missing db_type"); + + let variants = match input.data { + syn::Data::Enum(syn::DataEnum { variants, .. }) => variants, + _ => return Err(Error::InputNotEnum), + }; + + Ok(ActiveEnum { + ident, + rs_type, + db_type, + variants, + }) + } + + fn expand(&self) -> syn::Result { + let expanded_impl_active_enum = self.impl_active_enum(); + + Ok(expanded_impl_active_enum) + } + + fn impl_active_enum(&self) -> TokenStream { + let Self { + ident, + rs_type, + db_type, + variants, + } = self; + + let variant_idents: Vec = variants + .iter() + .map(|variant| variant.ident.clone()) + .collect(); + + let mut is_string = false; + + let variant_values: Vec = variants + .iter() + .map(|variant| { + let mut string_value = None; + let mut num_value = None; + for attr in variant.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 == "string_value" { + if let Lit::Str(litstr) = &nv.lit { + string_value = Some(litstr.value()); + } + } else if name == "num_value" { + if let Lit::Int(litstr) = &nv.lit { + num_value = litstr.base10_parse::().ok(); + } + } + } + } + } + } + } + + if let Some(string_value) = string_value { + is_string = true; + quote! { #string_value } + } else if let Some(num_value) = num_value { + quote! { #num_value } + } else { + panic!("Either string_value or num_value should be specified") + } + }) + .collect(); + + let val = if is_string { + quote! { v.as_ref() } + } else { + quote! { v } + }; + + quote!( + #[automatically_derived] + impl sea_orm::ActiveEnum for #ident { + type Value = #rs_type; + + fn to_value(&self) -> Self::Value { + match self { + #( Self::#variant_idents => #variant_values, )* + } + .to_owned() + } + + fn try_from_value(v: &Self::Value) -> Result { + match #val { + #( #variant_values => Ok(Self::#variant_idents), )* + _ => Err(sea_orm::DbErr::Query(format!( + "unexpected value for {} enum: {}", + stringify!(#ident), + v + ))), + } + } + + fn db_type() -> sea_orm::ColumnDef { + sea_orm::ColumnType::#db_type.def() + } + } + + #[automatically_derived] + impl Into for #ident { + fn into(self) -> sea_query::Value { + ::to_value(&self).into() + } + } + + #[automatically_derived] + impl sea_orm::TryGetable for #ident { + fn try_get(res: &sea_orm::QueryResult, pre: &str, col: &str) -> Result { + let value = <::Value as sea_orm::TryGetable>::try_get(res, pre, col)?; + ::try_from_value(&value).map_err(|e| sea_orm::TryGetError::DbErr(e)) + } + } + + #[automatically_derived] + impl sea_query::ValueType for #ident { + fn try_from(v: sea_query::Value) -> Result { + let value = <::Value as sea_query::ValueType>::try_from(v)?; + ::try_from_value(&value).map_err(|_| sea_query::ValueTypeErr) + } + + fn type_name() -> String { + <::Value as sea_query::ValueType>::type_name() + } + + fn column_type() -> sea_query::ColumnType { + ::db_type() + .get_column_type() + .to_owned() + .into() + } + } + + #[automatically_derived] + impl sea_query::Nullable for #ident { + fn null() -> sea_query::Value { + <::Value as sea_query::Nullable>::null() + } + } + ) + } +} + +pub fn expand_derive_active_enum(input: syn::DeriveInput) -> syn::Result { + let ident_span = input.ident.span(); + + match ActiveEnum::new(input) { + Ok(model) => model.expand(), + Err(Error::InputNotEnum) => Ok(quote_spanned! { + ident_span => compile_error!("you can only derive ActiveEnum on enums"); + }), + Err(Error::Syn(err)) => Err(err), + } +} diff --git a/sea-orm-macros/src/derives/entity_model.rs b/sea-orm-macros/src/derives/entity_model.rs index 3b0dac260..0963c2caa 100644 --- a/sea-orm-macros/src/derives/entity_model.rs +++ b/sea-orm-macros/src/derives/entity_model.rs @@ -1,7 +1,7 @@ use crate::util::{escape_rust_keyword, trim_starting_raw_identifier}; use convert_case::{Case, Casing}; use proc_macro2::{Ident, Span, TokenStream}; -use quote::quote; +use quote::{format_ident, quote, quote_spanned}; use syn::{ parse::Error, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, Fields, Lit, Meta, @@ -192,8 +192,8 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res primary_keys.push(quote! { #field_name }); } - let field_type = match sql_type { - Some(t) => t, + let col_type = match sql_type { + Some(t) => quote! { sea_orm::prelude::ColumnType::#t.def() }, None => { let field_type = &field.ty; let temp = quote! { #field_type } @@ -205,7 +205,7 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res } else { temp.as_str() }; - match temp { + let col_type = match temp { "char" => quote! { Char(None) }, "String" | "&str" => quote! { String(None) }, "u8" | "i8" => quote! { TinyInteger }, @@ -228,16 +228,24 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec) -> syn::Res "Decimal" => quote! { Decimal(None) }, "Vec" => quote! { Binary }, _ => { - return Err(Error::new( - field.span(), - format!("unrecognized type {}", temp), - )) + // Assumed it's ActiveEnum if none of the above type matches + quote! {} } + }; + if col_type.is_empty() { + let field_span = field.span(); + let ty = format_ident!("{}", temp); + let def = quote_spanned! { field_span => { + <#ty as ActiveEnum>::db_type() + }}; + quote! { #def } + } else { + quote! { sea_orm::prelude::ColumnType::#col_type.def() } } } }; - let mut match_row = quote! { Self::#field_name => sea_orm::prelude::ColumnType::#field_type.def() }; + let mut match_row = quote! { Self::#field_name => #col_type }; if nullable { match_row = quote! { #match_row.nullable() }; } diff --git a/sea-orm-macros/src/derives/mod.rs b/sea-orm-macros/src/derives/mod.rs index 6ba19a928..36b9f6698 100644 --- a/sea-orm-macros/src/derives/mod.rs +++ b/sea-orm-macros/src/derives/mod.rs @@ -1,3 +1,4 @@ +mod active_enum; mod active_model; mod active_model_behavior; mod column; @@ -9,6 +10,7 @@ mod model; mod primary_key; mod relation; +pub use active_enum::*; pub use active_model::*; pub use active_model_behavior::*; pub use column::*; diff --git a/sea-orm-macros/src/lib.rs b/sea-orm-macros/src/lib.rs index cf8c2f3c8..ccba2fabf 100644 --- a/sea-orm-macros/src/lib.rs +++ b/sea-orm-macros/src/lib.rs @@ -102,6 +102,15 @@ pub fn derive_active_model_behavior(input: TokenStream) -> TokenStream { } } +#[proc_macro_derive(DeriveActiveEnum, attributes(sea_orm))] +pub fn derive_active_enum(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + match derives::expand_derive_active_enum(input) { + Ok(ts) => ts.into(), + Err(e) => e.to_compile_error().into(), + } +} + #[proc_macro_derive(FromQueryResult)] pub fn derive_from_query_result(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs new file mode 100644 index 000000000..68c58c64d --- /dev/null +++ b/src/entity/active_enum.rs @@ -0,0 +1,184 @@ +use crate::{ColumnDef, DbErr, TryGetable}; +use sea_query::{Nullable, Value, ValueType}; +use std::fmt::Debug; + +pub trait ActiveEnum: Sized { + type Value: Sized + Send + Debug + PartialEq + Into + ValueType + Nullable + TryGetable; + + fn to_value(&self) -> Self::Value; + + fn try_from_value(v: &Self::Value) -> Result; + + fn db_type() -> ColumnDef; +} + +#[cfg(test)] +mod tests { + use crate as sea_orm; + use crate::{entity::prelude::*, *}; + use pretty_assertions::assert_eq; + + #[test] + fn active_enum_1() { + #[derive(Debug, PartialEq)] + pub enum Category { + Big, + Small, + } + + impl ActiveEnum for Category { + type Value = String; + + fn to_value(&self) -> Self::Value { + match self { + Self::Big => "B", + Self::Small => "S", + } + .to_owned() + } + + fn try_from_value(v: &Self::Value) -> Result { + match v.as_ref() { + "B" => Ok(Self::Big), + "S" => Ok(Self::Small), + _ => Err(DbErr::Query(format!( + "unexpected value for Category enum: {}", + v + ))), + } + } + + fn db_type() -> ColumnDef { + ColumnType::String(Some(1)).def() + } + } + + #[derive(Debug, PartialEq, DeriveActiveEnum)] + #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] + pub enum DeriveCategory { + #[sea_orm(string_value = "B")] + Big, + #[sea_orm(string_value = "S")] + Small, + } + + assert_eq!(Category::Big.to_value(), "B".to_owned()); + assert_eq!(Category::Small.to_value(), "S".to_owned()); + assert_eq!(DeriveCategory::Big.to_value(), "B".to_owned()); + assert_eq!(DeriveCategory::Small.to_value(), "S".to_owned()); + + assert_eq!( + Category::try_from_value(&"A".to_owned()).err(), + Some(DbErr::Query( + "unexpected value for Category enum: A".to_owned() + )) + ); + assert_eq!( + Category::try_from_value(&"B".to_owned()).ok(), + Some(Category::Big) + ); + assert_eq!( + Category::try_from_value(&"S".to_owned()).ok(), + Some(Category::Small) + ); + assert_eq!( + DeriveCategory::try_from_value(&"A".to_owned()).err(), + Some(DbErr::Query( + "unexpected value for DeriveCategory enum: A".to_owned() + )) + ); + assert_eq!( + DeriveCategory::try_from_value(&"B".to_owned()).ok(), + Some(DeriveCategory::Big) + ); + assert_eq!( + DeriveCategory::try_from_value(&"S".to_owned()).ok(), + Some(DeriveCategory::Small) + ); + + assert_eq!(Category::db_type(), ColumnType::String(Some(1)).def()); + assert_eq!(DeriveCategory::db_type(), ColumnType::String(Some(1)).def()); + } + + #[test] + fn active_enum_2() { + #[derive(Debug, PartialEq)] + pub enum Category { + Big, + Small, + } + + impl ActiveEnum for Category { + type Value = i32; // FIXME: only support i32 for now + + fn to_value(&self) -> Self::Value { + match self { + Self::Big => 1, + Self::Small => 0, + } + .to_owned() + } + + fn try_from_value(v: &Self::Value) -> Result { + match v { + 1 => Ok(Self::Big), + 0 => Ok(Self::Small), + _ => Err(DbErr::Query(format!( + "unexpected value for Category enum: {}", + v + ))), + } + } + + fn db_type() -> ColumnDef { + ColumnType::Integer.def() + } + } + + #[derive(Debug, PartialEq, DeriveActiveEnum)] + #[sea_orm(rs_type = "i32", db_type = "Integer")] + pub enum DeriveCategory { + #[sea_orm(num_value = 1)] + Big, + #[sea_orm(num_value = 0)] + Small, + } + + assert_eq!(Category::Big.to_value(), 1); + assert_eq!(Category::Small.to_value(), 0); + assert_eq!(DeriveCategory::Big.to_value(), 1); + assert_eq!(DeriveCategory::Small.to_value(), 0); + + assert_eq!( + Category::try_from_value(&2).err(), + Some(DbErr::Query( + "unexpected value for Category enum: 2".to_owned() + )) + ); + assert_eq!( + Category::try_from_value(&1).ok(), + Some(Category::Big) + ); + assert_eq!( + Category::try_from_value(&0).ok(), + Some(Category::Small) + ); + assert_eq!( + DeriveCategory::try_from_value(&2).err(), + Some(DbErr::Query( + "unexpected value for DeriveCategory enum: 2".to_owned() + )) + ); + assert_eq!( + DeriveCategory::try_from_value(&1).ok(), + Some(DeriveCategory::Big) + ); + assert_eq!( + DeriveCategory::try_from_value(&0).ok(), + Some(DeriveCategory::Small) + ); + + assert_eq!(Category::db_type(), ColumnType::Integer.def()); + assert_eq!(DeriveCategory::db_type(), ColumnType::Integer.def()); + } +} diff --git a/src/entity/column.rs b/src/entity/column.rs index a27215e5b..25ed84473 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -262,6 +262,10 @@ impl ColumnDef { self.indexed = true; self } + + pub fn get_column_type(&self) -> &ColumnType { + &self.col_type + } } impl From for sea_query::ColumnType { diff --git a/src/entity/mod.rs b/src/entity/mod.rs index c6d150528..7e8b78301 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -1,3 +1,4 @@ +mod active_enum; mod active_model; mod base_entity; mod column; @@ -8,6 +9,7 @@ pub mod prelude; mod primary_key; mod relation; +pub use active_enum::*; pub use active_model::*; pub use base_entity::*; pub use column::*; diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs index ac4d50fad..98f89c921 100644 --- a/src/entity/prelude.rs +++ b/src/entity/prelude.rs @@ -1,14 +1,15 @@ pub use crate::{ - error::*, ActiveModelBehavior, ActiveModelTrait, ColumnDef, ColumnTrait, ColumnType, - DatabaseConnection, DbConn, EntityName, EntityTrait, EnumIter, ForeignKeyAction, Iden, - IdenStatic, Linked, ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult, - Related, RelationDef, RelationTrait, Select, Value, + error::*, ActiveEnum, ActiveModelBehavior, ActiveModelTrait, ColumnDef, ColumnTrait, + ColumnType, DatabaseConnection, DbConn, EntityName, EntityTrait, EnumIter, ForeignKeyAction, + Iden, IdenStatic, Linked, ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, + QueryResult, Related, RelationDef, RelationTrait, Select, Value, }; #[cfg(feature = "macros")] pub use crate::{ - DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveCustomColumn, DeriveEntity, - DeriveEntityModel, DeriveIntoActiveModel, DeriveModel, DerivePrimaryKey, DeriveRelation, + DeriveActiveEnum, DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, + DeriveCustomColumn, DeriveEntity, DeriveEntityModel, DeriveIntoActiveModel, DeriveModel, + DerivePrimaryKey, DeriveRelation, }; #[cfg(feature = "with-json")] diff --git a/src/lib.rs b/src/lib.rs index 745692b92..d40db473a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -288,9 +288,9 @@ pub use schema::*; #[cfg(feature = "macros")] pub use sea_orm_macros::{ - DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveCustomColumn, DeriveEntity, - DeriveEntityModel, DeriveIntoActiveModel, DeriveModel, DerivePrimaryKey, DeriveRelation, - FromQueryResult, + DeriveActiveEnum, DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, + DeriveCustomColumn, DeriveEntity, DeriveEntityModel, DeriveIntoActiveModel, DeriveModel, + DerivePrimaryKey, DeriveRelation, FromQueryResult, }; pub use sea_query; diff --git a/tests/active_enum_tests.rs b/tests/active_enum_tests.rs new file mode 100644 index 000000000..90472fde4 --- /dev/null +++ b/tests/active_enum_tests.rs @@ -0,0 +1,39 @@ +pub mod common; + +pub use common::{features::*, setup::*, TestContext}; +use sea_orm::{entity::prelude::*, entity::*, DatabaseConnection}; + +#[sea_orm_macros::test] +#[cfg(any( + feature = "sqlx-mysql", + feature = "sqlx-sqlite", + feature = "sqlx-postgres" +))] +async fn main() -> Result<(), DbErr> { + let ctx = TestContext::new("active_enum_tests").await; + create_tables(&ctx.db).await?; + insert_active_enum(&ctx.db).await?; + ctx.delete().await; + + Ok(()) +} + +pub async fn insert_active_enum(db: &DatabaseConnection) -> Result<(), DbErr> { + active_enum::ActiveModel { + category: Set(active_enum::Category::Big), + ..Default::default() + } + .insert(db) + .await?; + + assert_eq!( + active_enum::Entity::find().one(db).await?.unwrap(), + active_enum::Model { + id: 1, + category: active_enum::Category::Big, + category_opt: None, + } + ); + + Ok(()) +} diff --git a/tests/common/features/active_enum.rs b/tests/common/features/active_enum.rs new file mode 100644 index 000000000..1b68d546b --- /dev/null +++ b/tests/common/features/active_enum.rs @@ -0,0 +1,87 @@ +use sea_orm::{entity::prelude::*, TryGetError, TryGetable}; +use sea_query::{Nullable, ValueType, ValueTypeErr}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +#[sea_orm(table_name = "active_enum")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub category: Category, + pub category_opt: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Debug, Clone, PartialEq)] +pub enum Category { + Big, + Small, +} + +impl ActiveEnum for Category { + type Value = String; + + fn to_value(&self) -> Self::Value { + match self { + Self::Big => "B", + Self::Small => "S", + } + .to_owned() + } + + fn try_from_value(v: &Self::Value) -> Result { + match v.as_ref() { + "B" => Ok(Self::Big), + "S" => Ok(Self::Small), + _ => Err(DbErr::Query(format!( + "unexpected value for {} enum: {}", + stringify!(Category), + v + ))), + } + } + + fn db_type() -> ColumnDef { + ColumnType::String(Some(1)).def() + } +} + +impl Into for Category { + fn into(self) -> Value { + self.to_value().into() + } +} + +impl TryGetable for Category { + fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { + let value = <::Value as TryGetable>::try_get(res, pre, col)?; + Self::try_from_value(&value).map_err(|e| TryGetError::DbErr(e)) + } +} + +impl ValueType for Category { + fn try_from(v: Value) -> Result { + let value = <::Value as ValueType>::try_from(v)?; + Self::try_from_value(&value).map_err(|_| ValueTypeErr) + } + + fn type_name() -> String { + <::Value as ValueType>::type_name() + } + + fn column_type() -> sea_query::ColumnType { + ::db_type() + .get_column_type() + .to_owned() + .into() + } +} + +impl Nullable for Category { + fn null() -> Value { + <::Value as Nullable>::null() + } +} diff --git a/tests/common/features/mod.rs b/tests/common/features/mod.rs index ff716f010..f0db35b7e 100644 --- a/tests/common/features/mod.rs +++ b/tests/common/features/mod.rs @@ -1,8 +1,10 @@ +pub mod active_enum; pub mod applog; pub mod metadata; pub mod repository; pub mod schema; +pub use active_enum::Entity as ActiveEnum; pub use applog::Entity as Applog; pub use metadata::Entity as Metadata; pub use repository::Entity as Repository; diff --git a/tests/common/features/schema.rs b/tests/common/features/schema.rs index 44b011c5c..5292f4fc5 100644 --- a/tests/common/features/schema.rs +++ b/tests/common/features/schema.rs @@ -9,6 +9,7 @@ pub async fn create_tables(db: &DatabaseConnection) -> Result<(), DbErr> { create_log_table(db).await?; create_metadata_table(db).await?; create_repository_table(db).await?; + create_active_enum_table(db).await?; Ok(()) } @@ -75,3 +76,24 @@ pub async fn create_repository_table(db: &DbConn) -> Result { create_table(db, &stmt, Repository).await } + +pub async fn create_active_enum_table(db: &DbConn) -> Result { + let stmt = sea_query::Table::create() + .table(active_enum::Entity) + .col( + ColumnDef::new(active_enum::Column::Id) + .integer() + .not_null() + .primary_key() + .auto_increment(), + ) + .col( + ColumnDef::new(active_enum::Column::Category) + .string_len(1) + .not_null(), + ) + .col(ColumnDef::new(active_enum::Column::CategoryOpt).string_len(1)) + .to_owned(); + + create_table(db, &stmt, ActiveEnum).await +} From 18f37150d7d104e8810fedac848903f8060b08d6 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 20 Oct 2021 10:49:26 +0800 Subject: [PATCH 02/39] Fixup --- tests/common/features/schema.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/common/features/schema.rs b/tests/common/features/schema.rs index 5292f4fc5..70752d35e 100644 --- a/tests/common/features/schema.rs +++ b/tests/common/features/schema.rs @@ -84,8 +84,8 @@ pub async fn create_active_enum_table(db: &DbConn) -> Result ColumnDef::new(active_enum::Column::Id) .integer() .not_null() - .primary_key() - .auto_increment(), + .auto_increment() + .primary_key(), ) .col( ColumnDef::new(active_enum::Column::Category) From 30e17a26d9168cab5f35769a14fd1f9250caeef1 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 20 Oct 2021 12:17:10 +0800 Subject: [PATCH 03/39] Better error messages --- sea-orm-macros/src/derives/active_enum.rs | 143 ++++++++++++++-------- 1 file changed, 93 insertions(+), 50 deletions(-) diff --git a/sea-orm-macros/src/derives/active_enum.rs b/sea-orm-macros/src/derives/active_enum.rs index 6f6b7fbc7..cd2e1c0a9 100644 --- a/sea-orm-macros/src/derives/active_enum.rs +++ b/sea-orm-macros/src/derives/active_enum.rs @@ -1,25 +1,38 @@ use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; -use syn::{punctuated::Punctuated, token::Comma, Lit, Meta}; +use syn::{punctuated::Punctuated, token::Comma, Lit, LitInt, LitStr, Meta}; enum Error { InputNotEnum, Syn(syn::Error), + TT(TokenStream), } struct ActiveEnum { ident: syn::Ident, rs_type: TokenStream, db_type: TokenStream, - variants: syn::punctuated::Punctuated, + is_string: bool, + variants: Vec, +} + +struct ActiveEnumVariant { + ident: syn::Ident, + string_value: Option, + num_value: Option, } impl ActiveEnum { fn new(input: syn::DeriveInput) -> Result { + let ident_span = input.ident.span(); let ident = input.ident; - let mut rs_type = None; - let mut db_type = None; + let mut rs_type = Err(Error::TT(quote_spanned! { + ident_span => compile_error!("Missing macro attribute `rs_type`"); + })); + let mut db_type = Err(Error::TT(quote_spanned! { + ident_span => compile_error!("Missing macro attribute `db_type`"); + })); for attr in input.attrs.iter() { if let Some(ident) = attr.path.get_ident() { if ident != "sea_orm" { @@ -34,11 +47,13 @@ impl ActiveEnum { if let Some(name) = nv.path.get_ident() { if name == "rs_type" { if let Lit::Str(litstr) = &nv.lit { - rs_type = syn::parse_str::(&litstr.value()).ok(); + rs_type = syn::parse_str::(&litstr.value()) + .map_err(Error::Syn); } } else if name == "db_type" { if let Lit::Str(litstr) = &nv.lit { - db_type = syn::parse_str::(&litstr.value()).ok(); + db_type = syn::parse_str::(&litstr.value()) + .map_err(Error::Syn); } } } @@ -46,18 +61,73 @@ impl ActiveEnum { } } } - let rs_type = rs_type.expect("Missing rs_type"); - let db_type = db_type.expect("Missing db_type"); - let variants = match input.data { + let variant_vec = match input.data { syn::Data::Enum(syn::DataEnum { variants, .. }) => variants, _ => return Err(Error::InputNotEnum), }; + let mut is_string = false; + let mut is_int = false; + let mut variants = Vec::new(); + for variant in variant_vec { + let variant_span = variant.ident.span(); + let mut string_value = None; + let mut num_value = None; + for attr in variant.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 { + if let Meta::NameValue(nv) = meta { + if let Some(name) = nv.path.get_ident() { + if name == "string_value" { + if let Lit::Str(lit) = nv.lit { + is_string = true; + string_value = Some(lit); + } + } else if name == "num_value" { + if let Lit::Int(lit) = nv.lit { + is_int = true; + num_value = Some(lit); + } + } + } + } + } + } + } + + if is_string && is_int { + return Err(Error::TT(quote_spanned! { + ident_span => compile_error!("All enum variants should specify the same `*_value` macro attribute, either `string_value` or `num_value` but not both"); + })); + } + + if string_value.is_none() && num_value.is_none() { + return Err(Error::TT(quote_spanned! { + variant_span => compile_error!("Missing macro attribute, either `string_value` or `num_value` should be specified"); + })); + } + + variants.push(ActiveEnumVariant { + ident: variant.ident, + string_value, + num_value, + }); + } + Ok(ActiveEnum { ident, - rs_type, - db_type, + rs_type: rs_type?, + db_type: db_type?, + is_string, variants, }) } @@ -73,6 +143,7 @@ impl ActiveEnum { ident, rs_type, db_type, + is_string, variants, } = self; @@ -81,54 +152,25 @@ impl ActiveEnum { .map(|variant| variant.ident.clone()) .collect(); - let mut is_string = false; - let variant_values: Vec = variants .iter() .map(|variant| { - let mut string_value = None; - let mut num_value = None; - for attr in variant.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 == "string_value" { - if let Lit::Str(litstr) = &nv.lit { - string_value = Some(litstr.value()); - } - } else if name == "num_value" { - if let Lit::Int(litstr) = &nv.lit { - num_value = litstr.base10_parse::().ok(); - } - } - } - } - } - } - } + let variant_span = variant.ident.span(); - if let Some(string_value) = string_value { - is_string = true; - quote! { #string_value } - } else if let Some(num_value) = num_value { + if let Some(string_value) = &variant.string_value { + let string = string_value.value(); + quote! { #string } + } else if let Some(num_value) = &variant.num_value { quote! { #num_value } } else { - panic!("Either string_value or num_value should be specified") + quote_spanned! { + variant_span => compile_error!("Missing macro attribute, either `string_value` or `num_value` should be specified"); + } } }) .collect(); - let val = if is_string { + let val = if *is_string { quote! { v.as_ref() } } else { quote! { v } @@ -214,6 +256,7 @@ pub fn expand_derive_active_enum(input: syn::DeriveInput) -> syn::Result Ok(quote_spanned! { ident_span => compile_error!("you can only derive ActiveEnum on enums"); }), - Err(Error::Syn(err)) => Err(err), + Err(Error::TT(token_stream)) => Ok(token_stream), + Err(Error::Syn(e)) => Err(e), } } From e177a338c4e2c9d2670044c5a84319a534c8c8df Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 20 Oct 2021 12:17:30 +0800 Subject: [PATCH 04/39] Minimal trait bounds --- src/entity/active_enum.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs index 68c58c64d..7e17000ff 100644 --- a/src/entity/active_enum.rs +++ b/src/entity/active_enum.rs @@ -1,9 +1,8 @@ use crate::{ColumnDef, DbErr, TryGetable}; use sea_query::{Nullable, Value, ValueType}; -use std::fmt::Debug; pub trait ActiveEnum: Sized { - type Value: Sized + Send + Debug + PartialEq + Into + ValueType + Nullable + TryGetable; + type Value: Into + ValueType + Nullable + TryGetable; fn to_value(&self) -> Self::Value; From 5b0720065f842128071173ae7cacf6e9a2076ebf Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 20 Oct 2021 12:18:08 +0800 Subject: [PATCH 05/39] More tests --- src/entity/active_enum.rs | 140 ++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 73 deletions(-) diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs index 7e17000ff..8c99e94c0 100644 --- a/src/entity/active_enum.rs +++ b/src/entity/active_enum.rs @@ -18,7 +18,7 @@ mod tests { use pretty_assertions::assert_eq; #[test] - fn active_enum_1() { + fn active_enum_string() { #[derive(Debug, PartialEq)] pub enum Category { Big, @@ -100,84 +100,78 @@ mod tests { } #[test] - fn active_enum_2() { - #[derive(Debug, PartialEq)] - pub enum Category { - Big, - Small, - } - - impl ActiveEnum for Category { - type Value = i32; // FIXME: only support i32 for now - - fn to_value(&self) -> Self::Value { - match self { - Self::Big => 1, - Self::Small => 0, - } - .to_owned() - } - - fn try_from_value(v: &Self::Value) -> Result { - match v { - 1 => Ok(Self::Big), - 0 => Ok(Self::Small), - _ => Err(DbErr::Query(format!( - "unexpected value for Category enum: {}", - v - ))), + fn active_enum_derive_signed_integers() { + macro_rules! test_int { + ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => { + #[derive(Debug, PartialEq, DeriveActiveEnum)] + #[sea_orm(rs_type = $rs_type, db_type = $db_type)] + pub enum $ident { + #[sea_orm(num_value = 1)] + Big, + #[sea_orm(num_value = 0)] + Small, + #[sea_orm(num_value = -10)] + Negative, } - } - fn db_type() -> ColumnDef { - ColumnType::Integer.def() - } + assert_eq!($ident::Big.to_value(), 1); + assert_eq!($ident::Small.to_value(), 0); + assert_eq!($ident::Negative.to_value(), -10); + + assert_eq!($ident::try_from_value(&1).ok(), Some($ident::Big)); + assert_eq!($ident::try_from_value(&0).ok(), Some($ident::Small)); + assert_eq!($ident::try_from_value(&-10).ok(), Some($ident::Negative)); + assert_eq!( + $ident::try_from_value(&2).err(), + Some(DbErr::Query(format!( + "unexpected value for {} enum: 2", + stringify!($ident) + ))) + ); + + assert_eq!($ident::db_type(), ColumnType::$col_def.def()); + }; } - #[derive(Debug, PartialEq, DeriveActiveEnum)] - #[sea_orm(rs_type = "i32", db_type = "Integer")] - pub enum DeriveCategory { - #[sea_orm(num_value = 1)] - Big, - #[sea_orm(num_value = 0)] - Small, - } + test_int!(I8, "i8", "TinyInteger", TinyInteger); + test_int!(I16, "i16", "SmallInteger", SmallInteger); + test_int!(I32, "i32", "Integer", Integer); + test_int!(I64, "i64", "BigInteger", BigInteger); + } - assert_eq!(Category::Big.to_value(), 1); - assert_eq!(Category::Small.to_value(), 0); - assert_eq!(DeriveCategory::Big.to_value(), 1); - assert_eq!(DeriveCategory::Small.to_value(), 0); + #[test] + fn active_enum_derive_unsigned_integers() { + macro_rules! test_uint { + ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => { + #[derive(Debug, PartialEq, DeriveActiveEnum)] + #[sea_orm(rs_type = $rs_type, db_type = $db_type)] + pub enum $ident { + #[sea_orm(num_value = 1)] + Big, + #[sea_orm(num_value = 0)] + Small, + } - assert_eq!( - Category::try_from_value(&2).err(), - Some(DbErr::Query( - "unexpected value for Category enum: 2".to_owned() - )) - ); - assert_eq!( - Category::try_from_value(&1).ok(), - Some(Category::Big) - ); - assert_eq!( - Category::try_from_value(&0).ok(), - Some(Category::Small) - ); - assert_eq!( - DeriveCategory::try_from_value(&2).err(), - Some(DbErr::Query( - "unexpected value for DeriveCategory enum: 2".to_owned() - )) - ); - assert_eq!( - DeriveCategory::try_from_value(&1).ok(), - Some(DeriveCategory::Big) - ); - assert_eq!( - DeriveCategory::try_from_value(&0).ok(), - Some(DeriveCategory::Small) - ); + assert_eq!($ident::Big.to_value(), 1); + assert_eq!($ident::Small.to_value(), 0); + + assert_eq!($ident::try_from_value(&1).ok(), Some($ident::Big)); + assert_eq!($ident::try_from_value(&0).ok(), Some($ident::Small)); + assert_eq!( + $ident::try_from_value(&2).err(), + Some(DbErr::Query(format!( + "unexpected value for {} enum: 2", + stringify!($ident) + ))) + ); + + assert_eq!($ident::db_type(), ColumnType::$col_def.def()); + }; + } - assert_eq!(Category::db_type(), ColumnType::Integer.def()); - assert_eq!(DeriveCategory::db_type(), ColumnType::Integer.def()); + test_uint!(U8, "u8", "TinyInteger", TinyInteger); + test_uint!(U16, "u16", "SmallInteger", SmallInteger); + test_uint!(U32, "u32", "Integer", Integer); + test_uint!(U64, "u64", "BigInteger", BigInteger); } } From b1e10aec861e3a6588522b9d13d4fe28022bc8fc Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 20 Oct 2021 12:23:55 +0800 Subject: [PATCH 06/39] Refactoring --- tests/common/features/active_enum.rs | 73 ++-------------------------- 1 file changed, 5 insertions(+), 68 deletions(-) diff --git a/tests/common/features/active_enum.rs b/tests/common/features/active_enum.rs index 1b68d546b..e1aef0fed 100644 --- a/tests/common/features/active_enum.rs +++ b/tests/common/features/active_enum.rs @@ -1,5 +1,4 @@ -use sea_orm::{entity::prelude::*, TryGetError, TryGetable}; -use sea_query::{Nullable, ValueType, ValueTypeErr}; +use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "active_enum")] @@ -15,73 +14,11 @@ pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(Some(1))")] pub enum Category { + #[sea_orm(string_value = "B")] Big, + #[sea_orm(string_value = "S")] Small, } - -impl ActiveEnum for Category { - type Value = String; - - fn to_value(&self) -> Self::Value { - match self { - Self::Big => "B", - Self::Small => "S", - } - .to_owned() - } - - fn try_from_value(v: &Self::Value) -> Result { - match v.as_ref() { - "B" => Ok(Self::Big), - "S" => Ok(Self::Small), - _ => Err(DbErr::Query(format!( - "unexpected value for {} enum: {}", - stringify!(Category), - v - ))), - } - } - - fn db_type() -> ColumnDef { - ColumnType::String(Some(1)).def() - } -} - -impl Into for Category { - fn into(self) -> Value { - self.to_value().into() - } -} - -impl TryGetable for Category { - fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { - let value = <::Value as TryGetable>::try_get(res, pre, col)?; - Self::try_from_value(&value).map_err(|e| TryGetError::DbErr(e)) - } -} - -impl ValueType for Category { - fn try_from(v: Value) -> Result { - let value = <::Value as ValueType>::try_from(v)?; - Self::try_from_value(&value).map_err(|_| ValueTypeErr) - } - - fn type_name() -> String { - <::Value as ValueType>::type_name() - } - - fn column_type() -> sea_query::ColumnType { - ::db_type() - .get_column_type() - .to_owned() - .into() - } -} - -impl Nullable for Category { - fn null() -> Value { - <::Value as Nullable>::null() - } -} From bf1663506aa2349c756f3714feb7beba84de6a85 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 20 Oct 2021 12:25:26 +0800 Subject: [PATCH 07/39] Fix clippy warnings --- sea-orm-macros/src/derives/active_enum.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sea-orm-macros/src/derives/active_enum.rs b/sea-orm-macros/src/derives/active_enum.rs index cd2e1c0a9..6823405d5 100644 --- a/sea-orm-macros/src/derives/active_enum.rs +++ b/sea-orm-macros/src/derives/active_enum.rs @@ -205,6 +205,7 @@ impl ActiveEnum { } #[automatically_derived] + #[allow(clippy::from_over_into)] impl Into for #ident { fn into(self) -> sea_query::Value { ::to_value(&self).into() @@ -215,7 +216,7 @@ impl ActiveEnum { impl sea_orm::TryGetable for #ident { fn try_get(res: &sea_orm::QueryResult, pre: &str, col: &str) -> Result { let value = <::Value as sea_orm::TryGetable>::try_get(res, pre, col)?; - ::try_from_value(&value).map_err(|e| sea_orm::TryGetError::DbErr(e)) + ::try_from_value(&value).map_err(sea_orm::TryGetError::DbErr) } } From ceba3ef7a03624d917182ef0bc6de2d80c7f1174 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 20 Oct 2021 13:16:22 +0800 Subject: [PATCH 08/39] Add docs --- src/entity/active_enum.rs | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs index 8c99e94c0..b41779ebf 100644 --- a/src/entity/active_enum.rs +++ b/src/entity/active_enum.rs @@ -1,13 +1,53 @@ use crate::{ColumnDef, DbErr, TryGetable}; use sea_query::{Nullable, Value, ValueType}; +/// A Rust representation of enum defined in database. +/// +/// # Implementations +/// +/// You can implement [ActiveEnum] manually by hand or use the derive macro [DeriveActiveEnum](sea_orm_macros::DeriveActiveEnum). +/// +/// # Examples +/// +/// ``` +/// use sea_orm::entity::prelude::*; +/// +/// // Define the `Category` active enum +/// #[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] +/// #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] +/// pub enum Category { +/// #[sea_orm(string_value = "B")] +/// Big, +/// #[sea_orm(string_value = "S")] +/// Small, +/// } +/// +/// #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +/// #[sea_orm(table_name = "active_enum")] +/// pub struct Model { +/// #[sea_orm(primary_key)] +/// pub id: i32, +/// // Represents a db column using `Category` active enum +/// pub category: Category, +/// pub category_opt: Option, +/// } +/// +/// #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +/// pub enum Relation {} +/// +/// impl ActiveModelBehavior for ActiveModel {} +/// ``` pub trait ActiveEnum: Sized { + /// Define the Rust type that each enum variant represents. type Value: Into + ValueType + Nullable + TryGetable; + /// Convert enum variant into the corresponding value. fn to_value(&self) -> Self::Value; + /// Try to convert the corresponding value into enum variant. fn try_from_value(v: &Self::Value) -> Result; + /// Get the database column definition of this active enum. fn db_type() -> ColumnDef; } From eed8b7c51ea2cdb64d12342691714f77612497aa Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 20 Oct 2021 15:06:23 +0800 Subject: [PATCH 09/39] Add docs --- sea-orm-macros/src/lib.rs | 23 ++++++++++++++++ src/entity/active_enum.rs | 56 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/sea-orm-macros/src/lib.rs b/sea-orm-macros/src/lib.rs index ccba2fabf..f8503756d 100644 --- a/sea-orm-macros/src/lib.rs +++ b/sea-orm-macros/src/lib.rs @@ -102,6 +102,29 @@ pub fn derive_active_model_behavior(input: TokenStream) -> TokenStream { } } +/// A derive macro to implement `sea_orm::ActiveEnum` trait for enums. +/// +/// # Limitations +/// +/// This derive macros can only be used on enums. +/// +/// # Macro Attributes +/// +/// All macro attributes listed below have to be annotated in the form of `#[sea_orm(attr = value)]`. +/// +/// - For enum +/// - `rs_type`: Define `ActiveEnum::Value` +/// - Possible values: `String`, `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64` +/// - Note that value has to be passed as string, i.e. `rs_type = "i8"` +/// - `db_type`: Define `ColumnType` returned by `ActiveEnum::db_type()` +/// - Possible values: all available enum variants of `ColumnType`, e.g. `String(None)`, `String(Some(1))`, `Integer` +/// - Note that value has to be passed as string, i.e. `db_type = "Integer"` +/// +/// - For enum variant +/// - `string_value` or `num_value`: +/// - For `string_value`, value should be passed as string, i.e. `string_value = "A"` +/// - For `num_value`, value should be passed as integer, i.e. `num_value = 1` or `num_value = 1i32` +/// - Note that only one of it can be specified, and all variants of an enum have to annotate with the same `*_value` macro attribute #[proc_macro_derive(DeriveActiveEnum, attributes(sea_orm))] pub fn derive_active_enum(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs index b41779ebf..c0453dda1 100644 --- a/src/entity/active_enum.rs +++ b/src/entity/active_enum.rs @@ -9,6 +9,62 @@ use sea_query::{Nullable, Value, ValueType}; /// /// # Examples /// +/// Implementing it manually versus using the derive macro [DeriveActiveEnum](sea_orm_macros::DeriveActiveEnum). +/// +/// > See [DeriveActiveEnum](sea_orm_macros::DeriveActiveEnum) for the full specification of macro attributes. +/// +/// ```rust +/// // Using the derive macro +/// #[derive(Debug, PartialEq, DeriveActiveEnum)] +/// #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] +/// pub enum DeriveCategory { +/// #[sea_orm(string_value = "B")] +/// Big, +/// #[sea_orm(string_value = "S")] +/// Small, +/// } +/// +/// // Implementing it manually +/// #[derive(Debug, PartialEq)] +/// pub enum Category { +/// Big, +/// Small, +/// } +/// +/// impl ActiveEnum for Category { +/// // The macro attribute `rs_type` is being pasted here +/// type Value = String; +/// +/// // Will be atomically generated by `DeriveActiveEnum` +/// fn to_value(&self) -> Self::Value { +/// match self { +/// Self::Big => "B", +/// Self::Small => "S", +/// } +/// .to_owned() +/// } +/// +/// // Will be atomically generated by `DeriveActiveEnum` +/// fn try_from_value(v: &Self::Value) -> Result { +/// match v.as_ref() { +/// "B" => Ok(Self::Big), +/// "S" => Ok(Self::Small), +/// _ => Err(DbErr::Query(format!( +/// "unexpected value for Category enum: {}", +/// v +/// ))), +/// } +/// } +/// +/// fn db_type() -> ColumnDef { +/// // The macro attribute `db_type` is being pasted here +/// ColumnType::String(Some(1)).def() +/// } +/// } +/// ``` +/// +/// Using [ActiveEnum] on Model. +/// /// ``` /// use sea_orm::entity::prelude::*; /// From 388bf2cfca054164877d2e68cf1d506e37f62432 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 20 Oct 2021 15:17:11 +0800 Subject: [PATCH 10/39] Fixup --- src/entity/active_enum.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs index c0453dda1..31a65db7a 100644 --- a/src/entity/active_enum.rs +++ b/src/entity/active_enum.rs @@ -14,6 +14,8 @@ use sea_query::{Nullable, Value, ValueType}; /// > See [DeriveActiveEnum](sea_orm_macros::DeriveActiveEnum) for the full specification of macro attributes. /// /// ```rust +/// use sea_orm::entity::prelude::*; +/// /// // Using the derive macro /// #[derive(Debug, PartialEq, DeriveActiveEnum)] /// #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] From f1ef7d9c47d459fd31e103851f27106b8449d46d Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 20 Oct 2021 16:41:01 +0800 Subject: [PATCH 11/39] Add `DbErr::Type` --- sea-orm-macros/src/derives/active_enum.rs | 2 +- src/entity/active_enum.rs | 14 +++++++------- src/error.rs | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/sea-orm-macros/src/derives/active_enum.rs b/sea-orm-macros/src/derives/active_enum.rs index 6823405d5..cfd5454dc 100644 --- a/sea-orm-macros/src/derives/active_enum.rs +++ b/sea-orm-macros/src/derives/active_enum.rs @@ -191,7 +191,7 @@ impl ActiveEnum { fn try_from_value(v: &Self::Value) -> Result { match #val { #( #variant_values => Ok(Self::#variant_idents), )* - _ => Err(sea_orm::DbErr::Query(format!( + _ => Err(sea_orm::DbErr::Type(format!( "unexpected value for {} enum: {}", stringify!(#ident), v diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs index 31a65db7a..5eb77b9f2 100644 --- a/src/entity/active_enum.rs +++ b/src/entity/active_enum.rs @@ -15,7 +15,7 @@ use sea_query::{Nullable, Value, ValueType}; /// /// ```rust /// use sea_orm::entity::prelude::*; -/// +/// /// // Using the derive macro /// #[derive(Debug, PartialEq, DeriveActiveEnum)] /// #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] @@ -51,7 +51,7 @@ use sea_query::{Nullable, Value, ValueType}; /// match v.as_ref() { /// "B" => Ok(Self::Big), /// "S" => Ok(Self::Small), -/// _ => Err(DbErr::Query(format!( +/// _ => Err(DbErr::Type(format!( /// "unexpected value for Category enum: {}", /// v /// ))), @@ -138,7 +138,7 @@ mod tests { match v.as_ref() { "B" => Ok(Self::Big), "S" => Ok(Self::Small), - _ => Err(DbErr::Query(format!( + _ => Err(DbErr::Type(format!( "unexpected value for Category enum: {}", v ))), @@ -166,7 +166,7 @@ mod tests { assert_eq!( Category::try_from_value(&"A".to_owned()).err(), - Some(DbErr::Query( + Some(DbErr::Type( "unexpected value for Category enum: A".to_owned() )) ); @@ -180,7 +180,7 @@ mod tests { ); assert_eq!( DeriveCategory::try_from_value(&"A".to_owned()).err(), - Some(DbErr::Query( + Some(DbErr::Type( "unexpected value for DeriveCategory enum: A".to_owned() )) ); @@ -221,7 +221,7 @@ mod tests { assert_eq!($ident::try_from_value(&-10).ok(), Some($ident::Negative)); assert_eq!( $ident::try_from_value(&2).err(), - Some(DbErr::Query(format!( + Some(DbErr::Type(format!( "unexpected value for {} enum: 2", stringify!($ident) ))) @@ -257,7 +257,7 @@ mod tests { assert_eq!($ident::try_from_value(&0).ok(), Some($ident::Small)); assert_eq!( $ident::try_from_value(&2).err(), - Some(DbErr::Query(format!( + Some(DbErr::Type(format!( "unexpected value for {} enum: 2", stringify!($ident) ))) diff --git a/src/error.rs b/src/error.rs index f39aee721..c138009ba 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,10 +1,23 @@ +/// Represents all the errors in SeaORM. #[derive(Debug, PartialEq)] pub enum DbErr { + /// Error occurred while connecting to database engine. Conn(String), + + /// Error occurred while executing SQL statement. Exec(String), + + /// Error occurred while querying SQL statement. Query(String), + + /// Error occurred while updating a non-existing row in database. RecordNotFound(String), + + /// Error occurred while performing custom validation logics in [ActiveModelBehavior](crate::ActiveModelBehavior) Custom(String), + + /// Error occurred while parsing value into [ActiveEnum](crate::ActiveEnum) + Type(String), } impl std::error::Error for DbErr {} @@ -17,6 +30,7 @@ impl std::fmt::Display for DbErr { Self::Query(s) => write!(f, "Query Error: {}", s), Self::RecordNotFound(s) => write!(f, "RecordNotFound Error: {}", s), Self::Custom(s) => write!(f, "Custom Error: {}", s), + Self::Type(s) => write!(f, "Type Error: {}", s), } } } From 868a469de0c89a033da886e043a714444eeabb6e Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 20 Oct 2021 18:02:23 +0800 Subject: [PATCH 12/39] Test integer enum --- tests/active_enum_tests.rs | 36 +++++++++++++++++++++++----- tests/common/features/active_enum.rs | 23 ++++++++++++++++-- tests/common/features/schema.rs | 13 ++++------ tests/common/setup/mod.rs | 2 +- 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/tests/active_enum_tests.rs b/tests/active_enum_tests.rs index 90472fde4..568524814 100644 --- a/tests/active_enum_tests.rs +++ b/tests/active_enum_tests.rs @@ -19,19 +19,43 @@ async fn main() -> Result<(), DbErr> { } pub async fn insert_active_enum(db: &DatabaseConnection) -> Result<(), DbErr> { - active_enum::ActiveModel { - category: Set(active_enum::Category::Big), + use active_enum::*; + + let am = ActiveModel { + category: Set(None), + color: Set(None), + // tea: Set(None), ..Default::default() } .insert(db) .await?; assert_eq!( - active_enum::Entity::find().one(db).await?.unwrap(), - active_enum::Model { + Entity::find().one(db).await?.unwrap(), + Model { + id: 1, + category: None, + color: None, + // tea: None, + } + ); + + ActiveModel { + category: Set(Some(Category::Big)), + color: Set(Some(Color::Black)), + // tea: Set(Some(Tea::EverydayTea)), + ..am + } + .save(db) + .await?; + + assert_eq!( + Entity::find().one(db).await?.unwrap(), + Model { id: 1, - category: active_enum::Category::Big, - category_opt: None, + category: Some(Category::Big), + color: Some(Color::Black), + // tea: Some(Tea::EverydayTea), } ); diff --git a/tests/common/features/active_enum.rs b/tests/common/features/active_enum.rs index e1aef0fed..d7b15443e 100644 --- a/tests/common/features/active_enum.rs +++ b/tests/common/features/active_enum.rs @@ -5,8 +5,9 @@ use sea_orm::entity::prelude::*; pub struct Model { #[sea_orm(primary_key)] pub id: i32, - pub category: Category, - pub category_opt: Option, + pub category: Option, + pub color: Option, + // pub tea: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -22,3 +23,21 @@ pub enum Category { #[sea_orm(string_value = "S")] Small, } + +#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] +#[sea_orm(rs_type = "i32", db_type = r#"Integer"#)] +pub enum Color { + #[sea_orm(num_value = 0)] + Black, + #[sea_orm(num_value = 1)] + White, +} + +#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = r#"Custom("tea".to_owned())"#)] +pub enum Tea { + #[sea_orm(string_value = "EverydayTea")] + EverydayTea, + #[sea_orm(string_value = "BreakfastTea")] + BreakfastTea, +} diff --git a/tests/common/features/schema.rs b/tests/common/features/schema.rs index 70752d35e..07a2953b4 100644 --- a/tests/common/features/schema.rs +++ b/tests/common/features/schema.rs @@ -2,8 +2,8 @@ pub use super::super::bakery_chain::*; use super::*; use crate::common::setup::create_table; -use sea_orm::{error::*, sea_query, DatabaseConnection, DbConn, ExecResult}; -use sea_query::ColumnDef; +use sea_orm::{ConnectionTrait, DatabaseConnection, DbConn, ExecResult, Statement, error::*, sea_query}; +use sea_query::{Alias, ColumnDef}; pub async fn create_tables(db: &DatabaseConnection) -> Result<(), DbErr> { create_log_table(db).await?; @@ -87,12 +87,9 @@ pub async fn create_active_enum_table(db: &DbConn) -> Result .auto_increment() .primary_key(), ) - .col( - ColumnDef::new(active_enum::Column::Category) - .string_len(1) - .not_null(), - ) - .col(ColumnDef::new(active_enum::Column::CategoryOpt).string_len(1)) + .col(ColumnDef::new(active_enum::Column::Category).string_len(1)) + .col(ColumnDef::new(active_enum::Column::Color).integer()) + // .col(ColumnDef::new(active_enum::Column::Tea).custom(Alias::new("tea"))) .to_owned(); create_table(db, &stmt, ActiveEnum).await diff --git a/tests/common/setup/mod.rs b/tests/common/setup/mod.rs index bbe8baa77..615de234c 100644 --- a/tests/common/setup/mod.rs +++ b/tests/common/setup/mod.rs @@ -1,8 +1,8 @@ +use pretty_assertions::assert_eq; use sea_orm::{ ConnectionTrait, Database, DatabaseBackend, DatabaseConnection, DbBackend, DbConn, DbErr, EntityTrait, ExecResult, Schema, Statement, }; - use sea_query::{Alias, Table, TableCreateStatement}; pub async fn setup(base_url: &str, db_name: &str) -> DatabaseConnection { From 734608471c2b14c96bda1e61df84fb83b2ee539f Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 20 Oct 2021 18:54:08 +0800 Subject: [PATCH 13/39] WIP --- src/entity/active_enum.rs | 18 +++++++++--------- src/entity/column.rs | 4 ++++ tests/active_enum_tests.rs | 8 ++++---- tests/common/features/active_enum.rs | 10 +++++----- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs index 5eb77b9f2..edca0aac1 100644 --- a/src/entity/active_enum.rs +++ b/src/entity/active_enum.rs @@ -1,4 +1,4 @@ -use crate::{ColumnDef, DbErr, TryGetable}; +use crate::{ColumnDef, DbErr, Iterable, TryGetable}; use sea_query::{Nullable, Value, ValueType}; /// A Rust representation of enum defined in database. @@ -17,7 +17,7 @@ use sea_query::{Nullable, Value, ValueType}; /// use sea_orm::entity::prelude::*; /// /// // Using the derive macro -/// #[derive(Debug, PartialEq, DeriveActiveEnum)] +/// #[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)] /// #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] /// pub enum DeriveCategory { /// #[sea_orm(string_value = "B")] @@ -27,7 +27,7 @@ use sea_query::{Nullable, Value, ValueType}; /// } /// /// // Implementing it manually -/// #[derive(Debug, PartialEq)] +/// #[derive(Debug, PartialEq, EnumIter)] /// pub enum Category { /// Big, /// Small, @@ -80,7 +80,7 @@ use sea_query::{Nullable, Value, ValueType}; /// Small, /// } /// -/// #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +/// #[derive(Clone, Debug, PartialEq, EnumIter, DeriveEntityModel)] /// #[sea_orm(table_name = "active_enum")] /// pub struct Model { /// #[sea_orm(primary_key)] @@ -95,7 +95,7 @@ use sea_query::{Nullable, Value, ValueType}; /// /// impl ActiveModelBehavior for ActiveModel {} /// ``` -pub trait ActiveEnum: Sized { +pub trait ActiveEnum: Sized + Iterable { /// Define the Rust type that each enum variant represents. type Value: Into + ValueType + Nullable + TryGetable; @@ -117,7 +117,7 @@ mod tests { #[test] fn active_enum_string() { - #[derive(Debug, PartialEq)] + #[derive(Debug, PartialEq, EnumIter)] pub enum Category { Big, Small, @@ -150,7 +150,7 @@ mod tests { } } - #[derive(Debug, PartialEq, DeriveActiveEnum)] + #[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] pub enum DeriveCategory { #[sea_orm(string_value = "B")] @@ -201,7 +201,7 @@ mod tests { fn active_enum_derive_signed_integers() { macro_rules! test_int { ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => { - #[derive(Debug, PartialEq, DeriveActiveEnum)] + #[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = $rs_type, db_type = $db_type)] pub enum $ident { #[sea_orm(num_value = 1)] @@ -241,7 +241,7 @@ mod tests { fn active_enum_derive_unsigned_integers() { macro_rules! test_uint { ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => { - #[derive(Debug, PartialEq, DeriveActiveEnum)] + #[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = $rs_type, db_type = $db_type)] pub enum $ident { #[sea_orm(num_value = 1)] diff --git a/src/entity/column.rs b/src/entity/column.rs index 25ed84473..384be24a0 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -34,6 +34,7 @@ pub enum ColumnType { JsonBinary, Custom(String), Uuid, + Enum(String), } macro_rules! bind_oper { @@ -295,6 +296,9 @@ impl From for sea_query::ColumnType { sea_query::ColumnType::Custom(sea_query::SeaRc::new(sea_query::Alias::new(&s))) } ColumnType::Uuid => sea_query::ColumnType::Uuid, + ColumnType::Enum(s) => { + sea_query::ColumnType::Custom(sea_query::SeaRc::new(sea_query::Alias::new(&s))) + } } } } diff --git a/tests/active_enum_tests.rs b/tests/active_enum_tests.rs index 568524814..b0c72a822 100644 --- a/tests/active_enum_tests.rs +++ b/tests/active_enum_tests.rs @@ -24,7 +24,7 @@ pub async fn insert_active_enum(db: &DatabaseConnection) -> Result<(), DbErr> { let am = ActiveModel { category: Set(None), color: Set(None), - // tea: Set(None), + tea: Set(None), ..Default::default() } .insert(db) @@ -36,14 +36,14 @@ pub async fn insert_active_enum(db: &DatabaseConnection) -> Result<(), DbErr> { id: 1, category: None, color: None, - // tea: None, + tea: None, } ); ActiveModel { category: Set(Some(Category::Big)), color: Set(Some(Color::Black)), - // tea: Set(Some(Tea::EverydayTea)), + tea: Set(Some(Tea::EverydayTea)), ..am } .save(db) @@ -55,7 +55,7 @@ pub async fn insert_active_enum(db: &DatabaseConnection) -> Result<(), DbErr> { id: 1, category: Some(Category::Big), color: Some(Color::Black), - // tea: Some(Tea::EverydayTea), + tea: Some(Tea::EverydayTea), } ); diff --git a/tests/common/features/active_enum.rs b/tests/common/features/active_enum.rs index d7b15443e..229d44466 100644 --- a/tests/common/features/active_enum.rs +++ b/tests/common/features/active_enum.rs @@ -7,7 +7,7 @@ pub struct Model { pub id: i32, pub category: Option, pub color: Option, - // pub tea: Option, + pub tea: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -15,7 +15,7 @@ pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} -#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] pub enum Category { #[sea_orm(string_value = "B")] @@ -24,7 +24,7 @@ pub enum Category { Small, } -#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = "i32", db_type = r#"Integer"#)] pub enum Color { #[sea_orm(num_value = 0)] @@ -33,8 +33,8 @@ pub enum Color { White, } -#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] -#[sea_orm(rs_type = "String", db_type = r#"Custom("tea".to_owned())"#)] +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = r#"Enum("tea".to_owned())"#)] pub enum Tea { #[sea_orm(string_value = "EverydayTea")] EverydayTea, From 80c72004d15afa8c4e6920abab76e91d55f5161a Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 21 Oct 2021 15:40:22 +0800 Subject: [PATCH 14/39] Try Postgres enum --- Cargo.toml | 2 +- src/entity/active_enum.rs | 2 +- src/entity/column.rs | 7 ++++++ src/query/insert.rs | 19 +++++++++++---- src/query/select.rs | 20 ++++++++++++--- src/query/update.rs | 12 +++++++-- tests/active_enum_tests.rs | 7 +++++- tests/common/features/schema.rs | 43 +++++++++++++++++++++++++++++++-- 8 files changed, 96 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 716903748..42f97b62e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ futures-util = { version = "^0.3" } log = { version = "^0.4", optional = true } rust_decimal = { version = "^1", optional = true } sea-orm-macros = { version = "^0.3.0", path = "sea-orm-macros", optional = true } -sea-query = { version = "^0.18.0", features = ["thread-safe"] } +sea-query = { version = "^0.18.0", git = "https://github.com/SeaQL/sea-query.git", branch = "sea-orm/active-enum-1", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1", optional = true } diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs index edca0aac1..b42b31f7b 100644 --- a/src/entity/active_enum.rs +++ b/src/entity/active_enum.rs @@ -71,7 +71,7 @@ use sea_query::{Nullable, Value, ValueType}; /// use sea_orm::entity::prelude::*; /// /// // Define the `Category` active enum -/// #[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] +/// #[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)] /// #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] /// pub enum Category { /// #[sea_orm(string_value = "B")] diff --git a/src/entity/column.rs b/src/entity/column.rs index 384be24a0..32fe030c6 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -242,6 +242,13 @@ impl ColumnType { indexed: false, } } + + pub(crate) fn get_enum_name(&self) -> Option<&String> { + match self { + ColumnType::Enum(s) => Some(s), + _ => None, + } + } } impl ColumnDef { diff --git a/src/query/insert.rs b/src/query/insert.rs index 5e504a0c5..f7f7c7d38 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -1,9 +1,9 @@ use crate::{ - ActiveModelTrait, EntityName, EntityTrait, IntoActiveModel, Iterable, PrimaryKeyTrait, - QueryTrait, + ActiveModelTrait, ColumnTrait, EntityName, EntityTrait, IntoActiveModel, Iterable, + PrimaryKeyTrait, QueryTrait, }; use core::marker::PhantomData; -use sea_query::{InsertStatement, ValueTuple}; +use sea_query::{Alias, Expr, Func, InsertStatement, ValueTuple}; #[derive(Debug)] pub struct Insert @@ -124,6 +124,9 @@ where for (idx, col) in ::Column::iter().enumerate() { let av = am.take(col); let av_has_val = av.is_set() || av.is_unchanged(); + let col_def = col.def(); + let enum_name = col_def.get_column_type().get_enum_name(); + if columns_empty { self.columns.push(av_has_val); } else if self.columns[idx] != av_has_val { @@ -131,11 +134,17 @@ where } if av_has_val { columns.push(col); - values.push(av.into_value().unwrap()); + let val = av.into_value().unwrap(); + let expr = if let Some(enum_name) = enum_name { + Func::cast_as(val, Alias::new(&enum_name)) + } else { + Expr::val(val).into() + }; + values.push(expr); } } self.query.columns(columns); - self.query.values_panic(values); + self.query.exprs_panic(values); self } diff --git a/src/query/select.rs b/src/query/select.rs index 1b0c93c3d..5a2e0cdc2 100644 --- a/src/query/select.rs +++ b/src/query/select.rs @@ -2,7 +2,7 @@ use crate::{ColumnTrait, EntityTrait, Iterable, QueryFilter, QueryOrder, QuerySe use core::fmt::Debug; use core::marker::PhantomData; pub use sea_query::JoinType; -use sea_query::{DynIden, IntoColumnRef, SeaRc, SelectStatement, SimpleExpr}; +use sea_query::{Alias, DynIden, Expr, Func, IntoColumnRef, SeaRc, SelectStatement, SimpleExpr}; #[derive(Clone, Debug)] pub struct Select @@ -109,13 +109,25 @@ where } fn prepare_select(mut self) -> Self { - self.query.columns(self.column_list()); + self.query.exprs(self.column_list()); self } - fn column_list(&self) -> Vec<(DynIden, E::Column)> { + fn column_list(&self) -> Vec { let table = SeaRc::new(E::default()) as DynIden; - E::Column::iter().map(|col| (table.clone(), col)).collect() + E::Column::iter() + .map(|col| { + let col_def = col.def(); + let enum_name = col_def.get_column_type().get_enum_name(); + let col_expr = Expr::tbl(table.clone(), col); + let col_expr = if let Some(_) = enum_name { + Func::cast_expr_as(col_expr, Alias::new("text")) + } else { + col_expr.into() + }; + col_expr + }) + .collect() } fn prepare_from(mut self) -> Self { diff --git a/src/query/update.rs b/src/query/update.rs index fec7757cc..2f9bdb3bc 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -3,7 +3,7 @@ use crate::{ QueryTrait, }; use core::marker::PhantomData; -use sea_query::{IntoIden, SimpleExpr, UpdateStatement}; +use sea_query::{Alias, Expr, Func, IntoIden, SimpleExpr, UpdateStatement}; #[derive(Clone, Debug)] pub struct Update; @@ -104,9 +104,17 @@ where if ::PrimaryKey::from_column(col).is_some() { continue; } + let col_def = col.def(); + let enum_name = col_def.get_column_type().get_enum_name(); let av = self.model.get(col); if av.is_set() { - self.query.value(col, av.unwrap()); + let val = av.into_value().unwrap(); + let expr = if let Some(enum_name) = enum_name { + Func::cast_as(val, Alias::new(&enum_name)) + } else { + Expr::val(val).into() + }; + self.query.value_expr(col, expr); } } self diff --git a/tests/active_enum_tests.rs b/tests/active_enum_tests.rs index b0c72a822..ca9bf7d99 100644 --- a/tests/active_enum_tests.rs +++ b/tests/active_enum_tests.rs @@ -40,7 +40,7 @@ pub async fn insert_active_enum(db: &DatabaseConnection) -> Result<(), DbErr> { } ); - ActiveModel { + let am = ActiveModel { category: Set(Some(Category::Big)), color: Set(Some(Color::Black)), tea: Set(Some(Tea::EverydayTea)), @@ -59,5 +59,10 @@ pub async fn insert_active_enum(db: &DatabaseConnection) -> Result<(), DbErr> { } ); + let res = am.delete(db).await?; + + assert_eq!(res.rows_affected, 1); + assert_eq!(Entity::find().one(db).await?, None); + Ok(()) } diff --git a/tests/common/features/schema.rs b/tests/common/features/schema.rs index 823ccdfd9..ffb47f269 100644 --- a/tests/common/features/schema.rs +++ b/tests/common/features/schema.rs @@ -3,7 +3,7 @@ pub use super::super::bakery_chain::*; use super::*; use crate::common::setup::create_table; use sea_orm::{error::*, sea_query, DatabaseConnection, DbConn, ExecResult}; -use sea_query::{ColumnDef, ForeignKeyCreateStatement}; +use sea_query::{Alias, ColumnDef, ForeignKeyCreateStatement}; pub async fn create_tables(db: &DatabaseConnection) -> Result<(), DbErr> { create_log_table(db).await?; @@ -103,6 +103,8 @@ pub async fn create_self_join_table(db: &DbConn) -> Result { } pub async fn create_active_enum_table(db: &DbConn) -> Result { + let tea_enum = Alias::new("tea"); + let stmt = sea_query::Table::create() .table(active_enum::Entity) .col( @@ -114,8 +116,45 @@ pub async fn create_active_enum_table(db: &DbConn) -> Result ) .col(ColumnDef::new(active_enum::Column::Category).string_len(1)) .col(ColumnDef::new(active_enum::Column::Color).integer()) - // .col(ColumnDef::new(active_enum::Column::Tea).custom(Alias::new("tea"))) + .col(ColumnDef::new(active_enum::Column::Tea).custom(tea_enum.clone())) .to_owned(); + match db { + #[cfg(feature = "sqlx-postgres")] + DatabaseConnection::SqlxPostgresPoolConnection(_) => { + use sea_orm::{ConnectionTrait, Statement}; + use sea_query::{extension::postgres::Type, PostgresQueryBuilder}; + + let drop_type_stmt = Type::drop() + .name(tea_enum.clone()) + .cascade() + .if_exists() + .to_owned(); + let (sql, values) = drop_type_stmt.build(PostgresQueryBuilder); + let stmt = Statement::from_sql_and_values(db.get_database_backend(), &sql, values); + db.execute(stmt).await?; + + let create_type_stmt = Type::create() + .as_enum(tea_enum) + .values(vec![Alias::new("EverydayTea"), Alias::new("BreakfastTea")]) + .to_owned(); + + // FIXME: This is not working + { + let (sql, values) = create_type_stmt.build(PostgresQueryBuilder); + let _stmt = Statement::from_sql_and_values(db.get_database_backend(), &sql, values); + } + + // But this is working... + let stmt = Statement::from_string( + db.get_database_backend(), + create_type_stmt.to_string(PostgresQueryBuilder), + ); + + db.execute(stmt).await?; + } + _ => {} + } + create_table(db, &stmt, ActiveEnum).await } From 20c66b2f059824a2148c2b9fd064cf9dc5c956af Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 21 Oct 2021 15:50:18 +0800 Subject: [PATCH 15/39] Refactoring --- src/query/insert.rs | 2 +- src/query/select.rs | 5 ++--- src/query/update.rs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/query/insert.rs b/src/query/insert.rs index f7f7c7d38..3a7c91d67 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -136,7 +136,7 @@ where columns.push(col); let val = av.into_value().unwrap(); let expr = if let Some(enum_name) = enum_name { - Func::cast_as(val, Alias::new(&enum_name)) + Func::cast_as(val, Alias::new(enum_name)) } else { Expr::val(val).into() }; diff --git a/src/query/select.rs b/src/query/select.rs index 5a2e0cdc2..0f2db65bf 100644 --- a/src/query/select.rs +++ b/src/query/select.rs @@ -120,12 +120,11 @@ where let col_def = col.def(); let enum_name = col_def.get_column_type().get_enum_name(); let col_expr = Expr::tbl(table.clone(), col); - let col_expr = if let Some(_) = enum_name { + if enum_name.is_some() { Func::cast_expr_as(col_expr, Alias::new("text")) } else { col_expr.into() - }; - col_expr + } }) .collect() } diff --git a/src/query/update.rs b/src/query/update.rs index 2f9bdb3bc..a881ad115 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -110,7 +110,7 @@ where if av.is_set() { let val = av.into_value().unwrap(); let expr = if let Some(enum_name) = enum_name { - Func::cast_as(val, Alias::new(&enum_name)) + Func::cast_as(val, Alias::new(enum_name)) } else { Expr::val(val).into() }; From 1ee2dab3b7df2f57b684a16ebc9d39d5b4344b33 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 21 Oct 2021 15:53:46 +0800 Subject: [PATCH 16/39] Fixup --- src/entity/active_enum.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs index b42b31f7b..230ee8f2a 100644 --- a/src/entity/active_enum.rs +++ b/src/entity/active_enum.rs @@ -80,7 +80,7 @@ use sea_query::{Nullable, Value, ValueType}; /// Small, /// } /// -/// #[derive(Clone, Debug, PartialEq, EnumIter, DeriveEntityModel)] +/// #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] /// #[sea_orm(table_name = "active_enum")] /// pub struct Model { /// #[sea_orm(primary_key)] From 8858d64dd0641cc0ef28a3969711ceae79e9f138 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 25 Oct 2021 15:48:01 +0800 Subject: [PATCH 17/39] create_table_from_entity with DB backend --- src/schema/entity.rs | 17 ++++++++++------- tests/common/setup/mod.rs | 5 ++++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/schema/entity.rs b/src/schema/entity.rs index a95b70478..2d0aa13a1 100644 --- a/src/schema/entity.rs +++ b/src/schema/entity.rs @@ -1,19 +1,19 @@ use crate::{ - unpack_table_ref, ColumnTrait, EntityTrait, Identity, Iterable, PrimaryKeyToColumn, + unpack_table_ref, ColumnTrait, DbBackend, EntityTrait, Identity, Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, RelationTrait, Schema, }; use sea_query::{ColumnDef, ForeignKeyCreateStatement, Iden, Index, TableCreateStatement}; impl Schema { - pub fn create_table_from_entity(entity: E) -> TableCreateStatement + pub fn create_table_from_entity(entity: E, db_backend: DbBackend) -> TableCreateStatement where E: EntityTrait, { - create_table_from_entity(entity) + create_table_from_entity(entity, db_backend) } } -pub(crate) fn create_table_from_entity(entity: E) -> TableCreateStatement +pub(crate) fn create_table_from_entity(entity: E, db_backend: DbBackend) -> TableCreateStatement where E: EntityTrait, { @@ -21,7 +21,9 @@ where for column in E::Column::iter() { let orm_column_def = column.def(); - let types = orm_column_def.col_type.into(); + let types = match db_backend { + _ => orm_column_def.col_type.into(), + }; let mut column_def = ColumnDef::new_with_type(column, types); if !orm_column_def.null { column_def.not_null(); @@ -121,13 +123,14 @@ where #[cfg(test)] mod tests { - use crate::{sea_query::*, tests_cfg::*, Schema}; + use crate::{sea_query::*, tests_cfg::*, DbBackend, Schema}; use pretty_assertions::assert_eq; #[test] fn test_create_table_from_entity() { assert_eq!( - Schema::create_table_from_entity(CakeFillingPrice).to_string(MysqlQueryBuilder), + Schema::create_table_from_entity(CakeFillingPrice, DbBackend::MySql) + .to_string(MysqlQueryBuilder), Table::create() .table(CakeFillingPrice) .col( diff --git a/tests/common/setup/mod.rs b/tests/common/setup/mod.rs index 615de234c..7266d1759 100644 --- a/tests/common/setup/mod.rs +++ b/tests/common/setup/mod.rs @@ -95,7 +95,10 @@ where let stmt = builder.build(create); assert_eq!( - builder.build(&Schema::create_table_from_entity(entity)), + builder.build(&Schema::create_table_from_entity( + entity, + db.get_database_backend() + )), stmt ); db.execute(stmt).await From f20c64988df6c4657cd625bfd33c69daee92cc7d Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 25 Oct 2021 16:52:02 +0800 Subject: [PATCH 18/39] Tests all DB --- src/entity/column.rs | 7 ++++--- src/schema/entity.rs | 14 ++++++++++--- tests/common/features/active_enum.rs | 5 ++++- tests/common/features/schema.rs | 30 +++++++++++++++++----------- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/entity/column.rs b/src/entity/column.rs index 32fe030c6..4b9af89fe 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -34,7 +34,7 @@ pub enum ColumnType { JsonBinary, Custom(String), Uuid, - Enum(String), + Enum(String, Vec), } macro_rules! bind_oper { @@ -245,7 +245,8 @@ impl ColumnType { pub(crate) fn get_enum_name(&self) -> Option<&String> { match self { - ColumnType::Enum(s) => Some(s), + // FIXME: How to get rid of this feature gate? + ColumnType::Enum(s, _) if cfg!(feature = "sqlx-postgres") => Some(s), _ => None, } } @@ -303,7 +304,7 @@ impl From for sea_query::ColumnType { sea_query::ColumnType::Custom(sea_query::SeaRc::new(sea_query::Alias::new(&s))) } ColumnType::Uuid => sea_query::ColumnType::Uuid, - ColumnType::Enum(s) => { + ColumnType::Enum(s, _) => { sea_query::ColumnType::Custom(sea_query::SeaRc::new(sea_query::Alias::new(&s))) } } diff --git a/src/schema/entity.rs b/src/schema/entity.rs index 2d0aa13a1..3f99f8928 100644 --- a/src/schema/entity.rs +++ b/src/schema/entity.rs @@ -1,6 +1,6 @@ use crate::{ - unpack_table_ref, ColumnTrait, DbBackend, EntityTrait, Identity, Iterable, PrimaryKeyToColumn, - PrimaryKeyTrait, RelationTrait, Schema, + unpack_table_ref, ColumnTrait, ColumnType, DbBackend, EntityTrait, Identity, Iterable, + PrimaryKeyToColumn, PrimaryKeyTrait, RelationTrait, Schema, }; use sea_query::{ColumnDef, ForeignKeyCreateStatement, Iden, Index, TableCreateStatement}; @@ -21,7 +21,15 @@ where for column in E::Column::iter() { let orm_column_def = column.def(); - let types = match db_backend { + let types = match orm_column_def.col_type { + ColumnType::Enum(s, variants) => match db_backend { + DbBackend::MySql => { + ColumnType::Custom(format!("ENUM('{}')", variants.join("', '"))) + } + DbBackend::Postgres => ColumnType::Custom(s), + DbBackend::Sqlite => ColumnType::Text, + } + .into(), _ => orm_column_def.col_type.into(), }; let mut column_def = ColumnDef::new_with_type(column, types); diff --git a/tests/common/features/active_enum.rs b/tests/common/features/active_enum.rs index 229d44466..78f217900 100644 --- a/tests/common/features/active_enum.rs +++ b/tests/common/features/active_enum.rs @@ -34,7 +34,10 @@ pub enum Color { } #[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)] -#[sea_orm(rs_type = "String", db_type = r#"Enum("tea".to_owned())"#)] +#[sea_orm( + rs_type = "String", + db_type = r#"Enum("tea".to_owned(), vec!["EverydayTea".to_owned(), "BreakfastTea".to_owned()])"# +)] pub enum Tea { #[sea_orm(string_value = "EverydayTea")] EverydayTea, diff --git a/tests/common/features/schema.rs b/tests/common/features/schema.rs index ffb47f269..0eeb5c045 100644 --- a/tests/common/features/schema.rs +++ b/tests/common/features/schema.rs @@ -2,8 +2,13 @@ pub use super::super::bakery_chain::*; use super::*; use crate::common::setup::create_table; -use sea_orm::{error::*, sea_query, DatabaseConnection, DbConn, ExecResult}; -use sea_query::{Alias, ColumnDef, ForeignKeyCreateStatement}; +use sea_orm::{ + error::*, sea_query, ConnectionTrait, DatabaseConnection, DbBackend, DbConn, ExecResult, + Statement, +}; +use sea_query::{ + extension::postgres::Type, Alias, ColumnDef, ForeignKeyCreateStatement, PostgresQueryBuilder, +}; pub async fn create_tables(db: &DatabaseConnection) -> Result<(), DbErr> { create_log_table(db).await?; @@ -103,8 +108,16 @@ pub async fn create_self_join_table(db: &DbConn) -> Result { } pub async fn create_active_enum_table(db: &DbConn) -> Result { + let db_backend = db.get_database_backend(); let tea_enum = Alias::new("tea"); + let mut tea_col = ColumnDef::new(active_enum::Column::Tea); + match db_backend { + DbBackend::MySql => tea_col.custom(Alias::new("ENUM('EverydayTea', 'BreakfastTea')")), + DbBackend::Postgres => tea_col.custom(tea_enum.clone()), + DbBackend::Sqlite => tea_col.text(), + }; + let stmt = sea_query::Table::create() .table(active_enum::Entity) .col( @@ -116,15 +129,11 @@ pub async fn create_active_enum_table(db: &DbConn) -> Result ) .col(ColumnDef::new(active_enum::Column::Category).string_len(1)) .col(ColumnDef::new(active_enum::Column::Color).integer()) - .col(ColumnDef::new(active_enum::Column::Tea).custom(tea_enum.clone())) + .col(&mut tea_col) .to_owned(); - match db { - #[cfg(feature = "sqlx-postgres")] - DatabaseConnection::SqlxPostgresPoolConnection(_) => { - use sea_orm::{ConnectionTrait, Statement}; - use sea_query::{extension::postgres::Type, PostgresQueryBuilder}; - + match db_backend { + DbBackend::Postgres => { let drop_type_stmt = Type::drop() .name(tea_enum.clone()) .cascade() @@ -138,19 +147,16 @@ pub async fn create_active_enum_table(db: &DbConn) -> Result .as_enum(tea_enum) .values(vec![Alias::new("EverydayTea"), Alias::new("BreakfastTea")]) .to_owned(); - // FIXME: This is not working { let (sql, values) = create_type_stmt.build(PostgresQueryBuilder); let _stmt = Statement::from_sql_and_values(db.get_database_backend(), &sql, values); } - // But this is working... let stmt = Statement::from_string( db.get_database_backend(), create_type_stmt.to_string(PostgresQueryBuilder), ); - db.execute(stmt).await?; } _ => {} From 6059cdd9adf7af4768b1dd727b6730dfcc930c46 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 25 Oct 2021 16:56:36 +0800 Subject: [PATCH 19/39] Remove unused EnumIter --- src/entity/active_enum.rs | 18 +++++++++--------- tests/common/features/active_enum.rs | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs index 230ee8f2a..5eb77b9f2 100644 --- a/src/entity/active_enum.rs +++ b/src/entity/active_enum.rs @@ -1,4 +1,4 @@ -use crate::{ColumnDef, DbErr, Iterable, TryGetable}; +use crate::{ColumnDef, DbErr, TryGetable}; use sea_query::{Nullable, Value, ValueType}; /// A Rust representation of enum defined in database. @@ -17,7 +17,7 @@ use sea_query::{Nullable, Value, ValueType}; /// use sea_orm::entity::prelude::*; /// /// // Using the derive macro -/// #[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)] +/// #[derive(Debug, PartialEq, DeriveActiveEnum)] /// #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] /// pub enum DeriveCategory { /// #[sea_orm(string_value = "B")] @@ -27,7 +27,7 @@ use sea_query::{Nullable, Value, ValueType}; /// } /// /// // Implementing it manually -/// #[derive(Debug, PartialEq, EnumIter)] +/// #[derive(Debug, PartialEq)] /// pub enum Category { /// Big, /// Small, @@ -71,7 +71,7 @@ use sea_query::{Nullable, Value, ValueType}; /// use sea_orm::entity::prelude::*; /// /// // Define the `Category` active enum -/// #[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)] +/// #[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] /// #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] /// pub enum Category { /// #[sea_orm(string_value = "B")] @@ -95,7 +95,7 @@ use sea_query::{Nullable, Value, ValueType}; /// /// impl ActiveModelBehavior for ActiveModel {} /// ``` -pub trait ActiveEnum: Sized + Iterable { +pub trait ActiveEnum: Sized { /// Define the Rust type that each enum variant represents. type Value: Into + ValueType + Nullable + TryGetable; @@ -117,7 +117,7 @@ mod tests { #[test] fn active_enum_string() { - #[derive(Debug, PartialEq, EnumIter)] + #[derive(Debug, PartialEq)] pub enum Category { Big, Small, @@ -150,7 +150,7 @@ mod tests { } } - #[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)] + #[derive(Debug, PartialEq, DeriveActiveEnum)] #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] pub enum DeriveCategory { #[sea_orm(string_value = "B")] @@ -201,7 +201,7 @@ mod tests { fn active_enum_derive_signed_integers() { macro_rules! test_int { ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => { - #[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)] + #[derive(Debug, PartialEq, DeriveActiveEnum)] #[sea_orm(rs_type = $rs_type, db_type = $db_type)] pub enum $ident { #[sea_orm(num_value = 1)] @@ -241,7 +241,7 @@ mod tests { fn active_enum_derive_unsigned_integers() { macro_rules! test_uint { ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => { - #[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)] + #[derive(Debug, PartialEq, DeriveActiveEnum)] #[sea_orm(rs_type = $rs_type, db_type = $db_type)] pub enum $ident { #[sea_orm(num_value = 1)] diff --git a/tests/common/features/active_enum.rs b/tests/common/features/active_enum.rs index 78f217900..f152f37c3 100644 --- a/tests/common/features/active_enum.rs +++ b/tests/common/features/active_enum.rs @@ -15,7 +15,7 @@ pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} -#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)] +#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] pub enum Category { #[sea_orm(string_value = "B")] @@ -24,7 +24,7 @@ pub enum Category { Small, } -#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)] +#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] #[sea_orm(rs_type = "i32", db_type = r#"Integer"#)] pub enum Color { #[sea_orm(num_value = 0)] @@ -33,7 +33,7 @@ pub enum Color { White, } -#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)] +#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] #[sea_orm( rs_type = "String", db_type = r#"Enum("tea".to_owned(), vec!["EverydayTea".to_owned(), "BreakfastTea".to_owned()])"# From 4b1cac7f354242dac9af2b9fa6698e9e30ef3f07 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 25 Oct 2021 17:05:27 +0800 Subject: [PATCH 20/39] Refactoring --- tests/common/features/schema.rs | 51 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/tests/common/features/schema.rs b/tests/common/features/schema.rs index 0eeb5c045..f630ccb79 100644 --- a/tests/common/features/schema.rs +++ b/tests/common/features/schema.rs @@ -132,34 +132,31 @@ pub async fn create_active_enum_table(db: &DbConn) -> Result .col(&mut tea_col) .to_owned(); - match db_backend { - DbBackend::Postgres => { - let drop_type_stmt = Type::drop() - .name(tea_enum.clone()) - .cascade() - .if_exists() - .to_owned(); - let (sql, values) = drop_type_stmt.build(PostgresQueryBuilder); - let stmt = Statement::from_sql_and_values(db.get_database_backend(), &sql, values); - db.execute(stmt).await?; - - let create_type_stmt = Type::create() - .as_enum(tea_enum) - .values(vec![Alias::new("EverydayTea"), Alias::new("BreakfastTea")]) - .to_owned(); - // FIXME: This is not working - { - let (sql, values) = create_type_stmt.build(PostgresQueryBuilder); - let _stmt = Statement::from_sql_and_values(db.get_database_backend(), &sql, values); - } - // But this is working... - let stmt = Statement::from_string( - db.get_database_backend(), - create_type_stmt.to_string(PostgresQueryBuilder), - ); - db.execute(stmt).await?; + if db_backend = DbBackend::Postgres { + let drop_type_stmt = Type::drop() + .name(tea_enum.clone()) + .cascade() + .if_exists() + .to_owned(); + let (sql, values) = drop_type_stmt.build(PostgresQueryBuilder); + let stmt = Statement::from_sql_and_values(db.get_database_backend(), &sql, values); + db.execute(stmt).await?; + + let create_type_stmt = Type::create() + .as_enum(tea_enum) + .values(vec![Alias::new("EverydayTea"), Alias::new("BreakfastTea")]) + .to_owned(); + // FIXME: This is not working + { + let (sql, values) = create_type_stmt.build(PostgresQueryBuilder); + let _stmt = Statement::from_sql_and_values(db.get_database_backend(), &sql, values); } - _ => {} + // But this is working... + let stmt = Statement::from_string( + db.get_database_backend(), + create_type_stmt.to_string(PostgresQueryBuilder), + ); + db.execute(stmt).await?; } create_table(db, &stmt, ActiveEnum).await From cf52839c3ac4564a10790b060d0e97b37f3daf1f Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 25 Oct 2021 17:12:17 +0800 Subject: [PATCH 21/39] Typo --- tests/common/features/schema.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/common/features/schema.rs b/tests/common/features/schema.rs index f630ccb79..bbc9faf8b 100644 --- a/tests/common/features/schema.rs +++ b/tests/common/features/schema.rs @@ -132,7 +132,7 @@ pub async fn create_active_enum_table(db: &DbConn) -> Result .col(&mut tea_col) .to_owned(); - if db_backend = DbBackend::Postgres { + if db_backend == DbBackend::Postgres { let drop_type_stmt = Type::drop() .name(tea_enum.clone()) .cascade() From 2ee376ddd117b2e14f16dd6e3952645c5fc7d007 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 26 Oct 2021 16:22:24 +0800 Subject: [PATCH 22/39] Try `EnumValue` --- src/entity/column.rs | 3 +-- src/query/insert.rs | 4 ++-- src/query/select.rs | 4 ++-- src/query/update.rs | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/entity/column.rs b/src/entity/column.rs index 4b9af89fe..8cd65d9cb 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -245,8 +245,7 @@ impl ColumnType { pub(crate) fn get_enum_name(&self) -> Option<&String> { match self { - // FIXME: How to get rid of this feature gate? - ColumnType::Enum(s, _) if cfg!(feature = "sqlx-postgres") => Some(s), + ColumnType::Enum(s, _) => Some(s), _ => None, } } diff --git a/src/query/insert.rs b/src/query/insert.rs index 3a7c91d67..a1b34f501 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -3,7 +3,7 @@ use crate::{ PrimaryKeyTrait, QueryTrait, }; use core::marker::PhantomData; -use sea_query::{Alias, Expr, Func, InsertStatement, ValueTuple}; +use sea_query::{Expr, InsertStatement, SimpleExpr, ValueTuple}; #[derive(Debug)] pub struct Insert @@ -136,7 +136,7 @@ where columns.push(col); let val = av.into_value().unwrap(); let expr = if let Some(enum_name) = enum_name { - Func::cast_as(val, Alias::new(enum_name)) + SimpleExpr::EnumValue(enum_name.to_owned(), Box::new(Expr::val(val).into())) } else { Expr::val(val).into() }; diff --git a/src/query/select.rs b/src/query/select.rs index 0f2db65bf..1bdfce1a0 100644 --- a/src/query/select.rs +++ b/src/query/select.rs @@ -2,7 +2,7 @@ use crate::{ColumnTrait, EntityTrait, Iterable, QueryFilter, QueryOrder, QuerySe use core::fmt::Debug; use core::marker::PhantomData; pub use sea_query::JoinType; -use sea_query::{Alias, DynIden, Expr, Func, IntoColumnRef, SeaRc, SelectStatement, SimpleExpr}; +use sea_query::{DynIden, Expr, IntoColumnRef, SeaRc, SelectStatement, SimpleExpr}; #[derive(Clone, Debug)] pub struct Select @@ -121,7 +121,7 @@ where let enum_name = col_def.get_column_type().get_enum_name(); let col_expr = Expr::tbl(table.clone(), col); if enum_name.is_some() { - Func::cast_expr_as(col_expr, Alias::new("text")) + SimpleExpr::EnumValue("text".to_owned(), Box::new(col_expr.into())) } else { col_expr.into() } diff --git a/src/query/update.rs b/src/query/update.rs index a881ad115..a81b5607e 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -3,7 +3,7 @@ use crate::{ QueryTrait, }; use core::marker::PhantomData; -use sea_query::{Alias, Expr, Func, IntoIden, SimpleExpr, UpdateStatement}; +use sea_query::{Expr, IntoIden, SimpleExpr, UpdateStatement}; #[derive(Clone, Debug)] pub struct Update; @@ -110,7 +110,7 @@ where if av.is_set() { let val = av.into_value().unwrap(); let expr = if let Some(enum_name) = enum_name { - Func::cast_as(val, Alias::new(enum_name)) + SimpleExpr::EnumValue(enum_name.to_owned(), Box::new(Expr::val(val).into())) } else { Expr::val(val).into() }; From db22e70c6339f65e0cb40d9b2a0aa034c6183d60 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 26 Oct 2021 17:51:36 +0800 Subject: [PATCH 23/39] Refactoring --- src/query/insert.rs | 8 ++++---- src/query/select.rs | 2 +- src/query/update.rs | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/query/insert.rs b/src/query/insert.rs index a1b34f501..63f22c35b 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -3,7 +3,7 @@ use crate::{ PrimaryKeyTrait, QueryTrait, }; use core::marker::PhantomData; -use sea_query::{Expr, InsertStatement, SimpleExpr, ValueTuple}; +use sea_query::{Expr, InsertStatement, ValueTuple}; #[derive(Debug)] pub struct Insert @@ -134,11 +134,11 @@ where } if av_has_val { columns.push(col); - let val = av.into_value().unwrap(); + let val = Expr::val(av.into_value().unwrap()); let expr = if let Some(enum_name) = enum_name { - SimpleExpr::EnumValue(enum_name.to_owned(), Box::new(Expr::val(val).into())) + Expr::enum_value(enum_name, val) } else { - Expr::val(val).into() + val.into() }; values.push(expr); } diff --git a/src/query/select.rs b/src/query/select.rs index 1bdfce1a0..5a270f6d8 100644 --- a/src/query/select.rs +++ b/src/query/select.rs @@ -121,7 +121,7 @@ where let enum_name = col_def.get_column_type().get_enum_name(); let col_expr = Expr::tbl(table.clone(), col); if enum_name.is_some() { - SimpleExpr::EnumValue("text".to_owned(), Box::new(col_expr.into())) + Expr::enum_value("text", col_expr) } else { col_expr.into() } diff --git a/src/query/update.rs b/src/query/update.rs index a81b5607e..18e4594b9 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -108,11 +108,11 @@ where let enum_name = col_def.get_column_type().get_enum_name(); let av = self.model.get(col); if av.is_set() { - let val = av.into_value().unwrap(); + let val = Expr::val(av.into_value().unwrap()); let expr = if let Some(enum_name) = enum_name { - SimpleExpr::EnumValue(enum_name.to_owned(), Box::new(Expr::val(val).into())) + Expr::enum_value(enum_name, val) } else { - Expr::val(val).into() + val.into() }; self.query.value_expr(col, expr); } From ded28be2c01a744a80bcb92cae4e3922b29f7baa Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 26 Oct 2021 18:58:06 +0800 Subject: [PATCH 24/39] Refactoring --- src/entity/column.rs | 5 +---- src/query/insert.rs | 4 ++-- src/query/select.rs | 5 +++-- src/query/update.rs | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/entity/column.rs b/src/entity/column.rs index 8cd65d9cb..9a1f5c9de 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -299,13 +299,10 @@ impl From for sea_query::ColumnType { ColumnType::Money(s) => sea_query::ColumnType::Money(s), ColumnType::Json => sea_query::ColumnType::Json, ColumnType::JsonBinary => sea_query::ColumnType::JsonBinary, - ColumnType::Custom(s) => { + ColumnType::Custom(s) | ColumnType::Enum(s, _) => { sea_query::ColumnType::Custom(sea_query::SeaRc::new(sea_query::Alias::new(&s))) } ColumnType::Uuid => sea_query::ColumnType::Uuid, - ColumnType::Enum(s, _) => { - sea_query::ColumnType::Custom(sea_query::SeaRc::new(sea_query::Alias::new(&s))) - } } } } diff --git a/src/query/insert.rs b/src/query/insert.rs index 63f22c35b..8719e0118 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -3,7 +3,7 @@ use crate::{ PrimaryKeyTrait, QueryTrait, }; use core::marker::PhantomData; -use sea_query::{Expr, InsertStatement, ValueTuple}; +use sea_query::{Alias, Expr, InsertStatement, ValueTuple}; #[derive(Debug)] pub struct Insert @@ -136,7 +136,7 @@ where columns.push(col); let val = Expr::val(av.into_value().unwrap()); let expr = if let Some(enum_name) = enum_name { - Expr::enum_value(enum_name, val) + val.as_enum(Alias::new(enum_name)) } else { val.into() }; diff --git a/src/query/select.rs b/src/query/select.rs index 5a270f6d8..5c6d1f3b2 100644 --- a/src/query/select.rs +++ b/src/query/select.rs @@ -2,7 +2,7 @@ use crate::{ColumnTrait, EntityTrait, Iterable, QueryFilter, QueryOrder, QuerySe use core::fmt::Debug; use core::marker::PhantomData; pub use sea_query::JoinType; -use sea_query::{DynIden, Expr, IntoColumnRef, SeaRc, SelectStatement, SimpleExpr}; +use sea_query::{Alias, DynIden, Expr, IntoColumnRef, SeaRc, SelectStatement, SimpleExpr}; #[derive(Clone, Debug)] pub struct Select @@ -115,13 +115,14 @@ where fn column_list(&self) -> Vec { let table = SeaRc::new(E::default()) as DynIden; + let text_type = SeaRc::new(Alias::new("text")) as DynIden; E::Column::iter() .map(|col| { let col_def = col.def(); let enum_name = col_def.get_column_type().get_enum_name(); let col_expr = Expr::tbl(table.clone(), col); if enum_name.is_some() { - Expr::enum_value("text", col_expr) + col_expr.as_enum(text_type.clone()) } else { col_expr.into() } diff --git a/src/query/update.rs b/src/query/update.rs index 18e4594b9..6b6f48f83 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -3,7 +3,7 @@ use crate::{ QueryTrait, }; use core::marker::PhantomData; -use sea_query::{Expr, IntoIden, SimpleExpr, UpdateStatement}; +use sea_query::{Alias, Expr, IntoIden, SimpleExpr, UpdateStatement}; #[derive(Clone, Debug)] pub struct Update; @@ -110,7 +110,7 @@ where if av.is_set() { let val = Expr::val(av.into_value().unwrap()); let expr = if let Some(enum_name) = enum_name { - Expr::enum_value(enum_name, val) + val.as_enum(Alias::new(enum_name)) } else { val.into() }; From fac528a3699b0b6c9e0ced88553ad5720acc4db4 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 27 Oct 2021 10:58:38 +0800 Subject: [PATCH 25/39] Refactoring --- src/query/insert.rs | 8 +++----- src/query/select.rs | 10 ++++------ src/query/update.rs | 8 +++----- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/query/insert.rs b/src/query/insert.rs index 8719e0118..f8b151d7b 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -125,7 +125,6 @@ where let av = am.take(col); let av_has_val = av.is_set() || av.is_unchanged(); let col_def = col.def(); - let enum_name = col_def.get_column_type().get_enum_name(); if columns_empty { self.columns.push(av_has_val); @@ -135,10 +134,9 @@ where if av_has_val { columns.push(col); let val = Expr::val(av.into_value().unwrap()); - let expr = if let Some(enum_name) = enum_name { - val.as_enum(Alias::new(enum_name)) - } else { - val.into() + let expr = match col_def.get_column_type().get_enum_name() { + Some(enum_name) => val.as_enum(Alias::new(enum_name)), + None => val.into(), }; values.push(expr); } diff --git a/src/query/select.rs b/src/query/select.rs index 5c6d1f3b2..037527c76 100644 --- a/src/query/select.rs +++ b/src/query/select.rs @@ -119,12 +119,10 @@ where E::Column::iter() .map(|col| { let col_def = col.def(); - let enum_name = col_def.get_column_type().get_enum_name(); - let col_expr = Expr::tbl(table.clone(), col); - if enum_name.is_some() { - col_expr.as_enum(text_type.clone()) - } else { - col_expr.into() + let expr = Expr::tbl(table.clone(), col); + match col_def.get_column_type().get_enum_name() { + Some(_) => expr.as_enum(text_type.clone()), + None => expr.into(), } }) .collect() diff --git a/src/query/update.rs b/src/query/update.rs index 6b6f48f83..7c2e9c2e6 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -105,14 +105,12 @@ where continue; } let col_def = col.def(); - let enum_name = col_def.get_column_type().get_enum_name(); let av = self.model.get(col); if av.is_set() { let val = Expr::val(av.into_value().unwrap()); - let expr = if let Some(enum_name) = enum_name { - val.as_enum(Alias::new(enum_name)) - } else { - val.into() + let expr = match col_def.get_column_type().get_enum_name() { + Some(enum_name) => val.as_enum(Alias::new(enum_name)), + None => val.into(), }; self.query.value_expr(col, expr); } From e04495b94d2e1849132ce4bd679ce32f4d7a4756 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 27 Oct 2021 11:28:33 +0800 Subject: [PATCH 26/39] Refactoring --- src/query/insert.rs | 4 +--- src/query/select.rs | 3 +-- src/query/update.rs | 3 +-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/query/insert.rs b/src/query/insert.rs index f8b151d7b..f7f77d7d0 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -124,8 +124,6 @@ where for (idx, col) in ::Column::iter().enumerate() { let av = am.take(col); let av_has_val = av.is_set() || av.is_unchanged(); - let col_def = col.def(); - if columns_empty { self.columns.push(av_has_val); } else if self.columns[idx] != av_has_val { @@ -134,7 +132,7 @@ where if av_has_val { columns.push(col); let val = Expr::val(av.into_value().unwrap()); - let expr = match col_def.get_column_type().get_enum_name() { + let expr = match col.def().get_column_type().get_enum_name() { Some(enum_name) => val.as_enum(Alias::new(enum_name)), None => val.into(), }; diff --git a/src/query/select.rs b/src/query/select.rs index 037527c76..5e433e8a4 100644 --- a/src/query/select.rs +++ b/src/query/select.rs @@ -118,9 +118,8 @@ where let text_type = SeaRc::new(Alias::new("text")) as DynIden; E::Column::iter() .map(|col| { - let col_def = col.def(); let expr = Expr::tbl(table.clone(), col); - match col_def.get_column_type().get_enum_name() { + match col.def().get_column_type().get_enum_name() { Some(_) => expr.as_enum(text_type.clone()), None => expr.into(), } diff --git a/src/query/update.rs b/src/query/update.rs index 7c2e9c2e6..89348229a 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -104,11 +104,10 @@ where if ::PrimaryKey::from_column(col).is_some() { continue; } - let col_def = col.def(); let av = self.model.get(col); if av.is_set() { let val = Expr::val(av.into_value().unwrap()); - let expr = match col_def.get_column_type().get_enum_name() { + let expr = match col.def().get_column_type().get_enum_name() { Some(enum_name) => val.as_enum(Alias::new(enum_name)), None => val.into(), }; From 55de1968bbb049c506c60b2df9f84a252ce0e4ad Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 27 Oct 2021 12:37:35 +0800 Subject: [PATCH 27/39] Add `create_enum_from_entity` --- src/database/statement.rs | 24 ++++++++++++++ src/schema/entity.rs | 39 ++++++++++++++++++++++- tests/common/features/schema.rs | 51 +++++++++--------------------- tests/common/setup/mod.rs | 55 +++++++++++++++++++++++++++++++-- 4 files changed, 129 insertions(+), 40 deletions(-) diff --git a/src/database/statement.rs b/src/database/statement.rs index 12b074873..86d7c630d 100644 --- a/src/database/statement.rs +++ b/src/database/statement.rs @@ -73,6 +73,15 @@ macro_rules! build_any_stmt { }; } +macro_rules! build_postgres_stmt { + ($stmt: expr, $db_backend: expr) => { + match $db_backend { + DbBackend::Postgres => $stmt.to_string(PostgresQueryBuilder), + DbBackend::MySql | DbBackend::Sqlite => unimplemented!(), + } + }; +} + macro_rules! build_query_stmt { ($stmt: ty) => { impl StatementBuilder for $stmt { @@ -105,3 +114,18 @@ build_schema_stmt!(sea_query::TableDropStatement); build_schema_stmt!(sea_query::TableAlterStatement); build_schema_stmt!(sea_query::TableRenameStatement); build_schema_stmt!(sea_query::TableTruncateStatement); + +macro_rules! build_type_stmt { + ($stmt: ty) => { + impl StatementBuilder for $stmt { + fn build(&self, db_backend: &DbBackend) -> Statement { + let stmt = build_postgres_stmt!(self, db_backend); + Statement::from_string(*db_backend, stmt) + } + } + }; +} + +build_type_stmt!(sea_query::extension::postgres::TypeAlterStatement); +build_type_stmt!(sea_query::extension::postgres::TypeCreateStatement); +build_type_stmt!(sea_query::extension::postgres::TypeDropStatement); diff --git a/src/schema/entity.rs b/src/schema/entity.rs index 3f99f8928..238bde36e 100644 --- a/src/schema/entity.rs +++ b/src/schema/entity.rs @@ -2,9 +2,19 @@ use crate::{ unpack_table_ref, ColumnTrait, ColumnType, DbBackend, EntityTrait, Identity, Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, RelationTrait, Schema, }; -use sea_query::{ColumnDef, ForeignKeyCreateStatement, Iden, Index, TableCreateStatement}; +use sea_query::{ + extension::postgres::{Type, TypeCreateStatement}, + Alias, ColumnDef, ForeignKeyCreateStatement, Iden, Index, TableCreateStatement, +}; impl Schema { + pub fn create_enum_from_entity(entity: E, db_backend: DbBackend) -> Vec + where + E: EntityTrait, + { + create_enum_from_entity(entity, db_backend) + } + pub fn create_table_from_entity(entity: E, db_backend: DbBackend) -> TableCreateStatement where E: EntityTrait, @@ -13,6 +23,33 @@ impl Schema { } } +pub(crate) fn create_enum_from_entity(_: E, db_backend: DbBackend) -> Vec +where + E: EntityTrait, +{ + if matches!(db_backend, DbBackend::MySql | DbBackend::Sqlite) { + return Vec::new(); + } + let mut vec = Vec::new(); + for col in E::Column::iter() { + let col_def = col.def(); + let col_type = col_def.get_column_type(); + if !matches!(col_type, ColumnType::Enum(_, _)) { + continue; + } + let (name, values) = match col_type { + ColumnType::Enum(s, v) => (s.as_str(), v), + _ => unreachable!(), + }; + let stmt = Type::create() + .as_enum(Alias::new(name)) + .values(values.into_iter().map(|val| Alias::new(val.as_str()))) + .to_owned(); + vec.push(stmt); + } + vec +} + pub(crate) fn create_table_from_entity(entity: E, db_backend: DbBackend) -> TableCreateStatement where E: EntityTrait, diff --git a/tests/common/features/schema.rs b/tests/common/features/schema.rs index bbc9faf8b..7e9f3de7b 100644 --- a/tests/common/features/schema.rs +++ b/tests/common/features/schema.rs @@ -1,14 +1,11 @@ pub use super::super::bakery_chain::*; use super::*; -use crate::common::setup::create_table; +use crate::{common::setup::create_table, create_enum}; use sea_orm::{ error::*, sea_query, ConnectionTrait, DatabaseConnection, DbBackend, DbConn, ExecResult, - Statement, -}; -use sea_query::{ - extension::postgres::Type, Alias, ColumnDef, ForeignKeyCreateStatement, PostgresQueryBuilder, }; +use sea_query::{extension::postgres::Type, Alias, ColumnDef, ForeignKeyCreateStatement}; pub async fn create_tables(db: &DatabaseConnection) -> Result<(), DbErr> { create_log_table(db).await?; @@ -111,14 +108,23 @@ pub async fn create_active_enum_table(db: &DbConn) -> Result let db_backend = db.get_database_backend(); let tea_enum = Alias::new("tea"); + let create_enum_stmts = match db_backend { + DbBackend::MySql | DbBackend::Sqlite => Vec::new(), + DbBackend::Postgres => vec![Type::create() + .as_enum(tea_enum.clone()) + .values(vec![Alias::new("EverydayTea"), Alias::new("BreakfastTea")]) + .to_owned()], + }; + + create_enum(db, &create_enum_stmts, ActiveEnum).await?; + let mut tea_col = ColumnDef::new(active_enum::Column::Tea); match db_backend { DbBackend::MySql => tea_col.custom(Alias::new("ENUM('EverydayTea', 'BreakfastTea')")), - DbBackend::Postgres => tea_col.custom(tea_enum.clone()), DbBackend::Sqlite => tea_col.text(), + DbBackend::Postgres => tea_col.custom(tea_enum), }; - - let stmt = sea_query::Table::create() + let create_table_stmt = sea_query::Table::create() .table(active_enum::Entity) .col( ColumnDef::new(active_enum::Column::Id) @@ -132,32 +138,5 @@ pub async fn create_active_enum_table(db: &DbConn) -> Result .col(&mut tea_col) .to_owned(); - if db_backend == DbBackend::Postgres { - let drop_type_stmt = Type::drop() - .name(tea_enum.clone()) - .cascade() - .if_exists() - .to_owned(); - let (sql, values) = drop_type_stmt.build(PostgresQueryBuilder); - let stmt = Statement::from_sql_and_values(db.get_database_backend(), &sql, values); - db.execute(stmt).await?; - - let create_type_stmt = Type::create() - .as_enum(tea_enum) - .values(vec![Alias::new("EverydayTea"), Alias::new("BreakfastTea")]) - .to_owned(); - // FIXME: This is not working - { - let (sql, values) = create_type_stmt.build(PostgresQueryBuilder); - let _stmt = Statement::from_sql_and_values(db.get_database_backend(), &sql, values); - } - // But this is working... - let stmt = Statement::from_string( - db.get_database_backend(), - create_type_stmt.to_string(PostgresQueryBuilder), - ); - db.execute(stmt).await?; - } - - create_table(db, &stmt, ActiveEnum).await + create_table(db, &create_table_stmt, ActiveEnum).await } diff --git a/tests/common/setup/mod.rs b/tests/common/setup/mod.rs index 7266d1759..263e19f2f 100644 --- a/tests/common/setup/mod.rs +++ b/tests/common/setup/mod.rs @@ -1,9 +1,12 @@ use pretty_assertions::assert_eq; use sea_orm::{ - ConnectionTrait, Database, DatabaseBackend, DatabaseConnection, DbBackend, DbConn, DbErr, - EntityTrait, ExecResult, Schema, Statement, + ColumnTrait, ColumnType, ConnectionTrait, Database, DatabaseBackend, DatabaseConnection, + DbBackend, DbConn, DbErr, EntityTrait, ExecResult, Iterable, Schema, Statement, +}; +use sea_query::{ + extension::postgres::{Type, TypeCreateStatement}, + Alias, Table, TableCreateStatement, }; -use sea_query::{Alias, Table, TableCreateStatement}; pub async fn setup(base_url: &str, db_name: &str) -> DatabaseConnection { let db = if cfg!(feature = "sqlx-mysql") { @@ -74,6 +77,52 @@ pub async fn tear_down(base_url: &str, db_name: &str) { }; } +pub async fn create_enum( + db: &DbConn, + creates: &[TypeCreateStatement], + entity: E, +) -> Result<(), DbErr> +where + E: EntityTrait, +{ + let builder = db.get_database_backend(); + if builder == DbBackend::Postgres { + for col in E::Column::iter() { + let col_def = col.def(); + let col_type = col_def.get_column_type(); + if !matches!(col_type, ColumnType::Enum(_, _)) { + continue; + } + let name = match col_type { + ColumnType::Enum(s, _) => s.as_str(), + _ => unreachable!(), + }; + let drop_type_stmt = Type::drop() + .name(Alias::new(name)) + .if_exists() + .cascade() + .to_owned(); + let stmt = builder.build(&drop_type_stmt); + db.execute(stmt).await?; + } + } + + let expect_stmts: Vec = creates.iter().map(|stmt| builder.build(stmt)).collect(); + let create_from_entity_stmts: Vec = + Schema::create_enum_from_entity(entity, db.get_database_backend()) + .iter() + .map(|stmt| builder.build(stmt)) + .collect(); + + assert_eq!(expect_stmts, create_from_entity_stmts); + + for stmt in expect_stmts { + db.execute(stmt).await.map(|_| ())?; + } + + Ok(()) +} + pub async fn create_table( db: &DbConn, create: &TableCreateStatement, From f88c7259fe116c1bbcba69564e2090df4de17b70 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 27 Oct 2021 12:50:02 +0800 Subject: [PATCH 28/39] Fixup --- tests/common/features/schema.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/common/features/schema.rs b/tests/common/features/schema.rs index 7e9f3de7b..977b4b61c 100644 --- a/tests/common/features/schema.rs +++ b/tests/common/features/schema.rs @@ -1,7 +1,7 @@ pub use super::super::bakery_chain::*; use super::*; -use crate::{common::setup::create_table, create_enum}; +use crate::common::setup::{create_table, create_enum}; use sea_orm::{ error::*, sea_query, ConnectionTrait, DatabaseConnection, DbBackend, DbConn, ExecResult, }; From e21af533742500dfe03fe688c915affc720cfb60 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 27 Oct 2021 15:23:21 +0800 Subject: [PATCH 29/39] Fix clippy warnings --- src/schema/entity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schema/entity.rs b/src/schema/entity.rs index 238bde36e..d99a41b92 100644 --- a/src/schema/entity.rs +++ b/src/schema/entity.rs @@ -43,7 +43,7 @@ where }; let stmt = Type::create() .as_enum(Alias::new(name)) - .values(values.into_iter().map(|val| Alias::new(val.as_str()))) + .values(values.iter().map(|val| Alias::new(val.as_str()))) .to_owned(); vec.push(stmt); } From a95ee3123f8e39e95a28be6591d6d52724e940c2 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 2 Nov 2021 12:25:34 +0800 Subject: [PATCH 30/39] Use sea-query master --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f45eeb556..2345844cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ futures-util = { version = "^0.3" } log = { version = "^0.4", optional = true } rust_decimal = { version = "^1", optional = true } sea-orm-macros = { version = "^0.3.1", path = "sea-orm-macros", optional = true } -sea-query = { version = "^0.18.0", git = "https://github.com/SeaQL/sea-query.git", branch = "sea-orm/active-enum-1", features = ["thread-safe"] } +sea-query = { version = "^0.18.0", git = "https://github.com/SeaQL/sea-query.git", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1", optional = true } From 65542301da5ec2d07d320f64e52c67de27ac5f17 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 2 Nov 2021 14:25:18 +0800 Subject: [PATCH 31/39] Add docs --- src/entity/column.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/entity/column.rs b/src/entity/column.rs index cc93bde42..865eccae7 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -62,6 +62,7 @@ pub enum ColumnType { Custom(String), /// A Universally Unique IDentifier that is specified in RFC 4122 Uuid, + /// `ENUM` data type with name and variants Enum(String, Vec), } @@ -315,6 +316,7 @@ impl ColumnDef { self } + /// Get [ColumnType] as reference pub fn get_column_type(&self) -> &ColumnType { &self.col_type } From 858e1e047ddef4576251e0a76f40e75247a0c67e Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 2 Nov 2021 14:34:08 +0800 Subject: [PATCH 32/39] Update docs --- src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index e2f24c0b5..5289c45f8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,7 +11,7 @@ pub enum DbErr { RecordNotFound(String), /// A custom error Custom(String), - /// Error occurred while parsing value into [ActiveEnum](crate::ActiveEnum) + /// Error occurred while parsing value as target type Type(String), } From 2f7c9ccda72f0ea463ca565f36662ae401ff681a Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 3 Nov 2021 10:56:06 +0800 Subject: [PATCH 33/39] Refactoring --- tests/common/features/schema.rs | 2 +- tests/common/setup/mod.rs | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/common/features/schema.rs b/tests/common/features/schema.rs index 94256c055..942776047 100644 --- a/tests/common/features/schema.rs +++ b/tests/common/features/schema.rs @@ -1,7 +1,7 @@ pub use super::super::bakery_chain::*; use super::*; -use crate::common::setup::{create_table, create_enum, create_table_without_asserts}; +use crate::common::setup::{create_enum, create_table, create_table_without_asserts}; use sea_orm::{ error::*, sea_query, ConnectionTrait, DatabaseConnection, DbBackend, DbConn, ExecResult, }; diff --git a/tests/common/setup/mod.rs b/tests/common/setup/mod.rs index 4926f68af..fae61984b 100644 --- a/tests/common/setup/mod.rs +++ b/tests/common/setup/mod.rs @@ -108,11 +108,10 @@ where } let expect_stmts: Vec = creates.iter().map(|stmt| builder.build(stmt)).collect(); - let create_from_entity_stmts: Vec = - Schema::create_enum_from_entity(entity, db.get_database_backend()) - .iter() - .map(|stmt| builder.build(stmt)) - .collect(); + let create_from_entity_stmts: Vec = Schema::create_enum_from_entity(entity, builder) + .iter() + .map(|stmt| builder.build(stmt)) + .collect(); assert_eq!(expect_stmts, create_from_entity_stmts); @@ -133,7 +132,7 @@ where { let builder = db.get_database_backend(); assert_eq!( - builder.build(&Schema::create_table_from_entity(entity)), + builder.build(&Schema::create_table_from_entity(entity, builder)), builder.build(create) ); From bb78a1d7097be2fc6cecceb547580ff8fd47250b Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 3 Nov 2021 15:38:42 +0800 Subject: [PATCH 34/39] More ergonomic `DeriveActiveEnum` derive macro --- sea-orm-macros/src/derives/active_enum.rs | 27 +++++++++++- sea-orm-macros/src/lib.rs | 3 ++ src/entity/active_enum.rs | 50 ++++++++++++++++++----- tests/common/features/active_enum.rs | 11 ++--- 4 files changed, 71 insertions(+), 20 deletions(-) diff --git a/sea-orm-macros/src/derives/active_enum.rs b/sea-orm-macros/src/derives/active_enum.rs index cfd5454dc..acb60a36c 100644 --- a/sea-orm-macros/src/derives/active_enum.rs +++ b/sea-orm-macros/src/derives/active_enum.rs @@ -1,3 +1,4 @@ +use heck::CamelCase; use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; use syn::{punctuated::Punctuated, token::Comma, Lit, LitInt, LitStr, Meta}; @@ -10,6 +11,7 @@ enum Error { struct ActiveEnum { ident: syn::Ident, + enum_name: String, rs_type: TokenStream, db_type: TokenStream, is_string: bool, @@ -27,6 +29,7 @@ impl ActiveEnum { let ident_span = input.ident.span(); let ident = input.ident; + let mut enum_name = ident.to_string().to_camel_case(); let mut rs_type = Err(Error::TT(quote_spanned! { ident_span => compile_error!("Missing macro attribute `rs_type`"); })); @@ -52,8 +55,22 @@ impl ActiveEnum { } } else if name == "db_type" { if let Lit::Str(litstr) = &nv.lit { - db_type = syn::parse_str::(&litstr.value()) - .map_err(Error::Syn); + let s = litstr.value(); + match s.as_ref() { + "Enum" => { + db_type = Ok(quote! { + Enum(Self::name(), Self::values()) + }) + } + _ => { + db_type = syn::parse_str::(&s) + .map_err(Error::Syn); + } + } + } + } else if name == "enum_name" { + if let Lit::Str(litstr) = &nv.lit { + enum_name = litstr.value(); } } } @@ -125,6 +142,7 @@ impl ActiveEnum { Ok(ActiveEnum { ident, + enum_name, rs_type: rs_type?, db_type: db_type?, is_string, @@ -141,6 +159,7 @@ impl ActiveEnum { fn impl_active_enum(&self) -> TokenStream { let Self { ident, + enum_name, rs_type, db_type, is_string, @@ -181,6 +200,10 @@ impl ActiveEnum { impl sea_orm::ActiveEnum for #ident { type Value = #rs_type; + fn name() -> String { + #enum_name.to_owned() + } + fn to_value(&self) -> Self::Value { match self { #( Self::#variant_idents => #variant_values, )* diff --git a/sea-orm-macros/src/lib.rs b/sea-orm-macros/src/lib.rs index 1db680b02..00540aa4a 100644 --- a/sea-orm-macros/src/lib.rs +++ b/sea-orm-macros/src/lib.rs @@ -509,6 +509,9 @@ pub fn derive_active_model_behavior(input: TokenStream) -> TokenStream { /// - `db_type`: Define `ColumnType` returned by `ActiveEnum::db_type()` /// - Possible values: all available enum variants of `ColumnType`, e.g. `String(None)`, `String(Some(1))`, `Integer` /// - Note that value has to be passed as string, i.e. `db_type = "Integer"` +/// - `enum_name`: Define `String` returned by `ActiveEnum::name()` +/// - This attribute is optional with default value being the name of enum in camel-case +/// - Note that value has to be passed as string, i.e. `db_type = "Integer"` /// /// - For enum variant /// - `string_value` or `num_value`: diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs index 5eb77b9f2..104b573fe 100644 --- a/src/entity/active_enum.rs +++ b/src/entity/active_enum.rs @@ -1,4 +1,4 @@ -use crate::{ColumnDef, DbErr, TryGetable}; +use crate::{ColumnDef, DbErr, Iterable, TryGetable}; use sea_query::{Nullable, Value, ValueType}; /// A Rust representation of enum defined in database. @@ -17,8 +17,12 @@ use sea_query::{Nullable, Value, ValueType}; /// use sea_orm::entity::prelude::*; /// /// // Using the derive macro -/// #[derive(Debug, PartialEq, DeriveActiveEnum)] -/// #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] +/// #[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)] +/// #[sea_orm( +/// rs_type = "String", +/// db_type = "String(Some(1))", +/// enum_name = "category" +/// )] /// pub enum DeriveCategory { /// #[sea_orm(string_value = "B")] /// Big, @@ -27,7 +31,7 @@ use sea_query::{Nullable, Value, ValueType}; /// } /// /// // Implementing it manually -/// #[derive(Debug, PartialEq)] +/// #[derive(Debug, PartialEq, EnumIter)] /// pub enum Category { /// Big, /// Small, @@ -38,6 +42,11 @@ use sea_query::{Nullable, Value, ValueType}; /// type Value = String; /// /// // Will be atomically generated by `DeriveActiveEnum` +/// fn name() -> String { +/// "category".to_owned() +/// } +/// +/// // Will be atomically generated by `DeriveActiveEnum` /// fn to_value(&self) -> Self::Value { /// match self { /// Self::Big => "B", @@ -71,7 +80,7 @@ use sea_query::{Nullable, Value, ValueType}; /// use sea_orm::entity::prelude::*; /// /// // Define the `Category` active enum -/// #[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] +/// #[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)] /// #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] /// pub enum Category { /// #[sea_orm(string_value = "B")] @@ -95,10 +104,13 @@ use sea_query::{Nullable, Value, ValueType}; /// /// impl ActiveModelBehavior for ActiveModel {} /// ``` -pub trait ActiveEnum: Sized { +pub trait ActiveEnum: Sized + Iterable { /// Define the Rust type that each enum variant represents. type Value: Into + ValueType + Nullable + TryGetable; + /// Get the name of enum + fn name() -> String; + /// Convert enum variant into the corresponding value. fn to_value(&self) -> Self::Value; @@ -107,6 +119,11 @@ pub trait ActiveEnum: Sized { /// Get the database column definition of this active enum. fn db_type() -> ColumnDef; + + /// Get the name of all enum variants + fn values() -> Vec { + Self::iter().map(|s| s.to_value()).collect() + } } #[cfg(test)] @@ -117,7 +134,7 @@ mod tests { #[test] fn active_enum_string() { - #[derive(Debug, PartialEq)] + #[derive(Debug, PartialEq, EnumIter)] pub enum Category { Big, Small, @@ -126,6 +143,10 @@ mod tests { impl ActiveEnum for Category { type Value = String; + fn name() -> String { + "category".to_owned() + } + fn to_value(&self) -> Self::Value { match self { Self::Big => "B", @@ -150,8 +171,12 @@ mod tests { } } - #[derive(Debug, PartialEq, DeriveActiveEnum)] - #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] + #[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)] + #[sea_orm( + rs_type = "String", + db_type = "String(Some(1))", + enum_name = "category" + )] pub enum DeriveCategory { #[sea_orm(string_value = "B")] Big, @@ -195,13 +220,16 @@ mod tests { assert_eq!(Category::db_type(), ColumnType::String(Some(1)).def()); assert_eq!(DeriveCategory::db_type(), ColumnType::String(Some(1)).def()); + + assert_eq!(Category::name(), DeriveCategory::name()); + assert_eq!(Category::values(), DeriveCategory::values()); } #[test] fn active_enum_derive_signed_integers() { macro_rules! test_int { ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => { - #[derive(Debug, PartialEq, DeriveActiveEnum)] + #[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = $rs_type, db_type = $db_type)] pub enum $ident { #[sea_orm(num_value = 1)] @@ -241,7 +269,7 @@ mod tests { fn active_enum_derive_unsigned_integers() { macro_rules! test_uint { ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => { - #[derive(Debug, PartialEq, DeriveActiveEnum)] + #[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = $rs_type, db_type = $db_type)] pub enum $ident { #[sea_orm(num_value = 1)] diff --git a/tests/common/features/active_enum.rs b/tests/common/features/active_enum.rs index f152f37c3..5285c5d94 100644 --- a/tests/common/features/active_enum.rs +++ b/tests/common/features/active_enum.rs @@ -15,7 +15,7 @@ pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} -#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = "String", db_type = "String(Some(1))")] pub enum Category { #[sea_orm(string_value = "B")] @@ -24,7 +24,7 @@ pub enum Category { Small, } -#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)] #[sea_orm(rs_type = "i32", db_type = r#"Integer"#)] pub enum Color { #[sea_orm(num_value = 0)] @@ -33,11 +33,8 @@ pub enum Color { White, } -#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)] -#[sea_orm( - rs_type = "String", - db_type = r#"Enum("tea".to_owned(), vec!["EverydayTea".to_owned(), "BreakfastTea".to_owned()])"# -)] +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")] pub enum Tea { #[sea_orm(string_value = "EverydayTea")] EverydayTea, From 2b841b1b5d5bcea74ffc0cd99951c7f9a0955be5 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 4 Nov 2021 10:49:50 +0800 Subject: [PATCH 35/39] Refactoring --- src/entity/active_enum.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs index 104b573fe..292389412 100644 --- a/src/entity/active_enum.rs +++ b/src/entity/active_enum.rs @@ -120,9 +120,14 @@ pub trait ActiveEnum: Sized + Iterable { /// Get the database column definition of this active enum. fn db_type() -> ColumnDef; + /// Convert an owned enum variant into the corresponding value. + fn into_value(self) -> Self::Value { + Self::to_value(&self) + } + /// Get the name of all enum variants fn values() -> Vec { - Self::iter().map(|s| s.to_value()).collect() + Self::iter().map(Self::into_value).collect() } } From f64f1e9216eaee57822ef2071960ff27072d06ec Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 4 Nov 2021 11:01:29 +0800 Subject: [PATCH 36/39] `DeriveActiveEnum` generate code that depends on sea-query inside sea-orm --- sea-orm-macros/src/derives/active_enum.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/sea-orm-macros/src/derives/active_enum.rs b/sea-orm-macros/src/derives/active_enum.rs index acb60a36c..bafb19086 100644 --- a/sea-orm-macros/src/derives/active_enum.rs +++ b/sea-orm-macros/src/derives/active_enum.rs @@ -229,8 +229,8 @@ impl ActiveEnum { #[automatically_derived] #[allow(clippy::from_over_into)] - impl Into for #ident { - fn into(self) -> sea_query::Value { + impl Into for #ident { + fn into(self) -> sea_orm::sea_query::Value { ::to_value(&self).into() } } @@ -244,17 +244,17 @@ impl ActiveEnum { } #[automatically_derived] - impl sea_query::ValueType for #ident { - fn try_from(v: sea_query::Value) -> Result { - let value = <::Value as sea_query::ValueType>::try_from(v)?; - ::try_from_value(&value).map_err(|_| sea_query::ValueTypeErr) + impl sea_orm::sea_query::ValueType for #ident { + fn try_from(v: sea_orm::sea_query::Value) -> Result { + let value = <::Value as sea_orm::sea_query::ValueType>::try_from(v)?; + ::try_from_value(&value).map_err(|_| sea_orm::sea_query::ValueTypeErr) } fn type_name() -> String { - <::Value as sea_query::ValueType>::type_name() + <::Value as sea_orm::sea_query::ValueType>::type_name() } - fn column_type() -> sea_query::ColumnType { + fn column_type() -> sea_orm::sea_query::ColumnType { ::db_type() .get_column_type() .to_owned() @@ -263,9 +263,9 @@ impl ActiveEnum { } #[automatically_derived] - impl sea_query::Nullable for #ident { - fn null() -> sea_query::Value { - <::Value as sea_query::Nullable>::null() + impl sea_orm::sea_query::Nullable for #ident { + fn null() -> sea_orm::sea_query::Value { + <::Value as sea_orm::sea_query::Nullable>::null() } } ) From 67bb168e4944a147d02ccf78d2bf16aabd4ef1b5 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 4 Nov 2021 11:40:05 +0800 Subject: [PATCH 37/39] Correctly apply filters on enum columns --- src/entity/column.rs | 23 ++++++++++++++--------- tests/active_enum_tests.rs | 28 ++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/entity/column.rs b/src/entity/column.rs index 865eccae7..d1910f1d8 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -1,5 +1,5 @@ use crate::{EntityName, IdenStatic, Iterable}; -use sea_query::{DynIden, Expr, SeaRc, SelectStatement, SimpleExpr, Value}; +use sea_query::{Alias, BinOper, DynIden, Expr, SeaRc, SelectStatement, SimpleExpr, Value}; use std::str::FromStr; /// Defines a Column for an Entity @@ -67,13 +67,18 @@ pub enum ColumnType { } macro_rules! bind_oper { - ( $op: ident ) => { + ( $op: ident, $bin_op: ident ) => { #[allow(missing_docs)] fn $op(&self, v: V) -> SimpleExpr where V: Into, { - Expr::tbl(self.entity_name(), *self).$op(v) + let val = Expr::val(v); + let expr = match self.def().get_column_type().get_enum_name() { + Some(enum_name) => val.as_enum(Alias::new(enum_name)), + None => val.into(), + }; + Expr::tbl(self.entity_name(), *self).binary(BinOper::$bin_op, expr) } }; } @@ -130,12 +135,12 @@ pub trait ColumnTrait: IdenStatic + Iterable + FromStr { (self.entity_name(), SeaRc::new(*self) as DynIden) } - bind_oper!(eq); - bind_oper!(ne); - bind_oper!(gt); - bind_oper!(gte); - bind_oper!(lt); - bind_oper!(lte); + bind_oper!(eq, Equal); + bind_oper!(ne, NotEqual); + bind_oper!(gt, GreaterThan); + bind_oper!(gte, GreaterThanOrEqual); + bind_oper!(lt, SmallerThan); + bind_oper!(lte, SmallerThanOrEqual); /// ``` /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; diff --git a/tests/active_enum_tests.rs b/tests/active_enum_tests.rs index ca9bf7d99..aaad419b2 100644 --- a/tests/active_enum_tests.rs +++ b/tests/active_enum_tests.rs @@ -30,8 +30,9 @@ pub async fn insert_active_enum(db: &DatabaseConnection) -> Result<(), DbErr> { .insert(db) .await?; + let model = Entity::find().one(db).await?.unwrap(); assert_eq!( - Entity::find().one(db).await?.unwrap(), + model, Model { id: 1, category: None, @@ -39,6 +40,17 @@ pub async fn insert_active_enum(db: &DatabaseConnection) -> Result<(), DbErr> { tea: None, } ); + assert_eq!( + model, + Entity::find() + .filter(Column::Id.is_not_null()) + .filter(Column::Category.is_null()) + .filter(Column::Color.is_null()) + .filter(Column::Tea.is_null()) + .one(db) + .await? + .unwrap() + ); let am = ActiveModel { category: Set(Some(Category::Big)), @@ -49,8 +61,9 @@ pub async fn insert_active_enum(db: &DatabaseConnection) -> Result<(), DbErr> { .save(db) .await?; + let model = Entity::find().one(db).await?.unwrap(); assert_eq!( - Entity::find().one(db).await?.unwrap(), + model, Model { id: 1, category: Some(Category::Big), @@ -58,6 +71,17 @@ pub async fn insert_active_enum(db: &DatabaseConnection) -> Result<(), DbErr> { tea: Some(Tea::EverydayTea), } ); + assert_eq!( + model, + Entity::find() + .filter(Column::Id.eq(1)) + .filter(Column::Category.eq(Category::Big)) + .filter(Column::Color.eq(Color::Black)) + .filter(Column::Tea.eq(Tea::EverydayTea)) + .one(db) + .await? + .unwrap() + ); let res = am.delete(db).await?; From 10f3de0f9da0738903c6cb962d11e371f1d2f278 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 5 Nov 2021 11:13:31 +0800 Subject: [PATCH 38/39] Only `eq` & `ne` operators with enum casting --- src/entity/column.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/entity/column.rs b/src/entity/column.rs index d1910f1d8..fdb7d4869 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -67,6 +67,18 @@ pub enum ColumnType { } macro_rules! bind_oper { + ( $op: ident ) => { + #[allow(missing_docs)] + fn $op(&self, v: V) -> SimpleExpr + where + V: Into, + { + Expr::tbl(self.entity_name(), *self).$op(v) + } + }; +} + +macro_rules! bind_oper_with_enum_casting { ( $op: ident, $bin_op: ident ) => { #[allow(missing_docs)] fn $op(&self, v: V) -> SimpleExpr @@ -135,12 +147,12 @@ pub trait ColumnTrait: IdenStatic + Iterable + FromStr { (self.entity_name(), SeaRc::new(*self) as DynIden) } - bind_oper!(eq, Equal); - bind_oper!(ne, NotEqual); - bind_oper!(gt, GreaterThan); - bind_oper!(gte, GreaterThanOrEqual); - bind_oper!(lt, SmallerThan); - bind_oper!(lte, SmallerThanOrEqual); + bind_oper_with_enum_casting!(eq, Equal); + bind_oper_with_enum_casting!(ne, NotEqual); + bind_oper!(gt); + bind_oper!(gte); + bind_oper!(lt); + bind_oper!(lte); /// ``` /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; From 47e2486ead40733ffcb18e03f65d648480c5f179 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 5 Nov 2021 16:25:55 +0800 Subject: [PATCH 39/39] Refactoring --- src/entity/column.rs | 4 +++- src/query/insert.rs | 4 +++- src/query/select.rs | 4 +++- src/query/update.rs | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/entity/column.rs b/src/entity/column.rs index fdb7d4869..d5234b312 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -86,7 +86,9 @@ macro_rules! bind_oper_with_enum_casting { V: Into, { let val = Expr::val(v); - let expr = match self.def().get_column_type().get_enum_name() { + let col_def = self.def(); + let col_type = col_def.get_column_type(); + let expr = match col_type.get_enum_name() { Some(enum_name) => val.as_enum(Alias::new(enum_name)), None => val.into(), }; diff --git a/src/query/insert.rs b/src/query/insert.rs index 9d4d6ce5d..7cafc44c6 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -134,7 +134,9 @@ where if av_has_val { columns.push(col); let val = Expr::val(av.into_value().unwrap()); - let expr = match col.def().get_column_type().get_enum_name() { + let col_def = col.def(); + let col_type = col_def.get_column_type(); + let expr = match col_type.get_enum_name() { Some(enum_name) => val.as_enum(Alias::new(enum_name)), None => val.into(), }; diff --git a/src/query/select.rs b/src/query/select.rs index 06158be05..3c9829480 100644 --- a/src/query/select.rs +++ b/src/query/select.rs @@ -124,7 +124,9 @@ where E::Column::iter() .map(|col| { let expr = Expr::tbl(table.clone(), col); - match col.def().get_column_type().get_enum_name() { + let col_def = col.def(); + let col_type = col_def.get_column_type(); + match col_type.get_enum_name() { Some(_) => expr.as_enum(text_type.clone()), None => expr.into(), } diff --git a/src/query/update.rs b/src/query/update.rs index d1b6b997e..f12d3b950 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -110,7 +110,9 @@ where let av = self.model.get(col); if av.is_set() { let val = Expr::val(av.into_value().unwrap()); - let expr = match col.def().get_column_type().get_enum_name() { + let col_def = col.def(); + let col_type = col_def.get_column_type(); + let expr = match col_type.get_enum_name() { Some(enum_name) => val.as_enum(Alias::new(enum_name)), None => val.into(), };