Skip to content

Commit

Permalink
codegen ActiveEnum & create enum from ActiveEnum
Browse files Browse the repository at this point in the history
  • Loading branch information
billy1624 committed Dec 12, 2021
1 parent 66a0052 commit e9298df
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 38 deletions.
2 changes: 1 addition & 1 deletion sea-orm-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ clap = { version = "^2.33.3" }
dotenv = { version = "^0.15" }
async-std = { version = "^1.9", features = [ "attributes" ] }
sea-orm-codegen = { version = "^0.4.2", path = "../sea-orm-codegen" }
sea-schema = { version = "^0.2.9", default-features = false, features = [
sea-schema = { version = "0.3.0", default-features = false, features = [
"debug-print",
"sqlx-mysql",
"sqlx-postgres",
Expand Down
2 changes: 1 addition & 1 deletion sea-orm-codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ name = "sea_orm_codegen"
path = "src/lib.rs"

[dependencies]
sea-query = { version = "^0.16.4" }
sea-query = { version = "0.20.0" }
syn = { version = "^1", default-features = false, features = [
"derive",
"parsing",
Expand Down
31 changes: 31 additions & 0 deletions sea-orm-codegen/src/entity/active_enum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use heck::CamelCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};

#[derive(Clone, Debug)]
pub struct ActiveEnum {
pub(crate) enum_name: String,
pub(crate) values: Vec<String>,
}

impl ActiveEnum {
pub fn impl_active_enum(&self) -> TokenStream {
let enum_name = &self.enum_name;
let enum_iden = format_ident!("{}", enum_name.to_camel_case());
let values = &self.values;
let variants = self
.values
.iter()
.map(|v| format_ident!("{}", v.to_camel_case()));
quote! {
#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = #enum_name)]
pub enum #enum_iden {
#(
#[sea_orm(string_value = #values)]
#variants,
)*
}
}
}
}
35 changes: 18 additions & 17 deletions sea-orm-codegen/src/entity/column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,27 @@ impl Column {

pub fn get_rs_type(&self) -> TokenStream {
#[allow(unreachable_patterns)]
let ident: TokenStream = match self.col_type {
let ident: TokenStream = match &self.col_type {
ColumnType::Char(_)
| ColumnType::String(_)
| ColumnType::Text
| ColumnType::Custom(_) => "String",
ColumnType::TinyInteger(_) => "i8",
ColumnType::SmallInteger(_) => "i16",
ColumnType::Integer(_) => "i32",
ColumnType::BigInteger(_) => "i64",
ColumnType::Float(_) => "f32",
ColumnType::Double(_) => "f64",
ColumnType::Json | ColumnType::JsonBinary => "Json",
ColumnType::Date => "Date",
ColumnType::Time(_) => "Time",
ColumnType::DateTime(_) | ColumnType::Timestamp(_) => "DateTime",
ColumnType::TimestampWithTimeZone(_) => "DateTimeWithTimeZone",
ColumnType::Decimal(_) | ColumnType::Money(_) => "Decimal",
ColumnType::Uuid => "Uuid",
ColumnType::Binary(_) => "Vec<u8>",
ColumnType::Boolean => "bool",
| ColumnType::Custom(_) => "String".to_owned(),
ColumnType::TinyInteger(_) => "i8".to_owned(),
ColumnType::SmallInteger(_) => "i16".to_owned(),
ColumnType::Integer(_) => "i32".to_owned(),
ColumnType::BigInteger(_) => "i64".to_owned(),
ColumnType::Float(_) => "f32".to_owned(),
ColumnType::Double(_) => "f64".to_owned(),
ColumnType::Json | ColumnType::JsonBinary => "Json".to_owned(),
ColumnType::Date => "Date".to_owned(),
ColumnType::Time(_) => "Time".to_owned(),
ColumnType::DateTime(_) | ColumnType::Timestamp(_) => "DateTime".to_owned(),
ColumnType::TimestampWithTimeZone(_) => "DateTimeWithTimeZone".to_owned(),
ColumnType::Decimal(_) | ColumnType::Money(_) => "Decimal".to_owned(),
ColumnType::Uuid => "Uuid".to_owned(),
ColumnType::Binary(_) => "Vec<u8>".to_owned(),
ColumnType::Boolean => "bool".to_owned(),
ColumnType::Enum(name, _) => name.to_camel_case(),
_ => unimplemented!(),
}
.parse()
Expand Down
2 changes: 2 additions & 0 deletions sea-orm-codegen/src/entity/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod active_enum;
mod base_entity;
mod column;
mod conjunct_relation;
Expand All @@ -6,6 +7,7 @@ mod relation;
mod transformer;
mod writer;

pub use active_enum::*;
pub use base_entity::*;
pub use column::*;
pub use conjunct_relation::*;
Expand Down
27 changes: 25 additions & 2 deletions sea-orm-codegen/src/entity/transformer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
Column, ConjunctRelation, Entity, EntityWriter, Error, PrimaryKey, Relation, RelationType,
ActiveEnum, Column, ConjunctRelation, Entity, EntityWriter, Error, PrimaryKey, Relation,
RelationType,
};
use sea_query::TableStatement;
use std::collections::HashMap;
Expand All @@ -9,6 +10,7 @@ pub struct EntityTransformer;

impl EntityTransformer {
pub fn transform(table_stmts: Vec<TableStatement>) -> Result<EntityWriter, Error> {
let mut enums: HashMap<String, ActiveEnum> = HashMap::new();
let mut inverse_relations: HashMap<String, Vec<Relation>> = HashMap::new();
let mut conjunct_relations: HashMap<String, Vec<ConjunctRelation>> = HashMap::new();
let mut entities = HashMap::new();
Expand All @@ -22,7 +24,15 @@ impl EntityTransformer {
}
};
let table_name = match table_create.get_table_name() {
Some(s) => s,
Some(table_ref) => match table_ref {
sea_query::TableRef::Table(t)
| sea_query::TableRef::SchemaTable(_, t)
| sea_query::TableRef::DatabaseSchemaTable(_, _, t)
| sea_query::TableRef::TableAlias(t, _)
| sea_query::TableRef::SchemaTableAlias(_, t, _)
| sea_query::TableRef::DatabaseSchemaTableAlias(_, _, t, _) => t.to_string(),
_ => unimplemented!(),
},
None => {
return Err(Error::TransformError(
"Table name should not be empty".into(),
Expand All @@ -44,6 +54,18 @@ impl EntityTransformer {
> 0;
col
})
.map(|col| {
if let sea_query::ColumnType::Enum(enum_name, values) = &col.col_type {
enums.insert(
enum_name.clone(),
ActiveEnum {
enum_name: enum_name.clone(),
values: values.clone(),
},
);
}
col
})
.collect();
let mut ref_table_counts: HashMap<String, usize> = HashMap::new();
let relations: Vec<Relation> = table_create
Expand Down Expand Up @@ -170,6 +192,7 @@ impl EntityTransformer {
}
Ok(EntityWriter {
entities: entities.into_iter().map(|(_, v)| v).collect(),
enums,
})
}
}
65 changes: 59 additions & 6 deletions sea-orm-codegen/src/entity/writer.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use std::str::FromStr;

use crate::Entity;
use crate::{ActiveEnum, Entity};
use heck::CamelCase;
use proc_macro2::TokenStream;
use quote::quote;
use quote::{format_ident, quote};
use std::{collections::HashMap, str::FromStr};
use syn::{punctuated::Punctuated, token::Comma};

#[derive(Clone, Debug)]
pub struct EntityWriter {
pub(crate) entities: Vec<Entity>,
pub(crate) enums: HashMap<String, ActiveEnum>,
}

pub struct WriterOutput {
Expand Down Expand Up @@ -83,6 +84,9 @@ impl EntityWriter {
files.extend(self.write_entities(expanded_format, with_serde));
files.push(self.write_mod());
files.push(self.write_prelude());
if !self.enums.is_empty() {
files.push(self.write_sea_orm_active_enums());
}
WriterOutput { files }
}

Expand Down Expand Up @@ -122,6 +126,14 @@ impl EntityWriter {
);
lines.push("".to_owned());
Self::write(&mut lines, code_blocks);
if !self.enums.is_empty() {
Self::write(
&mut lines,
vec![quote! {
pub mod sea_orm_active_enums;
}],
);
}
OutputFile {
name: "mod.rs".to_owned(),
content: lines.join("\n"),
Expand All @@ -143,6 +155,28 @@ impl EntityWriter {
}
}

pub fn write_sea_orm_active_enums(&self) -> OutputFile {
let mut lines = Vec::new();
Self::write_doc_comment(&mut lines);
Self::write(
&mut lines,
vec![quote! {
use sea_orm::entity::prelude::*;
}],
);
lines.push("".to_owned());
let code_blocks = self
.enums
.iter()
.map(|(_, active_enum)| active_enum.impl_active_enum())
.collect();
Self::write(&mut lines, code_blocks);
OutputFile {
name: "sea_orm_active_enums.rs".to_owned(),
content: lines.join("\n"),
}
}

pub fn write(lines: &mut Vec<String>, code_blocks: Vec<TokenStream>) {
lines.extend(
code_blocks
Expand All @@ -163,8 +197,10 @@ impl EntityWriter {
}

pub fn gen_expanded_code_blocks(entity: &Entity, with_serde: &WithSerde) -> Vec<TokenStream> {
let mut imports = Self::gen_import(with_serde);
imports.extend(Self::gen_import_active_enum(entity));
let mut code_blocks = vec![
Self::gen_import(with_serde),
imports,
Self::gen_entity_struct(),
Self::gen_impl_entity_name(entity),
Self::gen_model_struct(entity, with_serde),
Expand All @@ -182,8 +218,10 @@ impl EntityWriter {
}

pub fn gen_compact_code_blocks(entity: &Entity, with_serde: &WithSerde) -> Vec<TokenStream> {
let mut imports = Self::gen_import(with_serde);
imports.extend(Self::gen_import_active_enum(entity));
let mut code_blocks = vec![
Self::gen_import(with_serde),
imports,
Self::gen_compact_model_struct(entity, with_serde),
];
let relation_defs = if entity.get_relation_enum_name().is_empty() {
Expand Down Expand Up @@ -249,6 +287,21 @@ impl EntityWriter {
}
}

pub fn gen_import_active_enum(entity: &Entity) -> TokenStream {
entity
.columns
.iter()
.fold(TokenStream::new(), |mut ts, col| {
if let sea_query::ColumnType::Enum(enum_name, _) = &col.col_type {
let enum_name = format_ident!("{}", enum_name.to_camel_case());
ts.extend(vec![quote! {
use super::sea_orm_active_enums::#enum_name;
}]);
}
ts
})
}

pub fn gen_model_struct(entity: &Entity, with_serde: &WithSerde) -> TokenStream {
let column_names_snake_case = entity.get_column_names_snake_case();
let column_rs_types = entity.get_column_rs_types();
Expand Down
45 changes: 35 additions & 10 deletions src/schema/entity.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
use crate::{
unpack_table_ref, ColumnTrait, ColumnType, DbBackend, EntityTrait, Identity, Iterable,
PrimaryKeyToColumn, PrimaryKeyTrait, RelationTrait, Schema,
unpack_table_ref, ActiveEnum, ColumnTrait, ColumnType, DbBackend, EntityTrait, Identity,
Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, RelationTrait, Schema,
};
use sea_query::{
extension::postgres::{Type, TypeCreateStatement},
Alias, ColumnDef, ForeignKeyCreateStatement, Iden, Index, TableCreateStatement,
};

impl Schema {
/// Creates Postgres enums from an ActiveEnum. See [TypeCreateStatement] for more details
pub fn create_enum_from_active_enum<A>(&self) -> TypeCreateStatement
where
A: ActiveEnum,
{
create_enum_from_active_enum::<A>(self.backend)
}

/// Creates Postgres enums from an Entity. See [TypeCreateStatement] for more details
pub fn create_enum_from_entity<E>(&self, entity: E) -> Vec<TypeCreateStatement>
where
Expand All @@ -25,6 +33,30 @@ impl Schema {
}
}

pub(crate) fn create_enum_from_active_enum<A>(backend: DbBackend) -> TypeCreateStatement
where
A: ActiveEnum,
{
if matches!(backend, DbBackend::MySql | DbBackend::Sqlite) {
panic!("TypeCreateStatement is not supported in MySQL & SQLite");
}
let col_def = A::db_type();
let col_type = col_def.get_column_type();
create_enum_from_column_type(col_type)
}

pub(crate) fn create_enum_from_column_type(col_type: &ColumnType) -> TypeCreateStatement {
let (name, values) = match col_type {
ColumnType::Enum(s, v) => (s.as_str(), v),
_ => panic!("Should be ColumnType::Enum"),
};
Type::create()
.as_enum(Alias::new(name))
.values(values.iter().map(|val| Alias::new(val.as_str())))
.to_owned()
}

#[allow(clippy::needless_borrow)]
pub(crate) fn create_enum_from_entity<E>(_: E, backend: DbBackend) -> Vec<TypeCreateStatement>
where
E: EntityTrait,
Expand All @@ -39,14 +71,7 @@ where
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.iter().map(|val| Alias::new(val.as_str())))
.to_owned();
let stmt = create_enum_from_column_type(&col_type);
vec.push(stmt);
}
vec
Expand Down
2 changes: 1 addition & 1 deletion tests/common/features/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::*;
use crate::common::setup::{create_enum, create_table, create_table_without_asserts};
use sea_orm::{
error::*, sea_query, ConnectionTrait, DatabaseConnection, DbBackend, DbConn, EntityName,
ExecResult,
ExecResult, Schema,
};
use sea_query::{extension::postgres::Type, Alias, ColumnDef, ForeignKeyCreateStatement};

Expand Down

0 comments on commit e9298df

Please sign in to comment.