Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 35 additions & 10 deletions sea-orm-macros/src/derives/active_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use syn::{Expr, Lit, LitInt, LitStr, UnOp, parse};

struct ActiveEnum {
ident: syn::Ident,
enum_name: String,
// (schema name, enum name)
enum_name: (Option<String>, String),
rs_type: RsType,
db_type: DbType,
is_string: bool,
Expand Down Expand Up @@ -131,6 +132,7 @@ impl ActiveEnum {
let ident = input.ident;

let mut enum_name = ident.to_string().to_upper_camel_case();
let mut schema_name = None;
let mut rs_type = None;
let mut db_type = None;
let mut rename_all = None;
Expand All @@ -150,6 +152,9 @@ impl ActiveEnum {
} else if meta.path.is_ident("enum_name") {
let litstr: LitStr = meta.value()?.parse()?;
enum_name = litstr.value();
} else if meta.path.is_ident("schema_name") {
let litstr: LitStr = meta.value()?.parse()?;
schema_name = Some(litstr.value());
} else if meta.path.is_ident("rename_all") {
rename_all = Some((&meta).try_into()?);
} else {
Expand Down Expand Up @@ -298,7 +303,7 @@ impl ActiveEnum {

Ok(Self {
ident,
enum_name,
enum_name: (schema_name, enum_name),
rs_type,
db_type,
is_string,
Expand All @@ -314,7 +319,7 @@ impl ActiveEnum {
}

fn to_value_impl(&self) -> TokenStream {
let enum_name = &self.enum_name;
let enum_name = self.schema_qualified_name();
let variant_idents = &self.variant_idents;
let variant_values = &self.variant_values;

Expand Down Expand Up @@ -364,7 +369,7 @@ impl ActiveEnum {

fn nullable_impl(&self) -> TokenStream {
let ident = &self.ident;
let enum_name = &self.enum_name;
let enum_name = &self.enum_name.1;
let nullable_value_impl = if self.generate_enum_impls() {
quote! {
use sea_orm::sea_query::{OptionEnum, Value};
Expand All @@ -391,7 +396,7 @@ impl ActiveEnum {
fn value_type_impl(&self) -> TokenStream {
let ident = &self.ident;
let value_type_try_from_impl = self.value_type_try_from_impl();
let enum_name = &self.enum_name;
let enum_name = &self.enum_name.1;

let type_name_impl = quote! { stringify!(#ident).to_owned() };

Expand Down Expand Up @@ -453,7 +458,8 @@ impl ActiveEnum {
quote!()
};
let try_get_by_impl = {
let enum_name = &self.enum_name;
let enum_name = self.schema_qualified_name();

if self.generate_enum_impls() {
quote! {
#sqlx_postgres_try_get
Expand Down Expand Up @@ -504,6 +510,15 @@ impl ActiveEnum {
}
};

let schema_name_impl = match &self.enum_name.0 {
Some(name) => quote! {
fn schema_name() -> Option<&'static str> {
Some(#name)
}
},
None => quote! {},
};

let val = if self.generate_enum_impls() {
quote! { v.value.as_ref() }
} else if self.is_string {
Expand All @@ -523,6 +538,8 @@ impl ActiveEnum {
#enum_name_iden.into()
}

#schema_name_impl

fn to_value(&self) -> <Self as sea_orm::ActiveEnum>::Value {
#to_value_body
}
Expand All @@ -549,7 +566,7 @@ impl ActiveEnum {
let ident = &self.ident;

if self.generate_enum_impls() {
let enum_name = &self.enum_name;
let enum_name = self.schema_qualified_name();
let variant_idents = &self.variant_idents;
let variant_values = &self.variant_values;

Expand Down Expand Up @@ -593,16 +610,16 @@ impl ActiveEnum {
}

let ident = &self.ident;
let enum_name = &self.enum_name;
let ident_s = ident.to_string();
let variant_idents = &self.variant_idents;
let variant_values = &self.variant_values;
let pg_type_name = self.schema_qualified_name();

quote! {
#[automatically_derived]
impl sea_orm::sqlx::Type<sea_orm::sqlx::Postgres> for #ident {
fn type_info() -> sea_orm::sqlx::postgres::PgTypeInfo {
sea_orm::sqlx::postgres::PgTypeInfo::with_name(#enum_name)
sea_orm::sqlx::postgres::PgTypeInfo::with_name(#pg_type_name)
}
}

Expand Down Expand Up @@ -631,7 +648,7 @@ impl ActiveEnum {
#[automatically_derived]
impl sea_orm::sqlx::postgres::PgHasArrayType for #ident {
fn array_type_info() -> sea_orm::sqlx::postgres::PgTypeInfo {
sea_orm::sqlx::postgres::PgTypeInfo::array_of(#enum_name)
sea_orm::sqlx::postgres::PgTypeInfo::array_of(#pg_type_name)
}
}
}
Expand Down Expand Up @@ -677,6 +694,7 @@ impl ActiveEnum {
..
} = self;

let enum_name = enum_name.1.clone();
let enum_name_iden = format_ident!("{}Enum", ident);

let str_variants: Vec<String> = variants
Expand Down Expand Up @@ -795,6 +813,13 @@ impl ActiveEnum {
#not_u8_impl
)
}

fn schema_qualified_name(&self) -> String {
match &self.enum_name {
(Some(schema), enum_name) => format!("{schema}\".\"{enum_name}"),
(None, enum_name) => enum_name.clone(),
}
}
}

pub fn expand_derive_active_enum(input: syn::DeriveInput) -> syn::Result<TokenStream> {
Expand Down
2 changes: 2 additions & 0 deletions sea-orm-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,8 @@ pub fn derive_active_model_behavior(input: TokenStream) -> TokenStream {
/// - `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. `enum_name = "MyEnum"`
/// - `schema_name`: Define the database schema the enum type belongs to
/// - This attribute is optional; when omitted the enum uses the database search path
/// - Constraints for native enums (`db_type = "Enum"`):
/// - `rs_type` is optional; it defaults to `Enum`. If specified it must be `String` or `Enum`.
/// - `num_value` and numeric discriminants are not allowed.
Expand Down
75 changes: 75 additions & 0 deletions sea-orm-macros/tests/derive_active_enum_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,78 @@ fn derive_non_database_enum_value_type() {
assert_eq!(TestEnum2::enum_type_name(), None);
assert_eq!(TestEnum2::array_type(), ArrayType::String);
}

#[derive(Debug, EnumIter, DeriveActiveEnum, Eq, PartialEq)]
#[sea_orm(
rs_type = "Enum",
db_type = "Enum",
enum_name = "tea",
schema_name = "my_schema"
)]
enum SchemaEnum {
#[sea_orm(string_value = "EverydayTea")]
EverydayTea,
#[sea_orm(string_value = "BreakfastTea")]
BreakfastTea,
}

#[derive(Debug, EnumIter, DeriveActiveEnum, Eq, PartialEq)]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
enum_name = "color",
schema_name = "palette"
)]
enum SchemaStringEnum {
#[sea_orm(string_value = "Red")]
Red,
#[sea_orm(string_value = "Blue")]
Blue,
}

#[derive(Debug, EnumIter, DeriveActiveEnum, Eq, PartialEq)]
#[sea_orm(rs_type = "Enum", db_type = "Enum", enum_name = "status")]
enum NoSchemaEnum {
#[sea_orm(string_value = "Active")]
Active,
#[sea_orm(string_value = "Inactive")]
Inactive,
}

#[test]
fn derive_active_enum_schema_name() {
assert_eq!(SchemaEnum::schema_name(), Some("my_schema"));
assert_eq!(SchemaStringEnum::schema_name(), Some("palette"));
assert_eq!(NoSchemaEnum::schema_name(), None);
}

#[test]
fn derive_active_enum_schema_name_enum_type() {
assert_eq!(SchemaEnum::enum_type_name(), Some("tea"));
assert_eq!(SchemaStringEnum::enum_type_name(), Some("color"));
assert_eq!(NoSchemaEnum::enum_type_name(), Some("status"));
}

#[test]
fn derive_active_enum_schema_name_values_roundtrip() {
let value = SchemaEnum::EverydayTea.to_value();
assert_eq!(value.value.as_ref(), "EverydayTea");
assert_eq!(
<SchemaEnum as ActiveEnum>::try_from_value(&value),
Ok(SchemaEnum::EverydayTea)
);
}

#[test]
fn derive_active_enum_schema_name_value_conversion() {
let value: Value = SchemaEnum::BreakfastTea.to_value().into();
assert_eq!(
value,
Value::Enum(sea_orm::sea_query::OptionEnum::Some(Box::new(
sea_orm::sea_query::Enum {
type_name: String::from("tea").into(),
value: "BreakfastTea".into(),
},
)))
);
}
6 changes: 6 additions & 0 deletions sea-orm-sync/src/entity/active_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ pub trait ActiveEnum: Sized + Iterable {
/// Get the name of enum
fn name() -> DynIden;

/// Get the schema name of the enum, if specified.
/// Returns `None` by default, meaning the enum lives in the database search path.
fn schema_name() -> Option<&'static str> {
None
}

/// Convert enum variant into the corresponding value.
fn to_value(&self) -> Self::Value;

Expand Down
2 changes: 1 addition & 1 deletion sea-orm-sync/src/query/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,7 @@ pub(crate) fn join_tbl_on_condition(
foreign_keys: Identity,
) -> Condition {
let mut cond = Condition::all();
for (owner_key, foreign_key) in owner_keys.into_iter().zip(foreign_keys.into_iter()) {
for (owner_key, foreign_key) in owner_keys.into_iter().zip(foreign_keys) {
cond = cond
.add(Expr::col((from_tbl.clone(), owner_key)).equals((to_tbl.clone(), foreign_key)));
}
Expand Down
22 changes: 18 additions & 4 deletions sea-orm-sync/src/schema/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ use crate::{
PrimaryKeyArity, PrimaryKeyToColumn, PrimaryKeyTrait, RelationTrait, Schema,
};
use sea_query::{
ColumnDef, DynIden, Iden, Index, IndexCreateStatement, SeaRc, TableCreateStatement,
Alias, ColumnDef, DynIden, Iden, Index, IndexCreateStatement, SeaRc, TableCreateStatement,
extension::postgres::{Type, TypeCreateStatement},
};
use std::collections::BTreeMap;

impl Schema {
/// Creates Postgres enums from an ActiveEnum. See [`TypeCreateStatement`] for more details.
/// Returns None if not Postgres.
/// Creates a Postgres enum type from an [`ActiveEnum`]. See [`TypeCreateStatement`] for more details.
/// Returns `None` if not Postgres.
///
/// If the [`ActiveEnum`] has a `schema_name` (via `#[sea_orm(schema_name = "...")]`),
/// the resulting statement will be schema-qualified: `CREATE TYPE "schema"."name" AS ENUM (...)`.
pub fn create_enum_from_active_enum<A>(&self) -> Option<TypeCreateStatement>
where
A: ActiveEnum,
Expand Down Expand Up @@ -106,7 +109,18 @@ where
}
let col_def = A::db_type();
let col_type = col_def.get_column_type();
create_enum_from_column_type(col_type)
let (name, variants) = match col_type {
ColumnType::Enum { name, variants } => (name.clone(), variants.clone()),
_ => return None,
};
let mut stmt = Type::create();
if let Some(schema) = A::schema_name() {
let schema_iden: DynIden = SeaRc::new(Alias::new(schema));
stmt.as_enum((schema_iden, name));
} else {
stmt.as_enum(name);
}
Some(stmt.values(variants).to_owned())
}

pub(crate) fn create_enum_from_column_type(col_type: &ColumnType) -> Option<TypeCreateStatement> {
Expand Down
Loading