Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
27 changes: 24 additions & 3 deletions sea-orm-macros/src/derives/active_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use syn::{Expr, Lit, LitInt, LitStr, UnOp, parse};
struct ActiveEnum {
ident: syn::Ident,
enum_name: String,
schema_name: Option<String>,
Comment thread
nils-degroot marked this conversation as resolved.
Outdated
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 @@ -299,6 +304,7 @@ impl ActiveEnum {
Ok(Self {
ident,
enum_name,
schema_name,
rs_type,
db_type,
is_string,
Expand Down Expand Up @@ -504,6 +510,15 @@ impl ActiveEnum {
}
};

let schema_name_impl = match &self.schema_name {
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 Down Expand Up @@ -593,16 +610,20 @@ 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 = match &self.schema_name {
Comment thread
nils-degroot marked this conversation as resolved.
Outdated
Some(schema) => format!("{}.{}", schema, self.enum_name),
None => self.enum_name.clone(),
};

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 +652,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
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
125 changes: 125 additions & 0 deletions sea-orm-sync/tests/active_enum_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1153,4 +1153,129 @@ mod tests {
r#"SELECT CAST("aaa"."tea" AS "text") AS "tea", CAST("foo"."tea" AS "text") AS "nested_tea" FROM "public"."active_enum""#,
);
}

#[test]
fn create_enum_from_active_enum_with_schema_name() {
use sea_orm::{Schema, Statement};

let db_postgres = DbBackend::Postgres;
let schema = Schema::new(db_postgres);

assert_eq!(
db_postgres.build(&schema.create_enum_from_active_enum::<Mood>().unwrap()),
Statement::from_string(
db_postgres,
r#"CREATE TYPE "my_schema"."mood" AS ENUM ('Happy', 'Sad')"#.to_owned()
)
);

assert_eq!(
db_postgres.build(&schema.create_enum_from_active_enum::<Priority>().unwrap()),
Statement::from_string(
db_postgres,
r#"CREATE TYPE "my_schema"."priority" AS ENUM ('Low', 'High')"#.to_owned()
)
);
}

#[test]
fn active_enum_schema_name_returns_correct_value() {
use sea_orm::ActiveEnum as ActiveEnumTrait2;

assert_eq!(Mood::schema_name(), Some("my_schema"));
assert_eq!(Priority::schema_name(), Some("my_schema"));
assert_eq!(Tea::schema_name(), None);
}

#[test]
fn create_enum_without_schema_name_unchanged() {
use sea_orm::{Schema, Statement};

let db_postgres = DbBackend::Postgres;
let schema = Schema::new(db_postgres);

assert_eq!(
db_postgres.build(&schema.create_enum_from_active_enum::<Tea>().unwrap()),
Statement::from_string(
db_postgres,
r#"CREATE TYPE "tea" AS ENUM ('EverydayTea', 'BreakfastTea', 'AfternoonTea')"#
.to_owned()
)
);
}

#[cfg(feature = "sqlx-postgres")]
#[test]
fn schema_enum_find_select_sql() {
let select = schema_enum::Entity::find();
assert_eq!(
select.build(DbBackend::Postgres).to_string(),
[
r#"SELECT "schema_enum"."id","#,
r#"CAST("schema_enum"."mood" AS "text"),"#,
r#"CAST("schema_enum"."priority" AS "text")"#,
r#"FROM "my_schema"."schema_enum""#,
]
.join(" ")
);
}

#[cfg(feature = "sqlx-postgres")]
#[test]
fn schema_enum_filter_is_in_sql() {
let select = schema_enum::Entity::find()
.filter(schema_enum::Column::Mood.is_in([Mood::Happy, Mood::Sad]));
assert_eq!(
select.build(DbBackend::Postgres).to_string(),
[
r#"SELECT "schema_enum"."id","#,
r#"CAST("schema_enum"."mood" AS "text"),"#,
r#"CAST("schema_enum"."priority" AS "text")"#,
r#"FROM "my_schema"."schema_enum""#,
r#"WHERE "schema_enum"."mood" IN ('Happy'::"mood", 'Sad'::"mood")"#,
]
.join(" ")
);
}

#[cfg(feature = "sqlx-postgres")]
#[test]
fn schema_enum_filter_eq_sql() {
let select = schema_enum::Entity::find().filter(schema_enum::Column::Mood.eq(Mood::Happy));
assert_eq!(
select.build(DbBackend::Postgres).to_string(),
[
r#"SELECT "schema_enum"."id","#,
r#"CAST("schema_enum"."mood" AS "text"),"#,
r#"CAST("schema_enum"."priority" AS "text")"#,
r#"FROM "my_schema"."schema_enum""#,
r#"WHERE "schema_enum"."mood" = 'Happy'::"mood""#,
]
.join(" ")
);
}

#[test]
fn schema_enum_create_type_from_active_enum() {
use sea_orm::{Schema, Statement};

let db_postgres = DbBackend::Postgres;
let schema = Schema::new(db_postgres);

assert_eq!(
db_postgres.build(&schema.create_enum_from_active_enum::<Mood>().unwrap()),
Statement::from_string(
db_postgres,
r#"CREATE TYPE "my_schema"."mood" AS ENUM ('Happy', 'Sad')"#.to_owned()
)
);

assert_eq!(
db_postgres.build(&schema.create_enum_from_active_enum::<Priority>().unwrap()),
Statement::from_string(
db_postgres,
r#"CREATE TYPE "my_schema"."priority" AS ENUM ('Low', 'High')"#.to_owned()
)
);
}
}
2 changes: 2 additions & 0 deletions sea-orm-sync/tests/common/features/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub mod pi;
pub mod repository;
pub mod satellite;
pub mod schema;
pub mod schema_enum;
pub mod sea_orm_active_enums;
pub mod self_join;
pub mod teas;
Expand Down Expand Up @@ -57,6 +58,7 @@ pub use metadata::Entity as Metadata;
pub use repository::Entity as Repository;
pub use satellite::Entity as Satellite;
pub use schema::*;
pub use schema_enum::Entity as SchemaEnum;
pub use sea_orm_active_enums::*;
pub use self_join::Entity as SelfJoin;
pub use teas::Entity as Teas;
Expand Down
Loading
Loading