From 8627c8d961f5c53c720b8aa16046d887a0d1c589 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 19 Oct 2021 19:08:02 +0800 Subject: [PATCH 01/45] 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 00000000..6f6b7fbc --- /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 3b0dac26..0963c2ca 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 6ba19a92..36b9f669 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 cf8c2f3c..ccba2fab 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 00000000..68c58c64 --- /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 a27215e5..25ed8447 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 c6d15052..7e8b7830 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 ac4d50fa..98f89c92 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 745692b9..d40db473 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 00000000..90472fde --- /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 00000000..1b68d546 --- /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 ff716f01..f0db35b7 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 44b011c5..5292f4fc 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/45] 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 5292f4fc..70752d35 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/45] 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 6f6b7fbc..cd2e1c0a 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/45] 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 68c58c64..7e17000f 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/45] More tests --- src/entity/active_enum.rs | 142 ++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 74 deletions(-) diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs index 7e17000f..8c99e94c 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, + 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, } - .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 - ))), + 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()); + }; + } + + test_int!(I8, "i8", "TinyInteger", TinyInteger); + test_int!(I16, "i16", "SmallInteger", SmallInteger); + test_int!(I32, "i32", "Integer", Integer); + test_int!(I64, "i64", "BigInteger", BigInteger); + } + + #[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, } - } - fn db_type() -> ColumnDef { - ColumnType::Integer.def() - } + 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()); + }; } - #[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()); + 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/45] 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 1b68d546..e1aef0fe 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/45] 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 cd2e1c0a..6823405d 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/45] 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 8c99e94c..b41779eb 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/45] 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 ccba2fab..f8503756 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 b41779eb..c0453dda 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/45] 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 c0453dda..31a65db7 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/45] 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 6823405d..cfd5454d 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 31a65db7..5eb77b9f 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 f39aee72..c138009b 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/45] 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 90472fde..56852481 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: active_enum::Category::Big, - category_opt: None, + 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: 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 e1aef0fe..d7b15443 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 70752d35..07a2953b 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 bbe8baa7..615de234 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/45] 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 5eb77b9f..edca0aac 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 25ed8447..384be24a 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 56852481..b0c72a82 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 d7b15443..229d4446 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/45] 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 71690374..42f97b62 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 edca0aac..b42b31f7 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 384be24a..32fe030c 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 5e504a0c..f7f7c7d3 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 1b0c93c3..5a2e0cdc 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 fec7757c..2f9bdb3b 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 b0c72a82..ca9bf7d9 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 823ccdfd..ffb47f26 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/45] 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 f7f7c7d3..3a7c91d6 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 5a2e0cdc..0f2db65b 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 2f9bdb3b..a881ad11 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/45] 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 b42b31f7..230ee8f2 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/45] 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 a95b7047..2d0aa13a 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 615de234..7266d175 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/45] 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 32fe030c..4b9af89f 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 2d0aa13a..3f99f892 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 229d4446..78f21790 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 ffb47f26..0eeb5c04 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/45] 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 230ee8f2..5eb77b9f 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 78f21790..f152f37c 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/45] Refactoring --- tests/common/features/schema.rs | 49 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/tests/common/features/schema.rs b/tests/common/features/schema.rs index 0eeb5c04..f630ccb7 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?; + 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?; + 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/45] 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 f630ccb7..bbc9faf8 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/45] 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 4b9af89f..8cd65d9c 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 3a7c91d6..a1b34f50 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 0f2db65b..1bdfce1a 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 a881ad11..a81b5607 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/45] 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 a1b34f50..63f22c35 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 1bdfce1a..5a270f6d 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 a81b5607..18e4594b 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/45] 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 8cd65d9c..9a1f5c9d 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 63f22c35..8719e011 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 5a270f6d..5c6d1f3b 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 18e4594b..6b6f48f8 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/45] 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 8719e011..f8b151d7 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 5c6d1f3b..037527c7 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 6b6f48f8..7c2e9c2e 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/45] 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 f8b151d7..f7f77d7d 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 037527c7..5e433e8a 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 7c2e9c2e..89348229 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/45] 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 12b07487..86d7c630 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 3f99f892..238bde36 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 bbc9faf8..7e9f3de7 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 7266d175..263e19f2 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/45] 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 7e9f3de7..977b4b61 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/45] 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 238bde36..d99a41b9 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/45] Use sea-query master --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f45eeb55..2345844c 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/45] 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 cc93bde4..865eccae 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/45] Update docs --- src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index e2f24c0b..5289c45f 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/45] 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 94256c05..94277604 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 4926f68a..fae61984 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/45] 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 cfd5454d..acb60a36 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 1db680b0..00540aa4 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 5eb77b9f..104b573f 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 f152f37c..5285c5d9 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/45] 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 104b573f..29238941 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/45] `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 acb60a36..bafb1908 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/45] 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 865eccae..d1910f1d 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 ca9bf7d9..aaad419b 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/45] 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 d1910f1d..fdb7d486 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/45] 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 fdb7d486..d5234b31 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 9d4d6ce5..7cafc44c 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 06158be0..3c982948 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 d1b6b997..f12d3b95 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(), }; From e0302173c29d6bf5ed86b8aa1319438e18a4dc83 Mon Sep 17 00:00:00 2001 From: jasper Date: Sat, 6 Nov 2021 17:33:51 +0800 Subject: [PATCH 40/45] Add axum example --- examples/axum_example/.env | 3 + examples/axum_example/Cargo.toml | 33 ++ examples/axum_example/README.md | 10 + examples/axum_example/src/flash.rs | 51 +++ examples/axum_example/src/main.rs | 221 +++++++++ examples/axum_example/src/post.rs | 26 ++ examples/axum_example/src/setup.rs | 33 ++ .../axum_example/static/css/normalize.css | 427 ++++++++++++++++++ examples/axum_example/static/css/skeleton.css | 421 +++++++++++++++++ examples/axum_example/static/css/style.css | 73 +++ .../axum_example/static/images/favicon.png | Bin 0 -> 1155 bytes .../axum_example/templates/edit.html.tera | 49 ++ .../templates/error/404.html.tera | 11 + .../axum_example/templates/index.html.tera | 52 +++ .../axum_example/templates/layout.html.tera | 26 ++ examples/axum_example/templates/new.html.tera | 38 ++ 16 files changed, 1474 insertions(+) create mode 100644 examples/axum_example/.env create mode 100644 examples/axum_example/Cargo.toml create mode 100644 examples/axum_example/README.md create mode 100644 examples/axum_example/src/flash.rs create mode 100644 examples/axum_example/src/main.rs create mode 100644 examples/axum_example/src/post.rs create mode 100644 examples/axum_example/src/setup.rs create mode 100644 examples/axum_example/static/css/normalize.css create mode 100644 examples/axum_example/static/css/skeleton.css create mode 100644 examples/axum_example/static/css/style.css create mode 100644 examples/axum_example/static/images/favicon.png create mode 100644 examples/axum_example/templates/edit.html.tera create mode 100644 examples/axum_example/templates/error/404.html.tera create mode 100644 examples/axum_example/templates/index.html.tera create mode 100644 examples/axum_example/templates/layout.html.tera create mode 100644 examples/axum_example/templates/new.html.tera diff --git a/examples/axum_example/.env b/examples/axum_example/.env new file mode 100644 index 00000000..fb7fcfb5 --- /dev/null +++ b/examples/axum_example/.env @@ -0,0 +1,3 @@ +HOST=127.0.0.1 +PORT=8000 +DATABASE_URL="postgres://postgres:password@localhost/axum_exmaple" \ No newline at end of file diff --git a/examples/axum_example/Cargo.toml b/examples/axum_example/Cargo.toml new file mode 100644 index 00000000..945cab3a --- /dev/null +++ b/examples/axum_example/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "sea-orm-axum-example" +version = "0.1.0" +authors = ["Yoshiera Huang "] +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace] + +[dependencies] +tokio = { version = "1.5", features = ["full"] } +axum = { version = "0.3.0" } +tower = "0.4.10" +tower-http = { version = "0.1", features = ["fs"] } +tower-cookies = { git = "https://github.com/imbolc/tower-cookies" } +anyhow = "1" +dotenv = "0.15" +env_logger = "0.9" +serde = "1" +serde_json = "1" +tera = "1" + +[dependencies.sea-orm] +path = "../../" # remove this line in your own project +version = "^0.3.0" +features = ["macros", "runtime-tokio-native-tls"] +default-features = false + +[features] +default = ["sqlx-postgres"] +sqlx-mysql = ["sea-orm/sqlx-mysql"] +sqlx-postgres = ["sea-orm/sqlx-postgres"] \ No newline at end of file diff --git a/examples/axum_example/README.md b/examples/axum_example/README.md new file mode 100644 index 00000000..3b04a53e --- /dev/null +++ b/examples/axum_example/README.md @@ -0,0 +1,10 @@ +# Axum with SeaORM example app + +Edit `Cargo.toml` to use `sqlx-mysql` or `sqlx-postgres`. + +```toml +[features] +default = ["sqlx-$DATABASE"] +``` + +Edit `.env` to point to your database. \ No newline at end of file diff --git a/examples/axum_example/src/flash.rs b/examples/axum_example/src/flash.rs new file mode 100644 index 00000000..825447fb --- /dev/null +++ b/examples/axum_example/src/flash.rs @@ -0,0 +1,51 @@ +use axum::http::{header, HeaderMap, HeaderValue, StatusCode}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use tower_cookies::{Cookie, Cookies}; + +#[derive(Deserialize)] +struct ValuedMessage { + #[serde(rename = "_")] + value: T, +} + +#[derive(Serialize)] +struct ValuedMessageRef<'a, T> { + #[serde(rename = "_")] + value: &'a T, +} + +const FLASH_COOKIE_NAME: &str = "_flash"; + +pub fn get_flash_cookie(cookies: &Cookies) -> Option +where + T: DeserializeOwned, +{ + cookies.get(FLASH_COOKIE_NAME).and_then(|flash_cookie| + (if let Ok(ValuedMessage:: { value }) = serde_json::from_str(flash_cookie.value()) { + Some(value) + } else { + None + } + )) +} + +pub type PostResponse = (StatusCode, HeaderMap); + +pub fn post_response(cookies: &mut Cookies, data: T) -> PostResponse +where + T: Serialize, +{ + let valued_message_ref = ValuedMessageRef { value: &data }; + + let mut cookie = Cookie::new( + FLASH_COOKIE_NAME, + serde_json::to_string(&valued_message_ref).unwrap(), + ); + cookie.set_path("/"); + cookies.add(cookie); + + let mut header = HeaderMap::new(); + header.insert(header::LOCATION, HeaderValue::from_static("/")); + + (StatusCode::SEE_OTHER, header) +} diff --git a/examples/axum_example/src/main.rs b/examples/axum_example/src/main.rs new file mode 100644 index 00000000..7b0b428e --- /dev/null +++ b/examples/axum_example/src/main.rs @@ -0,0 +1,221 @@ +mod flash; +mod post; +mod setup; + +use axum::{ + error_handling::HandleErrorExt, + extract::{Extension, Form, Path, Query}, + http::StatusCode, + response::Html, + routing::{get, post, service_method_routing}, + AddExtensionLayer, Router, Server, +}; +use flash::{get_flash_cookie, post_response, PostResponse}; +use post::Entity as Post; +use sea_orm::{prelude::*, Database, QueryOrder, Set}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use std::{env, net::SocketAddr}; +use tera::Tera; +use tower::ServiceBuilder; +use tower_cookies::{CookieManagerLayer, Cookies}; +use tower_http::services::ServeDir; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + env::set_var("RUST_LOG", "debug"); + env_logger::init(); + + dotenv::dotenv().ok(); + let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"); + let host = env::var("HOST").expect("HOST is not set in .env file"); + let port = env::var("PORT").expect("PORT is not set in .env file"); + let server_url = format!("{}:{}", host, port); + + let conn = Database::connect(db_url) + .await + .expect("Database connection failed"); + let _ = setup::create_post_table(&conn).await; + let templates = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")) + .expect("Tera initialization failed"); + // let state = AppState { templates, conn }; + + let app = Router::new() + .route("/", get(list_posts).post(create_post)) + .route("/:id", get(edit_post).post(update_post)) + .route("/new", get(new_post)) + .route("/delete/:id", post(delete_post)) + .nest( + "/static", + service_method_routing::get(ServeDir::new(concat!( + env!("CARGO_MANIFEST_DIR"), + "/static" + ))) + .handle_error(|error: std::io::Error| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Unhandled internal error: {}", error), + ) + }), + ) + .layer( + ServiceBuilder::new() + .layer(CookieManagerLayer::new()) + .layer(AddExtensionLayer::new(conn)) + .layer(AddExtensionLayer::new(templates)), + ); + + let addr = SocketAddr::from_str(&server_url).unwrap(); + Server::bind(&addr).serve(app.into_make_service()).await?; + + Ok(()) +} + +#[derive(Deserialize)] +struct Params { + page: Option, + posts_per_page: Option, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +struct FlashData { + kind: String, + message: String, +} + +async fn list_posts( + Extension(ref templates): Extension, + Extension(ref conn): Extension, + Query(params): Query, + cookies: Cookies, +) -> Result, (StatusCode, &'static str)> { + let page = params.page.unwrap_or(1); + let posts_per_page = params.posts_per_page.unwrap_or(5); + let paginator = Post::find() + .order_by_asc(post::Column::Id) + .paginate(conn, posts_per_page); + let num_pages = paginator.num_pages().await.ok().unwrap(); + let posts = paginator + .fetch_page(page - 1) + .await + .expect("could not retrieve posts"); + + let mut ctx = tera::Context::new(); + ctx.insert("posts", &posts); + ctx.insert("page", &page); + ctx.insert("posts_per_page", &posts_per_page); + ctx.insert("num_pages", &num_pages); + + if let Some(value) = get_flash_cookie::(&cookies) { + ctx.insert("flash", &value); + } + + let body = templates + .render("index.html.tera", &ctx) + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Template error"))?; + + Ok(Html(body)) +} + +async fn new_post( + Extension(ref templates): Extension, +) -> Result, (StatusCode, &'static str)> { + let ctx = tera::Context::new(); + let body = templates + .render("new.html.tera", &ctx) + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Template error"))?; + + Ok(Html(body)) +} + +async fn create_post( + Extension(ref conn): Extension, + form: Form, + mut cookies: Cookies, +) -> Result { + let model = form.0; + + post::ActiveModel { + title: Set(model.title.to_owned()), + text: Set(model.text.to_owned()), + ..Default::default() + } + .save(conn) + .await + .expect("could not insert post"); + + let data = FlashData { + kind: "success".to_owned(), + message: "Post succcessfully added".to_owned(), + }; + + Ok(post_response(&mut cookies, data)) +} + +async fn edit_post( + Extension(ref templates): Extension, + Extension(ref conn): Extension, + Path(id): Path, +) -> Result, (StatusCode, &'static str)> { + let post: post::Model = Post::find_by_id(id) + .one(conn) + .await + .expect("could not find post") + .unwrap(); + + let mut ctx = tera::Context::new(); + ctx.insert("post", &post); + + let body = templates + .render("edit.html.tera", &ctx) + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Template error"))?; + + Ok(Html(body)) +} + +async fn update_post( + Extension(ref conn): Extension, + Path(id): Path, + form: Form, + mut cookies: Cookies, +) -> Result { + let model = form.0; + + post::ActiveModel { + id: Set(id), + title: Set(model.title.to_owned()), + text: Set(model.text.to_owned()), + } + .save(conn) + .await + .expect("could not edit post"); + + let data = FlashData { + kind: "success".to_owned(), + message: "Post succcessfully updated".to_owned(), + }; + + Ok(post_response(&mut cookies, data)) +} + +async fn delete_post( + Extension(ref conn): Extension, + Path(id): Path, + mut cookies: Cookies, +) -> Result { + let post: post::ActiveModel = Post::find_by_id(id) + .one(conn) + .await + .unwrap() + .unwrap() + .into(); + + post.delete(conn).await.unwrap(); + + let data = FlashData { + kind: "success".to_owned(), + message: "Post succcessfully deleted".to_owned(), + }; + + Ok(post_response(&mut cookies, data)) +} diff --git a/examples/axum_example/src/post.rs b/examples/axum_example/src/post.rs new file mode 100644 index 00000000..3bb4d6a3 --- /dev/null +++ b/examples/axum_example/src/post.rs @@ -0,0 +1,26 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.3.2 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] +#[sea_orm(table_name = "posts")] +pub struct Model { + #[sea_orm(primary_key)] + #[serde(skip_deserializing)] + pub id: i32, + pub title: String, + #[sea_orm(column_type = "Text")] + pub text: String, +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation {} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + panic!("No RelationDef") + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/axum_example/src/setup.rs b/examples/axum_example/src/setup.rs new file mode 100644 index 00000000..04677af4 --- /dev/null +++ b/examples/axum_example/src/setup.rs @@ -0,0 +1,33 @@ +use sea_orm::sea_query::{ColumnDef, TableCreateStatement}; +use sea_orm::{error::*, sea_query, ConnectionTrait, DbConn, ExecResult}; + +async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result { + let builder = db.get_database_backend(); + db.execute(builder.build(stmt)).await +} + +pub async fn create_post_table(db: &DbConn) -> Result { + let stmt = sea_query::Table::create() + .table(super::post::Entity) + .if_not_exists() + .col( + ColumnDef::new(super::post::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col( + ColumnDef::new(super::post::Column::Title) + .string() + .not_null(), + ) + .col( + ColumnDef::new(super::post::Column::Text) + .string() + .not_null(), + ) + .to_owned(); + + create_table(db, &stmt).await +} diff --git a/examples/axum_example/static/css/normalize.css b/examples/axum_example/static/css/normalize.css new file mode 100644 index 00000000..458eea1e --- /dev/null +++ b/examples/axum_example/static/css/normalize.css @@ -0,0 +1,427 @@ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} diff --git a/examples/axum_example/static/css/skeleton.css b/examples/axum_example/static/css/skeleton.css new file mode 100644 index 00000000..cdc432a4 --- /dev/null +++ b/examples/axum_example/static/css/skeleton.css @@ -0,0 +1,421 @@ +/* +* Skeleton V2.0.4 +* Copyright 2014, Dave Gamache +* www.getskeleton.com +* Free to use under the MIT license. +* https://opensource.org/licenses/mit-license.php +* 12/29/2014 +*/ + + +/* Table of contents +–––––––––––––––––––––––––––––––––––––––––––––––––– +- Grid +- Base Styles +- Typography +- Links +- Buttons +- Forms +- Lists +- Code +- Tables +- Spacing +- Utilities +- Clearing +- Media Queries +*/ + + +/* Grid +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.container { + position: relative; + width: 100%; + max-width: 960px; + margin: 0 auto; + padding: 0 20px; + box-sizing: border-box; } +.column, +.columns { + width: 100%; + float: left; + box-sizing: border-box; } + +/* For devices larger than 400px */ +@media (min-width: 400px) { + .container { + width: 85%; + padding: 0; } +} + +/* For devices larger than 550px */ +@media (min-width: 550px) { + .container { + width: 80%; } + .column, + .columns { + margin-left: 4%; } + .column:first-child, + .columns:first-child { + margin-left: 0; } + + .one.column, + .one.columns { width: 4.66666666667%; } + .two.columns { width: 13.3333333333%; } + .three.columns { width: 22%; } + .four.columns { width: 30.6666666667%; } + .five.columns { width: 39.3333333333%; } + .six.columns { width: 48%; } + .seven.columns { width: 56.6666666667%; } + .eight.columns { width: 65.3333333333%; } + .nine.columns { width: 74.0%; } + .ten.columns { width: 82.6666666667%; } + .eleven.columns { width: 91.3333333333%; } + .twelve.columns { width: 100%; margin-left: 0; } + + .one-third.column { width: 30.6666666667%; } + .two-thirds.column { width: 65.3333333333%; } + + .one-half.column { width: 48%; } + + /* Offsets */ + .offset-by-one.column, + .offset-by-one.columns { margin-left: 8.66666666667%; } + .offset-by-two.column, + .offset-by-two.columns { margin-left: 17.3333333333%; } + .offset-by-three.column, + .offset-by-three.columns { margin-left: 26%; } + .offset-by-four.column, + .offset-by-four.columns { margin-left: 34.6666666667%; } + .offset-by-five.column, + .offset-by-five.columns { margin-left: 43.3333333333%; } + .offset-by-six.column, + .offset-by-six.columns { margin-left: 52%; } + .offset-by-seven.column, + .offset-by-seven.columns { margin-left: 60.6666666667%; } + .offset-by-eight.column, + .offset-by-eight.columns { margin-left: 69.3333333333%; } + .offset-by-nine.column, + .offset-by-nine.columns { margin-left: 78.0%; } + .offset-by-ten.column, + .offset-by-ten.columns { margin-left: 86.6666666667%; } + .offset-by-eleven.column, + .offset-by-eleven.columns { margin-left: 95.3333333333%; } + + .offset-by-one-third.column, + .offset-by-one-third.columns { margin-left: 34.6666666667%; } + .offset-by-two-thirds.column, + .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } + + .offset-by-one-half.column, + .offset-by-one-half.columns { margin-left: 52%; } + +} + + +/* Base Styles +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +/* NOTE +html is set to 62.5% so that all the REM measurements throughout Skeleton +are based on 10px sizing. So basically 1.5rem = 15px :) */ +html { + font-size: 62.5%; } +body { + font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ + line-height: 1.6; + font-weight: 400; + font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #222; } + + +/* Typography +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 2rem; + font-weight: 300; } +h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} +h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } +h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } +h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } +h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } +h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } + +/* Larger than phablet */ +@media (min-width: 550px) { + h1 { font-size: 5.0rem; } + h2 { font-size: 4.2rem; } + h3 { font-size: 3.6rem; } + h4 { font-size: 3.0rem; } + h5 { font-size: 2.4rem; } + h6 { font-size: 1.5rem; } +} + +p { + margin-top: 0; } + + +/* Links +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +a { + color: #1EAEDB; } +a:hover { + color: #0FA0CE; } + + +/* Buttons +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.button, +button, +input[type="submit"], +input[type="reset"], +input[type="button"] { + display: inline-block; + height: 38px; + padding: 0 30px; + color: #555; + text-align: center; + font-size: 11px; + font-weight: 600; + line-height: 38px; + letter-spacing: .1rem; + text-transform: uppercase; + text-decoration: none; + white-space: nowrap; + background-color: transparent; + border-radius: 4px; + border: 1px solid #bbb; + cursor: pointer; + box-sizing: border-box; } +.button:hover, +button:hover, +input[type="submit"]:hover, +input[type="reset"]:hover, +input[type="button"]:hover, +.button:focus, +button:focus, +input[type="submit"]:focus, +input[type="reset"]:focus, +input[type="button"]:focus { + color: #333; + border-color: #888; + outline: 0; } +.button.button-primary, +button.button-primary, +button.primary, +input[type="submit"].button-primary, +input[type="reset"].button-primary, +input[type="button"].button-primary { + color: #FFF; + background-color: #33C3F0; + border-color: #33C3F0; } +.button.button-primary:hover, +button.button-primary:hover, +button.primary:hover, +input[type="submit"].button-primary:hover, +input[type="reset"].button-primary:hover, +input[type="button"].button-primary:hover, +.button.button-primary:focus, +button.button-primary:focus, +button.primary:focus, +input[type="submit"].button-primary:focus, +input[type="reset"].button-primary:focus, +input[type="button"].button-primary:focus { + color: #FFF; + background-color: #1EAEDB; + border-color: #1EAEDB; } + + +/* Forms +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +input[type="email"], +input[type="number"], +input[type="search"], +input[type="text"], +input[type="tel"], +input[type="url"], +input[type="password"], +textarea, +select { + height: 38px; + padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ + background-color: #fff; + border: 1px solid #D1D1D1; + border-radius: 4px; + box-shadow: none; + box-sizing: border-box; } +/* Removes awkward default styles on some inputs for iOS */ +input[type="email"], +input[type="number"], +input[type="search"], +input[type="text"], +input[type="tel"], +input[type="url"], +input[type="password"], +textarea { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; } +textarea { + min-height: 65px; + padding-top: 6px; + padding-bottom: 6px; } +input[type="email"]:focus, +input[type="number"]:focus, +input[type="search"]:focus, +input[type="text"]:focus, +input[type="tel"]:focus, +input[type="url"]:focus, +input[type="password"]:focus, +textarea:focus, +select:focus { + border: 1px solid #33C3F0; + outline: 0; } +label, +legend { + display: block; + margin-bottom: .5rem; + font-weight: 600; } +fieldset { + padding: 0; + border-width: 0; } +input[type="checkbox"], +input[type="radio"] { + display: inline; } +label > .label-body { + display: inline-block; + margin-left: .5rem; + font-weight: normal; } + + +/* Lists +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +ul { + list-style: circle inside; } +ol { + list-style: decimal inside; } +ol, ul { + padding-left: 0; + margin-top: 0; } +ul ul, +ul ol, +ol ol, +ol ul { + margin: 1.5rem 0 1.5rem 3rem; + font-size: 90%; } +li { + margin-bottom: 1rem; } + + +/* Code +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +code { + padding: .2rem .5rem; + margin: 0 .2rem; + font-size: 90%; + white-space: nowrap; + background: #F1F1F1; + border: 1px solid #E1E1E1; + border-radius: 4px; } +pre > code { + display: block; + padding: 1rem 1.5rem; + white-space: pre; } + + +/* Tables +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +th, +td { + padding: 12px 15px; + text-align: left; + border-bottom: 1px solid #E1E1E1; } +th:first-child, +td:first-child { + padding-left: 0; } +th:last-child, +td:last-child { + padding-right: 0; } + + +/* Spacing +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +button, +.button { + margin-bottom: 1rem; } +input, +textarea, +select, +fieldset { + margin-bottom: 1.5rem; } +pre, +blockquote, +dl, +figure, +table, +p, +ul, +ol, +form { + margin-bottom: 2.5rem; } + + +/* Utilities +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.u-full-width { + width: 100%; + box-sizing: border-box; } +.u-max-full-width { + max-width: 100%; + box-sizing: border-box; } +.u-pull-right { + float: right; } +.u-pull-left { + float: left; } + + +/* Misc +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +hr { + margin-top: 3rem; + margin-bottom: 3.5rem; + border-width: 0; + border-top: 1px solid #E1E1E1; } + + +/* Clearing +–––––––––––––––––––––––––––––––––––––––––––––––––– */ + +/* Self Clearing Goodness */ +.container:after, +.row:after, +.u-cf { + content: ""; + display: table; + clear: both; } + + +/* Media Queries +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +/* +Note: The best way to structure the use of media queries is to create the queries +near the relevant code. For example, if you wanted to change the styles for buttons +on small devices, paste the mobile query code up in the buttons section and style it +there. +*/ + + +/* Larger than mobile */ +@media (min-width: 400px) {} + +/* Larger than phablet (also point when grid becomes active) */ +@media (min-width: 550px) {} + +/* Larger than tablet */ +@media (min-width: 750px) {} + +/* Larger than desktop */ +@media (min-width: 1000px) {} + +/* Larger than Desktop HD */ +@media (min-width: 1200px) {} diff --git a/examples/axum_example/static/css/style.css b/examples/axum_example/static/css/style.css new file mode 100644 index 00000000..ac2720d3 --- /dev/null +++ b/examples/axum_example/static/css/style.css @@ -0,0 +1,73 @@ +.field-error { + border: 1px solid #ff0000 !important; +} + +.field-error-flash { + color: #ff0000; + display: block; + margin: -10px 0 10px 0; +} + +.field-success { + border: 1px solid #5ab953 !important; +} + +.field-success-flash { + color: #5ab953; + display: block; + margin: -10px 0 10px 0; +} + +span.completed { + text-decoration: line-through; +} + +form.inline { + display: inline; +} + +form.link, +button.link { + display: inline; + color: #1eaedb; + border: none; + outline: none; + background: none; + cursor: pointer; + padding: 0; + margin: 0 0 0 0; + height: inherit; + text-decoration: underline; + font-size: inherit; + text-transform: none; + font-weight: normal; + line-height: inherit; + letter-spacing: inherit; +} + +form.link:hover, +button.link:hover { + color: #0fa0ce; +} + +button.small { + height: 20px; + padding: 0 10px; + font-size: 10px; + line-height: 20px; + margin: 0 2.5px; +} + +.post:hover { + background-color: #bce2ee; +} + +.post td { + padding: 5px; + width: 150px; +} + +#delete-button { + color: red; + border-color: red; +} diff --git a/examples/axum_example/static/images/favicon.png b/examples/axum_example/static/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..02b7390443ae2877c186898f52da23f062adbdcc GIT binary patch literal 1155 zcmV-}1bq96P)(=Qx>xolP{L(>2x~5PFgqllLG?-XURWh zvn7|E20`C&yWRV!SI1Uhr?1K%v2UB0m}sFhe_PG5av(Pl2<+kH9bD;aRz}QZjK*Wi zzU=Ss53_$_q-W@WteT*|u)Sk6X{KXzp9O21Er;<5zg9bJJVN(-=Zy8l4qegdI#S(evShjLl#Pk=r9jtgPegJHb!|>nu2DZUh(FA*z*X!Ly#;=Z< zcY*h@wRV}ZUw{XTs%x<*&yAr6*O7QQIqlYkGx)Ptvae%$?Bex{(}9JyQ+)L}e`z0B>Y7?qR+gjq`2_(| z*YR1|dc+Lo=!ck$dL#l;$vC2h!$%RW3u(Hst`lh9wh`s^nV%ujFKnUV($0F52BLCWB8{a&>BRO_*@>CN}62 z(UjU)Dh1DMef+rt@phC|?U_NQ(5sA6dU2zmz~B@z{?iJlE7 z#AcOX=vSv4Lj8d7>Y@B!eoVBgSnmWY)={>fquK*F zBSk|8RK;;Ao#{j?9gxf#WYg+-BV)QTNo=ijU>!sLJNa>)EQ2-Nf((u4A6fZ5e+Gh* Ve)_7M0d)WX002ovPDHLkV1i9aF}45z literal 0 HcmV?d00001 diff --git a/examples/axum_example/templates/edit.html.tera b/examples/axum_example/templates/edit.html.tera new file mode 100644 index 00000000..5aeb22af --- /dev/null +++ b/examples/axum_example/templates/edit.html.tera @@ -0,0 +1,49 @@ +{% extends "layout.html.tera" %} {% block content %} + +{% endblock content %} diff --git a/examples/axum_example/templates/error/404.html.tera b/examples/axum_example/templates/error/404.html.tera new file mode 100644 index 00000000..afda653d --- /dev/null +++ b/examples/axum_example/templates/error/404.html.tera @@ -0,0 +1,11 @@ + + + + + 404 - tera + + +

404: Hey! There's nothing here.

+ The page at {{ uri }} does not exist! + + diff --git a/examples/axum_example/templates/index.html.tera b/examples/axum_example/templates/index.html.tera new file mode 100644 index 00000000..1dccdeba --- /dev/null +++ b/examples/axum_example/templates/index.html.tera @@ -0,0 +1,52 @@ +{% extends "layout.html.tera" %} {% block content %} +
+

+

Posts

+ {% if flash %} + + {{ flash.message }} + + {% endif %} + + + + + + + + + + {% for post in posts %} + + + + + + {% endfor %} + + + + + + + + +
IDTitleText
{{ post.id }}{{ post.title }}{{ post.text }}
+ {% if page == 1 %} Previous {% else %} + Previous + {% endif %} | {% if page == num_pages %} Next {% else %} + Next + {% endif %} +
+ +
+ + + +
+
+{% endblock content %} diff --git a/examples/axum_example/templates/layout.html.tera b/examples/axum_example/templates/layout.html.tera new file mode 100644 index 00000000..54c7397b --- /dev/null +++ b/examples/axum_example/templates/layout.html.tera @@ -0,0 +1,26 @@ + + + + + Axum Example + + + + + + + + + + + +
+

+ {% block content %}{% endblock content %} +
+ + diff --git a/examples/axum_example/templates/new.html.tera b/examples/axum_example/templates/new.html.tera new file mode 100644 index 00000000..dee19565 --- /dev/null +++ b/examples/axum_example/templates/new.html.tera @@ -0,0 +1,38 @@ +{% extends "layout.html.tera" %} {% block content %} +
+

New Post

+
+
+ + +
+
+
+ + + +
+
+
+ +
+
+
+
+{% endblock content %} From 8b217239fdf36285f0730ad5096f527510413b91 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 8 Nov 2021 12:13:29 +0800 Subject: [PATCH 41/45] Fix clippy warnings --- sea-orm-codegen/src/entity/writer.rs | 10 +++++----- sea-orm-codegen/tests/compact/filling.rs | 4 +--- sea-orm-codegen/tests/compact/rust_keyword.rs | 4 +--- sea-orm-codegen/tests/expanded/filling.rs | 4 +--- sea-orm-codegen/tests/expanded/rust_keyword.rs | 4 +--- 5 files changed, 9 insertions(+), 17 deletions(-) diff --git a/sea-orm-codegen/src/entity/writer.rs b/sea-orm-codegen/src/entity/writer.rs index be3c47bb..0efba306 100644 --- a/sea-orm-codegen/src/entity/writer.rs +++ b/sea-orm-codegen/src/entity/writer.rs @@ -328,19 +328,19 @@ impl EntityWriter { let relation_defs = entity.get_relation_defs(); let quoted = if relation_ref_tables_camel_case.is_empty() { quote! { - _ => panic!("No RelationDef"), + panic!("No RelationDef") } } else { quote! { - #(Self::#relation_ref_tables_camel_case => #relation_defs,)* + match self { + #(Self::#relation_ref_tables_camel_case => #relation_defs,)* + } } }; quote! { impl RelationTrait for Relation { fn def(&self) -> RelationDef { - match self { - #quoted - } + #quoted } } } diff --git a/sea-orm-codegen/tests/compact/filling.rs b/sea-orm-codegen/tests/compact/filling.rs index 820f65c8..dfedb1a7 100644 --- a/sea-orm-codegen/tests/compact/filling.rs +++ b/sea-orm-codegen/tests/compact/filling.rs @@ -15,9 +15,7 @@ pub enum Relation {} impl RelationTrait for Relation { fn def(&self) -> RelationDef { - match self { - _ => panic!("No RelationDef"), - } + panic!("No RelationDef") } } diff --git a/sea-orm-codegen/tests/compact/rust_keyword.rs b/sea-orm-codegen/tests/compact/rust_keyword.rs index 229eae22..9a7bd9eb 100644 --- a/sea-orm-codegen/tests/compact/rust_keyword.rs +++ b/sea-orm-codegen/tests/compact/rust_keyword.rs @@ -21,9 +21,7 @@ pub enum Relation {} impl RelationTrait for Relation { fn def(&self) -> RelationDef { - match self { - _ => panic!("No RelationDef"), - } + panic!("No RelationDef") } } diff --git a/sea-orm-codegen/tests/expanded/filling.rs b/sea-orm-codegen/tests/expanded/filling.rs index 73d58152..76764ecf 100644 --- a/sea-orm-codegen/tests/expanded/filling.rs +++ b/sea-orm-codegen/tests/expanded/filling.rs @@ -51,9 +51,7 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { - match self { - _ => panic!("No RelationDef"), - } + panic!("No RelationDef") } } diff --git a/sea-orm-codegen/tests/expanded/rust_keyword.rs b/sea-orm-codegen/tests/expanded/rust_keyword.rs index 1ab8a627..e83cc9ea 100644 --- a/sea-orm-codegen/tests/expanded/rust_keyword.rs +++ b/sea-orm-codegen/tests/expanded/rust_keyword.rs @@ -70,9 +70,7 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { - match self { - _ => panic!("No RelationDef"), - } + panic!("No RelationDef") } } From 1ed6371cbd71ed63a3118fa056d2c02171865a4a Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 8 Nov 2021 12:39:36 +0800 Subject: [PATCH 42/45] Run `axum_example` on CI --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 521238af..ad18203e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -290,7 +290,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - path: [basic, actix_example, actix4_example, rocket_example] + path: [basic, actix_example, actix4_example, axum_example, rocket_example] steps: - uses: actions/checkout@v2 From 666a5bb06863f89a9cbecd6e5564bee033b8335b Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 8 Nov 2021 12:50:38 +0800 Subject: [PATCH 43/45] cargo fmt --- examples/axum_example/src/flash.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/axum_example/src/flash.rs b/examples/axum_example/src/flash.rs index 825447fb..78db1f89 100644 --- a/examples/axum_example/src/flash.rs +++ b/examples/axum_example/src/flash.rs @@ -20,13 +20,13 @@ pub fn get_flash_cookie(cookies: &Cookies) -> Option where T: DeserializeOwned, { - cookies.get(FLASH_COOKIE_NAME).and_then(|flash_cookie| + cookies.get(FLASH_COOKIE_NAME).and_then(|flash_cookie| { (if let Ok(ValuedMessage:: { value }) = serde_json::from_str(flash_cookie.value()) { Some(value) } else { None - } - )) + }) + }) } pub type PostResponse = (StatusCode, HeaderMap); From cb996d84609a8cd0510fe6231bde25bfdbf1cb97 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 8 Nov 2021 12:53:12 +0800 Subject: [PATCH 44/45] Fix clippy warnings --- examples/axum_example/src/flash.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/axum_example/src/flash.rs b/examples/axum_example/src/flash.rs index 78db1f89..8f99a27a 100644 --- a/examples/axum_example/src/flash.rs +++ b/examples/axum_example/src/flash.rs @@ -21,11 +21,11 @@ where T: DeserializeOwned, { cookies.get(FLASH_COOKIE_NAME).and_then(|flash_cookie| { - (if let Ok(ValuedMessage:: { value }) = serde_json::from_str(flash_cookie.value()) { + if let Ok(ValuedMessage:: { value }) = serde_json::from_str(flash_cookie.value()) { Some(value) } else { None - }) + } }) } From 0d4a237d1c783f90150f1fee04c79756355840a5 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 8 Nov 2021 13:48:05 +0800 Subject: [PATCH 45/45] Readme badges --- README.md | 5 +++-- src/lib.rs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0532c080..b89e481f 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,10 @@ #### SeaORM is a relational ORM to help you build light weight and concurrent web services in Rust. [![Getting Started](https://img.shields.io/badge/Getting%20Started-brightgreen)](https://www.sea-ql.org/SeaORM/docs/index) -[![Usage Example](https://img.shields.io/badge/Usage%20Example-yellow)](https://github.com/SeaQL/sea-orm/tree/master/examples/basic) +[![Usage Example](https://img.shields.io/badge/Usage%20Example-green)](https://github.com/SeaQL/sea-orm/tree/master/examples/basic) +[![Rocket Example](https://img.shields.io/badge/Rocket%20Example-red)](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) [![Actix Example](https://img.shields.io/badge/Actix%20Example-blue)](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example) -[![Rocket Example](https://img.shields.io/badge/Rocket%20Example-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) +[![Axum Example](https://img.shields.io/badge/Axum%20Example-yellow)](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example) [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv) ## Features diff --git a/src/lib.rs b/src/lib.rs index 30d7cc0b..2f254d52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,9 +27,10 @@ //! #### SeaORM is a relational ORM to help you build light weight and concurrent web services in Rust. //! //! [![Getting Started](https://img.shields.io/badge/Getting%20Started-brightgreen)](https://www.sea-ql.org/SeaORM/docs/index) -//! [![Usage Example](https://img.shields.io/badge/Usage%20Example-yellow)](https://github.com/SeaQL/sea-orm/tree/master/examples/basic) +//! [![Usage Example](https://img.shields.io/badge/Usage%20Example-green)](https://github.com/SeaQL/sea-orm/tree/master/examples/basic) +//! [![Rocket Example](https://img.shields.io/badge/Rocket%20Example-red)](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) //! [![Actix Example](https://img.shields.io/badge/Actix%20Example-blue)](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example) -//! [![Rocket Example](https://img.shields.io/badge/Rocket%20Example-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) +//! [![Axum Example](https://img.shields.io/badge/Axum%20Example-yellow)](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example) //! [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv) //! //! ## Features