Merge pull request #143 from SeaQL/codegen-entity-compact-fomat
Codegen for compact Entity format
This commit is contained in:
commit
fbd390662b
@ -21,9 +21,8 @@ path = "src/main.rs"
|
|||||||
clap = { version = "^2.33.3" }
|
clap = { version = "^2.33.3" }
|
||||||
dotenv = { version = "^0.15" }
|
dotenv = { version = "^0.15" }
|
||||||
async-std = { version = "^1.9", features = [ "attributes" ] }
|
async-std = { version = "^1.9", features = [ "attributes" ] }
|
||||||
sea-orm = { version = "^0.1.2", features = [ "sqlx-all" ] }
|
sea-orm-codegen = { version = "^0.2.0", path = "../sea-orm-codegen" }
|
||||||
sea-orm-codegen = { version = "^0.2.0" }
|
sea-schema = { version = "^0.2.7", git = "https://github.com/SeaQL/sea-schema.git", branch = "update-sea-query-version", default-features = false, features = [
|
||||||
sea-schema = { version = "^0.2.7", default-features = false, features = [
|
|
||||||
"sqlx-mysql",
|
"sqlx-mysql",
|
||||||
"sqlx-postgres",
|
"sqlx-postgres",
|
||||||
"discovery",
|
"discovery",
|
||||||
|
@ -40,6 +40,20 @@ pub fn build_cli() -> App<'static, 'static> {
|
|||||||
.long("include-hidden-tables")
|
.long("include-hidden-tables")
|
||||||
.help("Generate entity file for hidden tables (i.e. table name starts with an underscore)")
|
.help("Generate entity file for hidden tables (i.e. table name starts with an underscore)")
|
||||||
.takes_value(false),
|
.takes_value(false),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("EXPANDED_FORMAT")
|
||||||
|
.long("expanded-format")
|
||||||
|
.help("Generate entity file of expanded format")
|
||||||
|
.takes_value(false)
|
||||||
|
.conflicts_with("COMPACT_FORMAT"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("COMPACT_FORMAT")
|
||||||
|
.long("compact-format")
|
||||||
|
.help("Generate entity file of compact format")
|
||||||
|
.takes_value(false)
|
||||||
|
.conflicts_with("EXPANDED_FORMAT"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.setting(AppSettings::SubcommandRequiredElseHelp);
|
.setting(AppSettings::SubcommandRequiredElseHelp);
|
||||||
|
@ -25,6 +25,7 @@ async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Er
|
|||||||
let url = args.value_of("DATABASE_URL").unwrap();
|
let url = args.value_of("DATABASE_URL").unwrap();
|
||||||
let output_dir = args.value_of("OUTPUT_DIR").unwrap();
|
let output_dir = args.value_of("OUTPUT_DIR").unwrap();
|
||||||
let include_hidden_tables = args.is_present("INCLUDE_HIDDEN_TABLES");
|
let include_hidden_tables = args.is_present("INCLUDE_HIDDEN_TABLES");
|
||||||
|
let expanded_format = args.is_present("EXPANDED_FORMAT");
|
||||||
let filter_hidden_tables = |table: &str| -> bool {
|
let filter_hidden_tables = |table: &str| -> bool {
|
||||||
if include_hidden_tables {
|
if include_hidden_tables {
|
||||||
true
|
true
|
||||||
@ -66,7 +67,7 @@ async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Er
|
|||||||
panic!("This database is not supported ({})", url)
|
panic!("This database is not supported ({})", url)
|
||||||
};
|
};
|
||||||
|
|
||||||
let output = EntityTransformer::transform(table_stmts)?.generate();
|
let output = EntityTransformer::transform(table_stmts)?.generate(expanded_format);
|
||||||
|
|
||||||
let dir = Path::new(output_dir);
|
let dir = Path::new(output_dir);
|
||||||
fs::create_dir_all(dir)?;
|
fs::create_dir_all(dir)?;
|
||||||
|
@ -15,7 +15,7 @@ name = "sea_orm_codegen"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sea-query = { version = "^0.15" }
|
sea-query = { version = "^0.16.1", git = "https://github.com/SeaQL/sea-query.git", branch = "foreign-key-getters" }
|
||||||
syn = { version = "^1", default-features = false, features = [
|
syn = { version = "^1", default-features = false, features = [
|
||||||
"derive",
|
"derive",
|
||||||
"parsing",
|
"parsing",
|
||||||
@ -25,3 +25,6 @@ syn = { version = "^1", default-features = false, features = [
|
|||||||
quote = "^1"
|
quote = "^1"
|
||||||
heck = "^0.3"
|
heck = "^0.3"
|
||||||
proc-macro2 = "^1"
|
proc-macro2 = "^1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pretty_assertions = { version = "^0.7" }
|
||||||
|
@ -91,6 +91,10 @@ impl Entity {
|
|||||||
self.relations.iter().map(|rel| rel.get_def()).collect()
|
self.relations.iter().map(|rel| rel.get_def()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_relation_attrs(&self) -> Vec<TokenStream> {
|
||||||
|
self.relations.iter().map(|rel| rel.get_attrs()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_relation_rel_types(&self) -> Vec<Ident> {
|
pub fn get_relation_rel_types(&self) -> Vec<Ident> {
|
||||||
self.relations
|
self.relations
|
||||||
.iter()
|
.iter()
|
||||||
@ -168,7 +172,7 @@ impl Entity {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::{Column, Entity, PrimaryKey, Relation, RelationType};
|
use crate::{Column, Entity, PrimaryKey, Relation, RelationType};
|
||||||
use quote::format_ident;
|
use quote::format_ident;
|
||||||
use sea_query::ColumnType;
|
use sea_query::{ColumnType, ForeignKeyAction};
|
||||||
|
|
||||||
fn setup() -> Entity {
|
fn setup() -> Entity {
|
||||||
Entity {
|
Entity {
|
||||||
@ -195,12 +199,16 @@ mod tests {
|
|||||||
columns: vec!["id".to_owned()],
|
columns: vec!["id".to_owned()],
|
||||||
ref_columns: vec!["cake_id".to_owned()],
|
ref_columns: vec!["cake_id".to_owned()],
|
||||||
rel_type: RelationType::HasOne,
|
rel_type: RelationType::HasOne,
|
||||||
|
on_delete: Some(ForeignKeyAction::Cascade),
|
||||||
|
on_update: Some(ForeignKeyAction::Cascade),
|
||||||
},
|
},
|
||||||
Relation {
|
Relation {
|
||||||
ref_table: "filling".to_owned(),
|
ref_table: "filling".to_owned(),
|
||||||
columns: vec!["id".to_owned()],
|
columns: vec!["id".to_owned()],
|
||||||
ref_columns: vec!["cake_id".to_owned()],
|
ref_columns: vec!["cake_id".to_owned()],
|
||||||
rel_type: RelationType::HasOne,
|
rel_type: RelationType::HasOne,
|
||||||
|
on_delete: Some(ForeignKeyAction::Cascade),
|
||||||
|
on_update: Some(ForeignKeyAction::Cascade),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
conjunct_relations: vec![],
|
conjunct_relations: vec![],
|
||||||
@ -347,6 +355,18 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_relation_attrs() {
|
||||||
|
let entity = setup();
|
||||||
|
|
||||||
|
for (i, elem) in entity.get_relation_attrs().into_iter().enumerate() {
|
||||||
|
assert_eq!(
|
||||||
|
elem.to_string(),
|
||||||
|
entity.relations[i].get_attrs().to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_relation_rel_types() {
|
fn test_get_relation_rel_types() {
|
||||||
let entity = setup();
|
let entity = setup();
|
||||||
|
@ -2,6 +2,7 @@ use heck::{CamelCase, SnakeCase};
|
|||||||
use proc_macro2::{Ident, TokenStream};
|
use proc_macro2::{Ident, TokenStream};
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use sea_query::{ColumnDef, ColumnSpec, ColumnType};
|
use sea_query::{ColumnDef, ColumnSpec, ColumnType};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Column {
|
pub struct Column {
|
||||||
@ -52,6 +53,20 @@ impl Column {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_col_type_attrs(&self) -> Option<TokenStream> {
|
||||||
|
let col_type = match &self.col_type {
|
||||||
|
ColumnType::Float(Some(l)) => Some(format!("Float(Some({}))", l)),
|
||||||
|
ColumnType::Double(Some(l)) => Some(format!("Double(Some({}))", l)),
|
||||||
|
ColumnType::Decimal(Some((p, s))) => Some(format!("Decimal(Some(({}, {})))", p, s)),
|
||||||
|
ColumnType::Money(Some((p, s))) => Some(format!("Money(Some({}, {}))", p, s)),
|
||||||
|
ColumnType::Custom(iden) => {
|
||||||
|
Some(format!("Custom(\"{}\".to_owned())", iden.to_string()))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
col_type.map(|ty| quote! { column_type = #ty })
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_def(&self) -> TokenStream {
|
pub fn get_def(&self) -> TokenStream {
|
||||||
let mut col_def = match &self.col_type {
|
let mut col_def = match &self.col_type {
|
||||||
ColumnType::Char(s) => match s {
|
ColumnType::Char(s) => match s {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use heck::{CamelCase, SnakeCase};
|
use heck::{CamelCase, SnakeCase};
|
||||||
use proc_macro2::{Ident, TokenStream};
|
use proc_macro2::{Ident, TokenStream};
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use sea_query::TableForeignKey;
|
use sea_query::{ForeignKeyAction, TableForeignKey};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum RelationType {
|
pub enum RelationType {
|
||||||
@ -16,6 +16,8 @@ pub struct Relation {
|
|||||||
pub(crate) columns: Vec<String>,
|
pub(crate) columns: Vec<String>,
|
||||||
pub(crate) ref_columns: Vec<String>,
|
pub(crate) ref_columns: Vec<String>,
|
||||||
pub(crate) rel_type: RelationType,
|
pub(crate) rel_type: RelationType,
|
||||||
|
pub(crate) on_update: Option<ForeignKeyAction>,
|
||||||
|
pub(crate) on_delete: Option<ForeignKeyAction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Relation {
|
impl Relation {
|
||||||
@ -49,6 +51,53 @@ impl Relation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_attrs(&self) -> TokenStream {
|
||||||
|
let rel_type = self.get_rel_type();
|
||||||
|
let ref_table_snake_case = self.get_ref_table_snake_case();
|
||||||
|
let ref_entity = format!("super::{}::Entity", ref_table_snake_case);
|
||||||
|
match self.rel_type {
|
||||||
|
RelationType::HasOne | RelationType::HasMany => {
|
||||||
|
quote! {
|
||||||
|
#[sea_orm(#rel_type = #ref_entity)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RelationType::BelongsTo => {
|
||||||
|
let column_camel_case = self.get_column_camel_case();
|
||||||
|
let ref_column_camel_case = self.get_ref_column_camel_case();
|
||||||
|
let from = format!("Column::{}", column_camel_case);
|
||||||
|
let to = format!(
|
||||||
|
"super::{}::Column::{}",
|
||||||
|
ref_table_snake_case, ref_column_camel_case
|
||||||
|
);
|
||||||
|
let on_update = if let Some(action) = &self.on_update {
|
||||||
|
let action = Self::get_foreign_key_action(action);
|
||||||
|
quote! {
|
||||||
|
on_update = #action,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TokenStream::new()
|
||||||
|
};
|
||||||
|
let on_delete = if let Some(action) = &self.on_delete {
|
||||||
|
let action = Self::get_foreign_key_action(action);
|
||||||
|
quote! {
|
||||||
|
on_delete = #action,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TokenStream::new()
|
||||||
|
};
|
||||||
|
quote! {
|
||||||
|
#[sea_orm(
|
||||||
|
#rel_type = #ref_entity,
|
||||||
|
from = #from,
|
||||||
|
to = #to,
|
||||||
|
#on_update
|
||||||
|
#on_delete
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_rel_type(&self) -> Ident {
|
pub fn get_rel_type(&self) -> Ident {
|
||||||
match self.rel_type {
|
match self.rel_type {
|
||||||
RelationType::HasOne => format_ident!("has_one"),
|
RelationType::HasOne => format_ident!("has_one"),
|
||||||
@ -64,6 +113,17 @@ impl Relation {
|
|||||||
pub fn get_ref_column_camel_case(&self) -> Ident {
|
pub fn get_ref_column_camel_case(&self) -> Ident {
|
||||||
format_ident!("{}", self.ref_columns[0].to_camel_case())
|
format_ident!("{}", self.ref_columns[0].to_camel_case())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_foreign_key_action(action: &ForeignKeyAction) -> String {
|
||||||
|
match action {
|
||||||
|
ForeignKeyAction::Restrict => "Restrict",
|
||||||
|
ForeignKeyAction::Cascade => "Cascade",
|
||||||
|
ForeignKeyAction::SetNull => "SetNull",
|
||||||
|
ForeignKeyAction::NoAction => "NoAction",
|
||||||
|
ForeignKeyAction::SetDefault => "SetDefault",
|
||||||
|
}
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&TableForeignKey> for Relation {
|
impl From<&TableForeignKey> for Relation {
|
||||||
@ -75,11 +135,15 @@ impl From<&TableForeignKey> for Relation {
|
|||||||
let columns = tbl_fk.get_columns();
|
let columns = tbl_fk.get_columns();
|
||||||
let ref_columns = tbl_fk.get_ref_columns();
|
let ref_columns = tbl_fk.get_ref_columns();
|
||||||
let rel_type = RelationType::BelongsTo;
|
let rel_type = RelationType::BelongsTo;
|
||||||
|
let on_delete = tbl_fk.get_on_delete();
|
||||||
|
let on_update = tbl_fk.get_on_update();
|
||||||
Self {
|
Self {
|
||||||
ref_table,
|
ref_table,
|
||||||
columns,
|
columns,
|
||||||
ref_columns,
|
ref_columns,
|
||||||
rel_type,
|
rel_type,
|
||||||
|
on_delete,
|
||||||
|
on_update,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,6 +152,7 @@ impl From<&TableForeignKey> for Relation {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::{Relation, RelationType};
|
use crate::{Relation, RelationType};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
|
use sea_query::ForeignKeyAction;
|
||||||
|
|
||||||
fn setup() -> Vec<Relation> {
|
fn setup() -> Vec<Relation> {
|
||||||
vec![
|
vec![
|
||||||
@ -96,18 +161,24 @@ mod tests {
|
|||||||
columns: vec!["id".to_owned()],
|
columns: vec!["id".to_owned()],
|
||||||
ref_columns: vec!["cake_id".to_owned()],
|
ref_columns: vec!["cake_id".to_owned()],
|
||||||
rel_type: RelationType::HasOne,
|
rel_type: RelationType::HasOne,
|
||||||
|
on_delete: None,
|
||||||
|
on_update: None,
|
||||||
},
|
},
|
||||||
Relation {
|
Relation {
|
||||||
ref_table: "filling".to_owned(),
|
ref_table: "filling".to_owned(),
|
||||||
columns: vec!["filling_id".to_owned()],
|
columns: vec!["filling_id".to_owned()],
|
||||||
ref_columns: vec!["id".to_owned()],
|
ref_columns: vec!["id".to_owned()],
|
||||||
rel_type: RelationType::BelongsTo,
|
rel_type: RelationType::BelongsTo,
|
||||||
|
on_delete: Some(ForeignKeyAction::Cascade),
|
||||||
|
on_update: Some(ForeignKeyAction::Cascade),
|
||||||
},
|
},
|
||||||
Relation {
|
Relation {
|
||||||
ref_table: "filling".to_owned(),
|
ref_table: "filling".to_owned(),
|
||||||
columns: vec!["filling_id".to_owned()],
|
columns: vec!["filling_id".to_owned()],
|
||||||
ref_columns: vec!["id".to_owned()],
|
ref_columns: vec!["id".to_owned()],
|
||||||
rel_type: RelationType::HasMany,
|
rel_type: RelationType::HasMany,
|
||||||
|
on_delete: Some(ForeignKeyAction::Cascade),
|
||||||
|
on_update: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use crate::Entity;
|
use crate::Entity;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
use syn::{punctuated::Punctuated, token::Comma};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct EntityWriter {
|
pub struct EntityWriter {
|
||||||
@ -17,21 +19,25 @@ pub struct OutputFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityWriter {
|
impl EntityWriter {
|
||||||
pub fn generate(self) -> WriterOutput {
|
pub fn generate(self, expanded_format: bool) -> WriterOutput {
|
||||||
let mut files = Vec::new();
|
let mut files = Vec::new();
|
||||||
files.extend(self.write_entities());
|
files.extend(self.write_entities(expanded_format));
|
||||||
files.push(self.write_mod());
|
files.push(self.write_mod());
|
||||||
files.push(self.write_prelude());
|
files.push(self.write_prelude());
|
||||||
WriterOutput { files }
|
WriterOutput { files }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_entities(&self) -> Vec<OutputFile> {
|
pub fn write_entities(&self, expanded_format: bool) -> Vec<OutputFile> {
|
||||||
self.entities
|
self.entities
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entity| {
|
.map(|entity| {
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
Self::write_doc_comment(&mut lines);
|
Self::write_doc_comment(&mut lines);
|
||||||
let code_blocks = Self::gen_code_blocks(entity);
|
let code_blocks = if expanded_format {
|
||||||
|
Self::gen_expanded_code_blocks(entity)
|
||||||
|
} else {
|
||||||
|
Self::gen_compact_code_blocks(entity)
|
||||||
|
};
|
||||||
Self::write(&mut lines, code_blocks);
|
Self::write(&mut lines, code_blocks);
|
||||||
OutputFile {
|
OutputFile {
|
||||||
name: format!("{}.rs", entity.get_table_name_snake_case()),
|
name: format!("{}.rs", entity.get_table_name_snake_case()),
|
||||||
@ -97,7 +103,7 @@ impl EntityWriter {
|
|||||||
lines.push("".to_owned());
|
lines.push("".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gen_code_blocks(entity: &Entity) -> Vec<TokenStream> {
|
pub fn gen_expanded_code_blocks(entity: &Entity) -> Vec<TokenStream> {
|
||||||
let mut code_blocks = vec![
|
let mut code_blocks = vec![
|
||||||
Self::gen_import(),
|
Self::gen_import(),
|
||||||
Self::gen_entity_struct(),
|
Self::gen_entity_struct(),
|
||||||
@ -116,6 +122,23 @@ impl EntityWriter {
|
|||||||
code_blocks
|
code_blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn gen_compact_code_blocks(entity: &Entity) -> Vec<TokenStream> {
|
||||||
|
let mut code_blocks = vec![Self::gen_import(), Self::gen_compact_model_struct(entity)];
|
||||||
|
let relation_defs = if entity.get_relation_ref_tables_camel_case().is_empty() {
|
||||||
|
vec![
|
||||||
|
Self::gen_relation_enum(entity),
|
||||||
|
Self::gen_impl_relation_trait(entity),
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![Self::gen_compact_relation_enum(entity)]
|
||||||
|
};
|
||||||
|
code_blocks.extend(relation_defs);
|
||||||
|
code_blocks.extend(Self::gen_impl_related(entity));
|
||||||
|
code_blocks.extend(Self::gen_impl_conjunct_related(entity));
|
||||||
|
code_blocks.extend(vec![Self::gen_impl_active_model_behavior()]);
|
||||||
|
code_blocks
|
||||||
|
}
|
||||||
|
|
||||||
pub fn gen_import() -> TokenStream {
|
pub fn gen_import() -> TokenStream {
|
||||||
quote! {
|
quote! {
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
@ -297,6 +320,71 @@ impl EntityWriter {
|
|||||||
pub use super::#table_name_snake_case_ident::Entity as #table_name_camel_case_ident;
|
pub use super::#table_name_snake_case_ident::Entity as #table_name_camel_case_ident;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn gen_compact_model_struct(entity: &Entity) -> TokenStream {
|
||||||
|
let table_name = entity.table_name.as_str();
|
||||||
|
let column_names_snake_case = entity.get_column_names_snake_case();
|
||||||
|
let column_rs_types = entity.get_column_rs_types();
|
||||||
|
let primary_keys: Vec<String> = entity
|
||||||
|
.primary_keys
|
||||||
|
.iter()
|
||||||
|
.map(|pk| pk.name.clone())
|
||||||
|
.collect();
|
||||||
|
let attrs: Vec<TokenStream> = entity
|
||||||
|
.columns
|
||||||
|
.iter()
|
||||||
|
.map(|col| {
|
||||||
|
let mut attrs: Punctuated<_, Comma> = Punctuated::new();
|
||||||
|
if primary_keys.contains(&col.name) {
|
||||||
|
attrs.push(quote! { primary_key });
|
||||||
|
if !col.auto_increment {
|
||||||
|
attrs.push(quote! { auto_increment = false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ts) = col.get_col_type_attrs() {
|
||||||
|
attrs.extend(vec![ts]);
|
||||||
|
};
|
||||||
|
if !attrs.is_empty() {
|
||||||
|
let mut ts = TokenStream::new();
|
||||||
|
for (i, attr) in attrs.into_iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
ts = quote! { #ts, };
|
||||||
|
}
|
||||||
|
ts = quote! { #ts #attr };
|
||||||
|
}
|
||||||
|
quote! {
|
||||||
|
#[sea_orm(#ts)]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TokenStream::new()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
quote! {
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = #table_name)]
|
||||||
|
pub struct Model {
|
||||||
|
#(
|
||||||
|
#attrs
|
||||||
|
pub #column_names_snake_case: #column_rs_types,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen_compact_relation_enum(entity: &Entity) -> TokenStream {
|
||||||
|
let relation_ref_tables_camel_case = entity.get_relation_ref_tables_camel_case();
|
||||||
|
let attrs = entity.get_relation_attrs();
|
||||||
|
quote! {
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#(
|
||||||
|
#attrs
|
||||||
|
#relation_ref_tables_camel_case,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -304,18 +392,11 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
Column, ConjunctRelation, Entity, EntityWriter, PrimaryKey, Relation, RelationType,
|
Column, ConjunctRelation, Entity, EntityWriter, PrimaryKey, Relation, RelationType,
|
||||||
};
|
};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use sea_query::ColumnType;
|
use sea_query::{ColumnType, ForeignKeyAction};
|
||||||
use std::io::{self, BufRead, BufReader};
|
use std::io::{self, BufRead, BufReader};
|
||||||
|
|
||||||
const ENTITY_FILES: [&str; 5] = [
|
|
||||||
include_str!("../../tests/entity/cake.rs"),
|
|
||||||
include_str!("../../tests/entity/cake_filling.rs"),
|
|
||||||
include_str!("../../tests/entity/filling.rs"),
|
|
||||||
include_str!("../../tests/entity/fruit.rs"),
|
|
||||||
include_str!("../../tests/entity/vendor.rs"),
|
|
||||||
];
|
|
||||||
|
|
||||||
fn setup() -> Vec<Entity> {
|
fn setup() -> Vec<Entity> {
|
||||||
vec![
|
vec![
|
||||||
Entity {
|
Entity {
|
||||||
@ -341,6 +422,8 @@ mod tests {
|
|||||||
columns: vec![],
|
columns: vec![],
|
||||||
ref_columns: vec![],
|
ref_columns: vec![],
|
||||||
rel_type: RelationType::HasMany,
|
rel_type: RelationType::HasMany,
|
||||||
|
on_delete: None,
|
||||||
|
on_update: None,
|
||||||
}],
|
}],
|
||||||
conjunct_relations: vec![ConjunctRelation {
|
conjunct_relations: vec![ConjunctRelation {
|
||||||
via: "cake_filling".to_owned(),
|
via: "cake_filling".to_owned(),
|
||||||
@ -374,12 +457,16 @@ mod tests {
|
|||||||
columns: vec!["cake_id".to_owned()],
|
columns: vec!["cake_id".to_owned()],
|
||||||
ref_columns: vec!["id".to_owned()],
|
ref_columns: vec!["id".to_owned()],
|
||||||
rel_type: RelationType::BelongsTo,
|
rel_type: RelationType::BelongsTo,
|
||||||
|
on_delete: Some(ForeignKeyAction::Cascade),
|
||||||
|
on_update: Some(ForeignKeyAction::Cascade),
|
||||||
},
|
},
|
||||||
Relation {
|
Relation {
|
||||||
ref_table: "filling".to_owned(),
|
ref_table: "filling".to_owned(),
|
||||||
columns: vec!["filling_id".to_owned()],
|
columns: vec!["filling_id".to_owned()],
|
||||||
ref_columns: vec!["id".to_owned()],
|
ref_columns: vec!["id".to_owned()],
|
||||||
rel_type: RelationType::BelongsTo,
|
rel_type: RelationType::BelongsTo,
|
||||||
|
on_delete: Some(ForeignKeyAction::Cascade),
|
||||||
|
on_update: Some(ForeignKeyAction::Cascade),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
conjunct_relations: vec![],
|
conjunct_relations: vec![],
|
||||||
@ -450,12 +537,16 @@ mod tests {
|
|||||||
columns: vec!["cake_id".to_owned()],
|
columns: vec!["cake_id".to_owned()],
|
||||||
ref_columns: vec!["id".to_owned()],
|
ref_columns: vec!["id".to_owned()],
|
||||||
rel_type: RelationType::BelongsTo,
|
rel_type: RelationType::BelongsTo,
|
||||||
|
on_delete: None,
|
||||||
|
on_update: None,
|
||||||
},
|
},
|
||||||
Relation {
|
Relation {
|
||||||
ref_table: "vendor".to_owned(),
|
ref_table: "vendor".to_owned(),
|
||||||
columns: vec![],
|
columns: vec![],
|
||||||
ref_columns: vec![],
|
ref_columns: vec![],
|
||||||
rel_type: RelationType::HasMany,
|
rel_type: RelationType::HasMany,
|
||||||
|
on_delete: None,
|
||||||
|
on_update: None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
conjunct_relations: vec![],
|
conjunct_relations: vec![],
|
||||||
@ -493,6 +584,8 @@ mod tests {
|
|||||||
columns: vec!["fruit_id".to_owned()],
|
columns: vec!["fruit_id".to_owned()],
|
||||||
ref_columns: vec!["id".to_owned()],
|
ref_columns: vec!["id".to_owned()],
|
||||||
rel_type: RelationType::BelongsTo,
|
rel_type: RelationType::BelongsTo,
|
||||||
|
on_delete: None,
|
||||||
|
on_update: None,
|
||||||
}],
|
}],
|
||||||
conjunct_relations: vec![],
|
conjunct_relations: vec![],
|
||||||
primary_keys: vec![PrimaryKey {
|
primary_keys: vec![PrimaryKey {
|
||||||
@ -503,8 +596,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gen_code_blocks() -> io::Result<()> {
|
fn test_gen_expanded_code_blocks() -> io::Result<()> {
|
||||||
let entities = setup();
|
let entities = setup();
|
||||||
|
const ENTITY_FILES: [&str; 5] = [
|
||||||
|
include_str!("../../tests/expanded/cake.rs"),
|
||||||
|
include_str!("../../tests/expanded/cake_filling.rs"),
|
||||||
|
include_str!("../../tests/expanded/filling.rs"),
|
||||||
|
include_str!("../../tests/expanded/fruit.rs"),
|
||||||
|
include_str!("../../tests/expanded/vendor.rs"),
|
||||||
|
];
|
||||||
|
|
||||||
assert_eq!(entities.len(), ENTITY_FILES.len());
|
assert_eq!(entities.len(), ENTITY_FILES.len());
|
||||||
|
|
||||||
@ -521,7 +621,46 @@ mod tests {
|
|||||||
}
|
}
|
||||||
let content = lines.join("");
|
let content = lines.join("");
|
||||||
let expected: TokenStream = content.parse().unwrap();
|
let expected: TokenStream = content.parse().unwrap();
|
||||||
let generated = EntityWriter::gen_code_blocks(entity)
|
let generated = EntityWriter::gen_expanded_code_blocks(entity)
|
||||||
|
.into_iter()
|
||||||
|
.skip(1)
|
||||||
|
.fold(TokenStream::new(), |mut acc, tok| {
|
||||||
|
acc.extend(tok);
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
assert_eq!(expected.to_string(), generated.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gen_compact_code_blocks() -> io::Result<()> {
|
||||||
|
let entities = setup();
|
||||||
|
const ENTITY_FILES: [&str; 5] = [
|
||||||
|
include_str!("../../tests/compact/cake.rs"),
|
||||||
|
include_str!("../../tests/compact/cake_filling.rs"),
|
||||||
|
include_str!("../../tests/compact/filling.rs"),
|
||||||
|
include_str!("../../tests/compact/fruit.rs"),
|
||||||
|
include_str!("../../tests/compact/vendor.rs"),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(entities.len(), ENTITY_FILES.len());
|
||||||
|
|
||||||
|
for (i, entity) in entities.iter().enumerate() {
|
||||||
|
let mut reader = BufReader::new(ENTITY_FILES[i].as_bytes());
|
||||||
|
let mut lines: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
reader.read_until(b';', &mut Vec::new())?;
|
||||||
|
|
||||||
|
let mut line = String::new();
|
||||||
|
while reader.read_line(&mut line)? > 0 {
|
||||||
|
lines.push(line.to_owned());
|
||||||
|
line.clear();
|
||||||
|
}
|
||||||
|
let content = lines.join("");
|
||||||
|
let expected: TokenStream = content.parse().unwrap();
|
||||||
|
let generated = EntityWriter::gen_compact_code_blocks(entity)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.fold(TokenStream::new(), |mut acc, tok| {
|
.fold(TokenStream::new(), |mut acc, tok| {
|
||||||
|
34
sea-orm-codegen/tests/compact/cake.rs
Normal file
34
sea-orm-codegen/tests/compact/cake.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "cake")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(has_many = "super::fruit::Entity")]
|
||||||
|
Fruit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::fruit::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Fruit.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::filling::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
super::cake_filling::Relation::Filling.def()
|
||||||
|
}
|
||||||
|
fn via() -> Option<RelationDef> {
|
||||||
|
Some(super::cake_filling::Relation::Cake.def().rev())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
46
sea-orm-codegen/tests/compact/cake_filling.rs
Normal file
46
sea-orm-codegen/tests/compact/cake_filling.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "_cake_filling_")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub cake_id: i32,
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub filling_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::cake::Entity",
|
||||||
|
from = "Column::CakeId",
|
||||||
|
to = "super::cake::Column::Id",
|
||||||
|
on_update = "Cascade",
|
||||||
|
on_delete = "Cascade",
|
||||||
|
)]
|
||||||
|
Cake,
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::filling::Entity",
|
||||||
|
from = "Column::FillingId",
|
||||||
|
to = "super::filling::Column::Id",
|
||||||
|
on_update = "Cascade",
|
||||||
|
on_delete = "Cascade",
|
||||||
|
)]
|
||||||
|
Filling,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::cake::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Cake.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::filling::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Filling.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
33
sea-orm-codegen/tests/compact/filling.rs
Normal file
33
sea-orm-codegen/tests/compact/filling.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "filling")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl RelationTrait for Relation {
|
||||||
|
fn def(&self) -> RelationDef {
|
||||||
|
match self {
|
||||||
|
_ => panic!("No RelationDef"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::cake::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
super::cake_filling::Relation::Cake.def()
|
||||||
|
}
|
||||||
|
fn via() -> Option<RelationDef> {
|
||||||
|
Some(super::cake_filling::Relation::Filling.def().rev())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
38
sea-orm-codegen/tests/compact/fruit.rs
Normal file
38
sea-orm-codegen/tests/compact/fruit.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "fruit")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
pub name: String,
|
||||||
|
pub cake_id: Option<i32> ,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::cake::Entity",
|
||||||
|
from = "Column::CakeId",
|
||||||
|
to = "super::cake::Column::Id",
|
||||||
|
)]
|
||||||
|
Cake,
|
||||||
|
#[sea_orm(has_many = "super::vendor::Entity")]
|
||||||
|
Vendor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::cake::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Cake.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::vendor::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Vendor.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
30
sea-orm-codegen/tests/compact/vendor.rs
Normal file
30
sea-orm-codegen/tests/compact/vendor.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "vendor")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
pub name: String,
|
||||||
|
pub fruit_id: Option<i32> ,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::fruit::Entity",
|
||||||
|
from = "Column::FruitId",
|
||||||
|
to = "super::fruit::Column::Id",
|
||||||
|
)]
|
||||||
|
Fruit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::fruit::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Fruit.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
9
sea-orm-codegen/tests/expanded/mod.rs
Normal file
9
sea-orm-codegen/tests/expanded/mod.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||||
|
|
||||||
|
pub mod prelude;
|
||||||
|
|
||||||
|
pub mod cake;
|
||||||
|
pub mod cake_filling;
|
||||||
|
pub mod filling;
|
||||||
|
pub mod fruit;
|
||||||
|
pub mod vendor;
|
7
sea-orm-codegen/tests/expanded/prelude.rs
Normal file
7
sea-orm-codegen/tests/expanded/prelude.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||||
|
|
||||||
|
pub use super::cake::Entity as Cake;
|
||||||
|
pub use super::cake_filling::Entity as CakeFilling;
|
||||||
|
pub use super::filling::Entity as Filling;
|
||||||
|
pub use super::fruit::Entity as Fruit;
|
||||||
|
pub use super::vendor::Entity as Vendor;
|
Loading…
x
Reference in New Issue
Block a user