Codegen: Entity Generator (#23)
This commit is contained in:
parent
0b3ea685d5
commit
cab4b5a3f7
@ -2,7 +2,10 @@
|
||||
members = [
|
||||
".",
|
||||
"sea-orm-macros",
|
||||
"sea-orm-codegen",
|
||||
"sea-orm-cli",
|
||||
"examples/sqlx-mysql",
|
||||
"examples/codegen",
|
||||
]
|
||||
|
||||
[package]
|
||||
@ -28,7 +31,6 @@ futures = { version = "^0.3" }
|
||||
futures-util = { version = "^0.3" }
|
||||
sea-query = { path = "../sea-query", version = "^0.12" }
|
||||
sea-orm-macros = { path = "sea-orm-macros", optional = true }
|
||||
# sea-schema = { path = "../sea-schema" }
|
||||
serde = { version = "^1.0", features = [ "derive" ] }
|
||||
sqlx = { version = "^0.5", optional = true }
|
||||
strum = { version = "^0.20", features = [ "derive" ] }
|
||||
|
14
examples/codegen/Cargo.toml
Normal file
14
examples/codegen/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "sea-orm-codegen-example"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
async-std = { version = "^1.9", features = [ "attributes" ] }
|
||||
sea-orm = { path = "../../", features = [ "sqlx-mysql", "runtime-async-std-native-tls", "debug-print" ] }
|
||||
sea-orm-codegen = { path = "../../sea-orm-codegen" }
|
||||
sea-query = { path = "../../../sea-query" }
|
||||
strum = { version = "^0.20", features = [ "derive" ] }
|
||||
serde_json = { version = "^1" }
|
||||
quote = "1"
|
12
examples/codegen/README.md
Normal file
12
examples/codegen/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# SeaORM Entity Generator Example
|
||||
|
||||
Prepare:
|
||||
|
||||
Setup a test database and configure the connection string in `main.rs`.
|
||||
Run `bakery.sql` to setup the test table and data.
|
||||
|
||||
Running:
|
||||
|
||||
```sh
|
||||
cargo run
|
||||
```
|
16
examples/codegen/src/main.rs
Normal file
16
examples/codegen/src/main.rs
Normal file
@ -0,0 +1,16 @@
|
||||
mod out;
|
||||
|
||||
use sea_orm_codegen::{EntityGenerator, Error};
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
let uri = "mysql://sea:sea@localhost/bakery";
|
||||
let schema = "bakery";
|
||||
|
||||
let _generator = EntityGenerator::discover(uri, schema)
|
||||
.await?
|
||||
.transform()?
|
||||
.generate("src/out")?;
|
||||
|
||||
Ok(())
|
||||
}
|
89
examples/codegen/src/out/cake.rs
Normal file
89
examples/codegen/src/out/cake.rs
Normal file
@ -0,0 +1,89 @@
|
||||
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
||||
pub struct Entity;
|
||||
|
||||
impl EntityName for Entity {
|
||||
fn table_name(&self) -> &str {
|
||||
"cake"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
|
||||
pub struct Model {
|
||||
pub id: i32,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
pub enum Column {
|
||||
Id,
|
||||
Name,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
|
||||
pub enum PrimaryKey {
|
||||
Id,
|
||||
}
|
||||
|
||||
impl PrimaryKeyTrait for PrimaryKey {
|
||||
fn auto_increment() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {
|
||||
CakeFilling,
|
||||
Fruit,
|
||||
}
|
||||
|
||||
impl ColumnTrait for Column {
|
||||
type EntityName = Entity;
|
||||
fn def(&self) -> ColumnType {
|
||||
match self {
|
||||
Self::Id => ColumnType::Integer(Some(11u32)),
|
||||
Self::Name => ColumnType::String(Some(255u32)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
Self::CakeFilling => Entity::has_many(super::cake_filling::Entity)
|
||||
.from(Column::Id)
|
||||
.to(super::cake_filling::Column::CakeId)
|
||||
.into(),
|
||||
Self::Fruit => Entity::has_many(super::fruit::Entity)
|
||||
.from(Column::Id)
|
||||
.to(super::fruit::Column::CakeId)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::cake_filling::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::CakeFilling.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::fruit::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Fruit.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn find_cake_filling(&self) -> Select<super::cake_filling::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
pub fn find_fruit(&self) -> Select<super::fruit::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
90
examples/codegen/src/out/cake_filling.rs
Normal file
90
examples/codegen/src/out/cake_filling.rs
Normal file
@ -0,0 +1,90 @@
|
||||
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
||||
pub struct Entity;
|
||||
|
||||
impl EntityName for Entity {
|
||||
fn table_name(&self) -> &str {
|
||||
"cake_filling"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
|
||||
pub struct Model {
|
||||
pub cake_id: i32,
|
||||
pub filling_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
pub enum Column {
|
||||
CakeId,
|
||||
FillingId,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
|
||||
pub enum PrimaryKey {
|
||||
CakeId,
|
||||
FillingId,
|
||||
}
|
||||
|
||||
impl PrimaryKeyTrait for PrimaryKey {
|
||||
fn auto_increment() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {
|
||||
Cake,
|
||||
Filling,
|
||||
}
|
||||
|
||||
impl ColumnTrait for Column {
|
||||
type EntityName = Entity;
|
||||
fn def(&self) -> ColumnType {
|
||||
match self {
|
||||
Self::CakeId => ColumnType::Integer(Some(11u32)),
|
||||
Self::FillingId => ColumnType::Integer(Some(11u32)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
Self::Cake => Entity::has_one(super::cake::Entity)
|
||||
.from(Column::CakeId)
|
||||
.to(super::cake::Column::Id)
|
||||
.into(),
|
||||
Self::Filling => Entity::has_one(super::filling::Entity)
|
||||
.from(Column::FillingId)
|
||||
.to(super::filling::Column::Id)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 Model {
|
||||
pub fn find_cake(&self) -> Select<super::cake::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
pub fn find_filling(&self) -> Select<super::filling::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
75
examples/codegen/src/out/filling.rs
Normal file
75
examples/codegen/src/out/filling.rs
Normal file
@ -0,0 +1,75 @@
|
||||
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
||||
pub struct Entity;
|
||||
|
||||
impl EntityName for Entity {
|
||||
fn table_name(&self) -> &str {
|
||||
"filling"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
|
||||
pub struct Model {
|
||||
pub id: i32,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
pub enum Column {
|
||||
Id,
|
||||
Name,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
|
||||
pub enum PrimaryKey {
|
||||
Id,
|
||||
}
|
||||
|
||||
impl PrimaryKeyTrait for PrimaryKey {
|
||||
fn auto_increment() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {
|
||||
CakeFilling,
|
||||
}
|
||||
|
||||
impl ColumnTrait for Column {
|
||||
type EntityName = Entity;
|
||||
fn def(&self) -> ColumnType {
|
||||
match self {
|
||||
Self::Id => ColumnType::Integer(Some(11u32)),
|
||||
Self::Name => ColumnType::String(Some(255u32)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
Self::CakeFilling => Entity::has_many(super::cake_filling::Entity)
|
||||
.from(Column::Id)
|
||||
.to(super::cake_filling::Column::FillingId)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::cake_filling::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::CakeFilling.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn find_cake_filling(&self) -> Select<super::cake_filling::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
78
examples/codegen/src/out/fruit.rs
Normal file
78
examples/codegen/src/out/fruit.rs
Normal file
@ -0,0 +1,78 @@
|
||||
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
||||
pub struct Entity;
|
||||
|
||||
impl EntityName for Entity {
|
||||
fn table_name(&self) -> &str {
|
||||
"fruit"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
|
||||
pub struct Model {
|
||||
pub id: i32,
|
||||
pub name: Option<String>,
|
||||
pub cake_id: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
pub enum Column {
|
||||
Id,
|
||||
Name,
|
||||
CakeId,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
|
||||
pub enum PrimaryKey {
|
||||
Id,
|
||||
}
|
||||
|
||||
impl PrimaryKeyTrait for PrimaryKey {
|
||||
fn auto_increment() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {
|
||||
Cake,
|
||||
}
|
||||
|
||||
impl ColumnTrait for Column {
|
||||
type EntityName = Entity;
|
||||
fn def(&self) -> ColumnType {
|
||||
match self {
|
||||
Self::Id => ColumnType::Integer(Some(11u32)),
|
||||
Self::Name => ColumnType::String(Some(255u32)),
|
||||
Self::CakeId => ColumnType::Integer(Some(11u32)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
Self::Cake => Entity::has_one(super::cake::Entity)
|
||||
.from(Column::CakeId)
|
||||
.to(super::cake::Column::Id)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::cake::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Cake.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn find_cake(&self) -> Select<super::cake::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
63
examples/codegen/src/out/fruit_copy.rs
Normal file
63
examples/codegen/src/out/fruit_copy.rs
Normal file
@ -0,0 +1,63 @@
|
||||
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
||||
pub struct Entity;
|
||||
|
||||
impl EntityName for Entity {
|
||||
fn table_name(&self) -> &str {
|
||||
"fruit_copy"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
|
||||
pub struct Model {
|
||||
pub id: i32,
|
||||
pub name: Option<String>,
|
||||
pub cake_id: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
pub enum Column {
|
||||
Id,
|
||||
Name,
|
||||
CakeId,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
|
||||
pub enum PrimaryKey {
|
||||
Id,
|
||||
}
|
||||
|
||||
impl PrimaryKeyTrait for PrimaryKey {
|
||||
fn auto_increment() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ColumnTrait for Column {
|
||||
type EntityName = Entity;
|
||||
fn def(&self) -> ColumnType {
|
||||
match self {
|
||||
Self::Id => ColumnType::Integer(Some(11u32)),
|
||||
Self::Name => ColumnType::String(Some(255u32)),
|
||||
Self::CakeId => ColumnType::Integer(Some(11u32)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
_ => panic!("No RelationDef"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
7
examples/codegen/src/out/mod.rs
Normal file
7
examples/codegen/src/out/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||
|
||||
pub mod cake;
|
||||
pub mod cake_filling;
|
||||
pub mod filling;
|
||||
pub mod fruit;
|
||||
pub mod fruit_copy;
|
7
examples/codegen/src/out/prelude.rs
Normal file
7
examples/codegen/src/out/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::fruit_copy::Entity as FruitCopy;
|
21
sea-orm-cli/Cargo.toml
Normal file
21
sea-orm-cli/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "sea-orm-cli"
|
||||
version = "0.1.0"
|
||||
authors = [ "Billy Chan <ccw.billy.123@gmail.com>" ]
|
||||
edition = "2018"
|
||||
description = ""
|
||||
license = "MIT OR Apache-2.0"
|
||||
documentation = "https://docs.rs/sea-orm"
|
||||
repository = "https://github.com/SeaQL/sea-orm"
|
||||
categories = [ "database" ]
|
||||
keywords = [ "orm", "database", "sql", "mysql", "postgres", "sqlite", "cli" ]
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "sea-orm"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "^2.33.3" }
|
||||
async-std = { version = "^1.9", features = [ "attributes" ] }
|
||||
sea-orm-codegen = { path = "../sea-orm-codegen" }
|
13
sea-orm-cli/README.md
Normal file
13
sea-orm-cli/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# SeaORM CLI
|
||||
|
||||
Getting Help:
|
||||
|
||||
```sh
|
||||
cargo run -- -h
|
||||
```
|
||||
|
||||
Running Entity Generator:
|
||||
|
||||
```sh
|
||||
cargo run -- entity generate -u mysql://sea:sea@localhost/bakery -s bakery -o out
|
||||
```
|
41
sea-orm-cli/src/cli.rs
Normal file
41
sea-orm-cli/src/cli.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use clap::{App, AppSettings, Arg, SubCommand};
|
||||
|
||||
pub fn build_cli() -> App<'static, 'static> {
|
||||
let entity_subcommand = SubCommand::with_name("entity")
|
||||
.about("Entity related commands")
|
||||
.setting(AppSettings::VersionlessSubcommands)
|
||||
.subcommand(
|
||||
SubCommand::with_name("generate")
|
||||
.about("Generate entity")
|
||||
.arg(
|
||||
Arg::with_name("DATABASE_URI")
|
||||
.long("uri")
|
||||
.short("u")
|
||||
.help("Database URI")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("DATABASE_SCHEMA")
|
||||
.long("schema")
|
||||
.short("s")
|
||||
.help("Database schema")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("OUTPUT_DIR")
|
||||
.long("output_dir")
|
||||
.short("o")
|
||||
.help("Entity file output directory")
|
||||
.takes_value(true)
|
||||
.default_value("./"),
|
||||
),
|
||||
)
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp);
|
||||
|
||||
App::new("sea-orm")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.setting(AppSettings::VersionlessSubcommands)
|
||||
.subcommand(entity_subcommand)
|
||||
}
|
42
sea-orm-cli/src/main.rs
Normal file
42
sea-orm-cli/src/main.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use clap::ArgMatches;
|
||||
use sea_orm_codegen::EntityGenerator;
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
||||
mod cli;
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
let matches = cli::build_cli().get_matches();
|
||||
|
||||
match matches.subcommand() {
|
||||
("entity", Some(matches)) => run_entity_command(matches)
|
||||
.await
|
||||
.unwrap_or_else(handle_error),
|
||||
_ => unreachable!("You should never see this message"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_entity_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Error>> {
|
||||
match matches.subcommand() {
|
||||
("generate", Some(args)) => {
|
||||
let uri = args.value_of("DATABASE_URI").unwrap();
|
||||
let schema = args.value_of("DATABASE_SCHEMA").unwrap();
|
||||
let output_dir = args.value_of("OUTPUT_DIR").unwrap();
|
||||
EntityGenerator::discover(uri, schema)
|
||||
.await?
|
||||
.transform()?
|
||||
.generate(output_dir)?;
|
||||
}
|
||||
_ => unreachable!("You should never see this message"),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_error<E>(error: E)
|
||||
where
|
||||
E: Display,
|
||||
{
|
||||
eprintln!("{}", error);
|
||||
::std::process::exit(1);
|
||||
}
|
26
sea-orm-codegen/Cargo.toml
Normal file
26
sea-orm-codegen/Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "sea-orm-codegen"
|
||||
version = "0.1.0"
|
||||
authors = [ "Billy Chan <ccw.billy.123@gmail.com>" ]
|
||||
edition = "2018"
|
||||
description = ""
|
||||
license = "MIT OR Apache-2.0"
|
||||
documentation = "https://docs.rs/sea-orm"
|
||||
repository = "https://github.com/SeaQL/sea-orm"
|
||||
categories = [ "database" ]
|
||||
keywords = [ "orm", "database", "sql", "mysql", "postgres", "sqlite" ]
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "sea_orm_codegen"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
sea-orm = { path = "../", features = [ "sqlx-mysql", "runtime-async-std-native-tls", "debug-print", "with-json", "macros" ], default-features = false }
|
||||
sea-schema = { path = "../../sea-schema", default-features = false, features = [ "sqlx-mysql", "runtime-async-std-native-tls", "discovery", "writer" ] }
|
||||
sea-query = { path = "../../sea-query", version = "^0.12" }
|
||||
sqlx = { version = "^0.5", features = [ "mysql", "runtime-async-std-native-tls" ] }
|
||||
syn = { version = "1", default-features = false, features = [ "derive", "parsing", "proc-macro", "printing" ] }
|
||||
quote = "1"
|
||||
heck = "0.3"
|
||||
proc-macro2 = "1"
|
1
sea-orm-codegen/README.md
Normal file
1
sea-orm-codegen/README.md
Normal file
@ -0,0 +1 @@
|
||||
# SeaORM Codegen
|
155
sea-orm-codegen/src/entity/column.rs
Normal file
155
sea-orm-codegen/src/entity/column.rs
Normal file
@ -0,0 +1,155 @@
|
||||
use heck::{CamelCase, SnakeCase};
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use sea_query::{ColumnDef, ColumnSpec, ColumnType};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Column {
|
||||
pub(crate) name: String,
|
||||
pub(crate) col_type: ColumnType,
|
||||
pub(crate) auto_increment: bool,
|
||||
pub(crate) not_null: bool,
|
||||
}
|
||||
|
||||
impl Column {
|
||||
pub fn get_name_snake_case(&self) -> Ident {
|
||||
format_ident!("{}", self.name.to_snake_case())
|
||||
}
|
||||
|
||||
pub fn get_name_camel_case(&self) -> Ident {
|
||||
format_ident!("{}", self.name.to_camel_case())
|
||||
}
|
||||
|
||||
pub fn get_rs_type(&self) -> TokenStream {
|
||||
let ident = match self.col_type {
|
||||
ColumnType::Char(_)
|
||||
| ColumnType::String(_)
|
||||
| ColumnType::Text
|
||||
| ColumnType::DateTime(_)
|
||||
| ColumnType::Timestamp(_)
|
||||
| ColumnType::Time(_)
|
||||
| ColumnType::Date
|
||||
| ColumnType::Json
|
||||
| ColumnType::JsonBinary
|
||||
| ColumnType::Custom(_) => format_ident!("String"),
|
||||
ColumnType::TinyInteger(_) => format_ident!("i8"),
|
||||
ColumnType::SmallInteger(_) => format_ident!("i16"),
|
||||
ColumnType::Integer(_) => format_ident!("i32"),
|
||||
ColumnType::BigInteger(_) => format_ident!("i64"),
|
||||
ColumnType::Float(_)
|
||||
| ColumnType::Decimal(_)
|
||||
| ColumnType::Money(_) => format_ident!("f32"),
|
||||
ColumnType::Double(_) => format_ident!("f64"),
|
||||
ColumnType::Binary(_) => format_ident!("Vec<u8>"),
|
||||
ColumnType::Boolean => format_ident!("bool"),
|
||||
};
|
||||
match self.not_null {
|
||||
true => quote! { #ident },
|
||||
false => quote! { Option<#ident> },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> TokenStream {
|
||||
match &self.col_type {
|
||||
ColumnType::Char(s) => match s {
|
||||
Some(s) => quote! { ColumnType::Char(Some(#s)) },
|
||||
None => quote! { ColumnType::Char(None) },
|
||||
},
|
||||
ColumnType::String(s) => match s {
|
||||
Some(s) => quote! { ColumnType::String(Some(#s)) },
|
||||
None => quote! { ColumnType::String(None) },
|
||||
},
|
||||
ColumnType::Text => quote! { ColumnType::Text },
|
||||
ColumnType::TinyInteger(s) => match s {
|
||||
Some(s) => quote! { ColumnType::TinyInteger(Some(#s)) },
|
||||
None => quote! { ColumnType::TinyInteger(None) },
|
||||
},
|
||||
ColumnType::SmallInteger(s) => match s {
|
||||
Some(s) => quote! { ColumnType::SmallInteger(Some(#s)) },
|
||||
None => quote! { ColumnType::SmallInteger(None) },
|
||||
},
|
||||
ColumnType::Integer(s) => match s {
|
||||
Some(s) => quote! { ColumnType::Integer(Some(#s)) },
|
||||
None => quote! { ColumnType::Integer(None) },
|
||||
},
|
||||
ColumnType::BigInteger(s) => match s {
|
||||
Some(s) => quote! { ColumnType::BigInteger(Some(#s)) },
|
||||
None => quote! { ColumnType::BigInteger(None) },
|
||||
},
|
||||
ColumnType::Float(s) => match s {
|
||||
Some(s) => quote! { ColumnType::Float(Some(#s)) },
|
||||
None => quote! { ColumnType::Float(None) },
|
||||
},
|
||||
ColumnType::Double(s) => match s {
|
||||
Some(s) => quote! { ColumnType::Double(Some(#s)) },
|
||||
None => quote! { ColumnType::Double(None) },
|
||||
},
|
||||
ColumnType::Decimal(s) => match s {
|
||||
Some((s1, s2)) => quote! { ColumnType::Decimal(Some((#s1, #s2))) },
|
||||
None => quote! { ColumnType::Decimal(None) },
|
||||
},
|
||||
ColumnType::DateTime(s) => match s {
|
||||
Some(s) => quote! { ColumnType::DateTime(Some(#s)) },
|
||||
None => quote! { ColumnType::DateTime(None) },
|
||||
},
|
||||
ColumnType::Timestamp(s) => match s {
|
||||
Some(s) => quote! { ColumnType::Timestamp(Some(#s)) },
|
||||
None => quote! { ColumnType::Timestamp(None) },
|
||||
},
|
||||
ColumnType::Time(s) => match s {
|
||||
Some(s) => quote! { ColumnType::Time(Some(#s)) },
|
||||
None => quote! { ColumnType::Time(None) },
|
||||
},
|
||||
ColumnType::Date => quote! { ColumnType::Date },
|
||||
ColumnType::Binary(s) => match s {
|
||||
Some(s) => quote! { ColumnType::Binary(Some(#s)) },
|
||||
None => quote! { ColumnType::Binary(None) },
|
||||
},
|
||||
ColumnType::Boolean => quote! { ColumnType::Boolean },
|
||||
ColumnType::Money(s) => match s {
|
||||
Some((s1, s2)) => quote! { ColumnType::Money(Some((#s1, #s2))) },
|
||||
None => quote! { ColumnType::Money(None) },
|
||||
},
|
||||
ColumnType::Json => quote! { ColumnType::Json },
|
||||
ColumnType::JsonBinary => quote! { ColumnType::JsonBinary },
|
||||
ColumnType::Custom(s) => {
|
||||
let s = s.to_string();
|
||||
quote! { ColumnType::Custom(sea_query::SeaRc::new(sea_query::Alias::new(#s))) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ColumnDef> for Column {
|
||||
fn from(col_def: &ColumnDef) -> Self {
|
||||
let name = col_def.get_column_name();
|
||||
let col_type = match col_def.get_column_type() {
|
||||
Some(ty) => ty.clone(),
|
||||
None => panic!("ColumnType should not be empty"),
|
||||
};
|
||||
let auto_increments: Vec<bool> = col_def
|
||||
.get_column_spec()
|
||||
.iter()
|
||||
.filter_map(|spec| match spec {
|
||||
ColumnSpec::AutoIncrement => Some(true),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let auto_increment = !auto_increments.is_empty();
|
||||
let not_nulls: Vec<bool> = col_def
|
||||
.get_column_spec()
|
||||
.iter()
|
||||
.filter_map(|spec| match spec {
|
||||
ColumnSpec::NotNull => Some(true),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let not_null = !not_nulls.is_empty();
|
||||
Self {
|
||||
name,
|
||||
col_type,
|
||||
auto_increment,
|
||||
not_null,
|
||||
}
|
||||
}
|
||||
}
|
121
sea-orm-codegen/src/entity/entity.rs
Normal file
121
sea-orm-codegen/src/entity/entity.rs
Normal file
@ -0,0 +1,121 @@
|
||||
use crate::{Column, PrimaryKey, Relation};
|
||||
use heck::{CamelCase, SnakeCase};
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::format_ident;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Entity {
|
||||
pub(crate) table_name: String,
|
||||
pub(crate) columns: Vec<Column>,
|
||||
pub(crate) relations: Vec<Relation>,
|
||||
pub(crate) primary_keys: Vec<PrimaryKey>,
|
||||
}
|
||||
|
||||
impl Entity {
|
||||
pub fn get_table_name_snake_case(&self) -> String {
|
||||
self.table_name.to_snake_case()
|
||||
}
|
||||
|
||||
pub fn get_table_name_camel_case(&self) -> String {
|
||||
self.table_name.to_camel_case()
|
||||
}
|
||||
|
||||
pub fn get_table_name_snake_case_ident(&self) -> Ident {
|
||||
format_ident!("{}", self.get_table_name_snake_case())
|
||||
}
|
||||
|
||||
pub fn get_table_name_camel_case_ident(&self) -> Ident {
|
||||
format_ident!("{}", self.get_table_name_camel_case())
|
||||
}
|
||||
|
||||
pub fn get_column_names_snake_case(&self) -> Vec<Ident> {
|
||||
self.columns
|
||||
.iter()
|
||||
.map(|col| col.get_name_snake_case())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_column_names_camel_case(&self) -> Vec<Ident> {
|
||||
self.columns
|
||||
.iter()
|
||||
.map(|col| col.get_name_camel_case())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_column_rs_types(&self) -> Vec<TokenStream> {
|
||||
self.columns
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|col| col.get_rs_type())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_column_types(&self) -> Vec<TokenStream> {
|
||||
self.columns
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|col| col.get_type())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_primary_key_names_snake_case(&self) -> Vec<Ident> {
|
||||
self.primary_keys
|
||||
.iter()
|
||||
.map(|pk| pk.get_name_snake_case())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_primary_key_names_camel_case(&self) -> Vec<Ident> {
|
||||
self.primary_keys
|
||||
.iter()
|
||||
.map(|pk| pk.get_name_camel_case())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_relation_ref_tables_snake_case(&self) -> Vec<Ident> {
|
||||
self.relations
|
||||
.iter()
|
||||
.map(|rel| rel.get_ref_table_snake_case())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_relation_ref_tables_camel_case(&self) -> Vec<Ident> {
|
||||
self.relations
|
||||
.iter()
|
||||
.map(|rel| rel.get_ref_table_camel_case())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_relation_rel_types(&self) -> Vec<Ident> {
|
||||
self.relations
|
||||
.iter()
|
||||
.map(|rel| rel.get_rel_type())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_relation_columns_camel_case(&self) -> Vec<Ident> {
|
||||
self.relations
|
||||
.iter()
|
||||
.map(|rel| rel.get_column_camel_case())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_relation_ref_columns_camel_case(&self) -> Vec<Ident> {
|
||||
self.relations
|
||||
.iter()
|
||||
.map(|rel| rel.get_ref_column_camel_case())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_relation_rel_find_helpers(&self) -> Vec<Ident> {
|
||||
self.relations
|
||||
.iter()
|
||||
.map(|rel| rel.get_rel_find_helper())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_primary_key_auto_increment(&self) -> Ident {
|
||||
let auto_increment = self.columns.iter().any(|col| col.auto_increment);
|
||||
format_ident!("{}", auto_increment)
|
||||
}
|
||||
}
|
15
sea-orm-codegen/src/entity/generator.rs
Normal file
15
sea-orm-codegen/src/entity/generator.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use crate::{EntityTransformer, Error};
|
||||
use sea_schema::mysql::discovery::SchemaDiscovery;
|
||||
use sqlx::MySqlPool;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EntityGenerator {}
|
||||
|
||||
impl EntityGenerator {
|
||||
pub async fn discover(uri: &str, schema: &str) -> Result<EntityTransformer, Error> {
|
||||
let connection = MySqlPool::connect(uri).await?;
|
||||
let schema_discovery = SchemaDiscovery::new(connection, schema);
|
||||
let schema = schema_discovery.discover().await;
|
||||
Ok(EntityTransformer { schema })
|
||||
}
|
||||
}
|
15
sea-orm-codegen/src/entity/mod.rs
Normal file
15
sea-orm-codegen/src/entity/mod.rs
Normal file
@ -0,0 +1,15 @@
|
||||
mod column;
|
||||
mod entity;
|
||||
mod generator;
|
||||
mod primary_key;
|
||||
mod relation;
|
||||
mod transformer;
|
||||
mod writer;
|
||||
|
||||
pub use column::*;
|
||||
pub use entity::*;
|
||||
pub use generator::*;
|
||||
pub use primary_key::*;
|
||||
pub use relation::*;
|
||||
pub use transformer::*;
|
||||
pub use writer::*;
|
18
sea-orm-codegen/src/entity/primary_key.rs
Normal file
18
sea-orm-codegen/src/entity/primary_key.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use heck::{CamelCase, SnakeCase};
|
||||
use proc_macro2::Ident;
|
||||
use quote::format_ident;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PrimaryKey {
|
||||
pub(crate) name: String,
|
||||
}
|
||||
|
||||
impl PrimaryKey {
|
||||
pub fn get_name_snake_case(&self) -> Ident {
|
||||
format_ident!("{}", self.name.to_snake_case())
|
||||
}
|
||||
|
||||
pub fn get_name_camel_case(&self) -> Ident {
|
||||
format_ident!("{}", self.name.to_camel_case())
|
||||
}
|
||||
}
|
60
sea-orm-codegen/src/entity/relation.rs
Normal file
60
sea-orm-codegen/src/entity/relation.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use heck::{CamelCase, SnakeCase};
|
||||
use proc_macro2::Ident;
|
||||
use quote::format_ident;
|
||||
use sea_orm::RelationType;
|
||||
use sea_query::TableForeignKey;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Relation {
|
||||
pub(crate) ref_table: String,
|
||||
pub(crate) columns: Vec<String>,
|
||||
pub(crate) ref_columns: Vec<String>,
|
||||
pub(crate) rel_type: RelationType,
|
||||
}
|
||||
|
||||
impl Relation {
|
||||
pub fn get_ref_table_snake_case(&self) -> Ident {
|
||||
format_ident!("{}", self.ref_table.to_snake_case())
|
||||
}
|
||||
|
||||
pub fn get_ref_table_camel_case(&self) -> Ident {
|
||||
format_ident!("{}", self.ref_table.to_camel_case())
|
||||
}
|
||||
|
||||
pub fn get_rel_type(&self) -> Ident {
|
||||
match self.rel_type {
|
||||
RelationType::HasOne => format_ident!("has_one"),
|
||||
RelationType::HasMany => format_ident!("has_many"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_column_camel_case(&self) -> Ident {
|
||||
format_ident!("{}", self.columns[0].to_camel_case())
|
||||
}
|
||||
|
||||
pub fn get_ref_column_camel_case(&self) -> Ident {
|
||||
format_ident!("{}", self.ref_columns[0].to_camel_case())
|
||||
}
|
||||
|
||||
pub fn get_rel_find_helper(&self) -> Ident {
|
||||
format_ident!("find_{}", self.ref_table.to_snake_case())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TableForeignKey> for Relation {
|
||||
fn from(tbl_fk: &TableForeignKey) -> Self {
|
||||
let ref_table = match tbl_fk.get_ref_table() {
|
||||
Some(s) => s,
|
||||
None => panic!("RefTable should not be empty"),
|
||||
};
|
||||
let columns = tbl_fk.get_columns();
|
||||
let ref_columns = tbl_fk.get_ref_columns();
|
||||
let rel_type = RelationType::HasOne;
|
||||
Self {
|
||||
ref_table,
|
||||
columns,
|
||||
ref_columns,
|
||||
rel_type,
|
||||
}
|
||||
}
|
||||
}
|
86
sea-orm-codegen/src/entity/transformer.rs
Normal file
86
sea-orm-codegen/src/entity/transformer.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use crate::{Entity, EntityWriter, Error, PrimaryKey, Relation};
|
||||
use sea_orm::RelationType;
|
||||
use sea_query::TableStatement;
|
||||
use sea_schema::mysql::def::Schema;
|
||||
use std::{collections::HashMap, mem::swap};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EntityTransformer {
|
||||
pub(crate) schema: Schema,
|
||||
}
|
||||
|
||||
impl EntityTransformer {
|
||||
pub fn transform(self) -> Result<EntityWriter, Error> {
|
||||
let mut inverse_relations: HashMap<String, Vec<Relation>> = HashMap::new();
|
||||
let mut entities = Vec::new();
|
||||
for table_ref in self.schema.tables.iter() {
|
||||
let table_stmt = table_ref.write();
|
||||
let table_create = match table_stmt {
|
||||
TableStatement::Create(stmt) => stmt,
|
||||
_ => {
|
||||
return Err(Error::TransformError(
|
||||
"TableStatement should be create".into(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let table_name = match table_create.get_table_name() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
return Err(Error::TransformError(
|
||||
"Table name should not be empty".into(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let columns = table_create
|
||||
.get_columns()
|
||||
.iter()
|
||||
.map(|col_def| col_def.into())
|
||||
.collect();
|
||||
let relations = table_create
|
||||
.get_foreign_key_create_stmts()
|
||||
.iter()
|
||||
.map(|fk_create_stmt| fk_create_stmt.get_foreign_key())
|
||||
.map(|tbl_fk| tbl_fk.into());
|
||||
let primary_keys = table_create
|
||||
.get_indexes()
|
||||
.iter()
|
||||
.filter(|index| index.is_primary_key())
|
||||
.map(|index| {
|
||||
index
|
||||
.get_index_spec()
|
||||
.get_column_names()
|
||||
.into_iter()
|
||||
.map(|name| PrimaryKey { name })
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
let entity = Entity {
|
||||
table_name: table_name.clone(),
|
||||
columns,
|
||||
relations: relations.clone().collect(),
|
||||
primary_keys,
|
||||
};
|
||||
entities.push(entity);
|
||||
for mut rel in relations.into_iter() {
|
||||
let ref_table = rel.ref_table;
|
||||
swap(&mut rel.columns, &mut rel.ref_columns);
|
||||
rel.rel_type = RelationType::HasMany;
|
||||
rel.ref_table = table_name.clone();
|
||||
if let Some(vec) = inverse_relations.get_mut(&ref_table) {
|
||||
vec.push(rel);
|
||||
} else {
|
||||
inverse_relations.insert(ref_table, vec![rel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (tbl_name, relations) in inverse_relations.iter() {
|
||||
for ent in entities.iter_mut() {
|
||||
if ent.table_name.eq(tbl_name) {
|
||||
ent.relations.append(relations.clone().as_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(EntityWriter { entities })
|
||||
}
|
||||
}
|
297
sea-orm-codegen/src/entity/writer.rs
Normal file
297
sea-orm-codegen/src/entity/writer.rs
Normal file
@ -0,0 +1,297 @@
|
||||
use crate::{Entity, Error};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::{self, Write},
|
||||
path::Path,
|
||||
process::Command,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EntityWriter {
|
||||
pub(crate) entities: Vec<Entity>,
|
||||
}
|
||||
|
||||
impl EntityWriter {
|
||||
pub fn generate(self, output_dir: &str) -> Result<(), Error> {
|
||||
for entity in self.entities.iter() {
|
||||
let code_blocks = Self::gen_code_blocks(entity);
|
||||
Self::write(output_dir, entity, code_blocks)?;
|
||||
}
|
||||
for entity in self.entities.iter() {
|
||||
Self::format_entity(output_dir, entity)?;
|
||||
}
|
||||
self.write_mod(output_dir)?;
|
||||
self.write_prelude(output_dir)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_mod(&self, output_dir: &str) -> io::Result<()> {
|
||||
let file_name = "mod.rs";
|
||||
let dir = Self::create_dir(output_dir)?;
|
||||
let file_path = dir.join(file_name);
|
||||
let mut file = fs::File::create(file_path)?;
|
||||
Self::write_doc_comment(&mut file)?;
|
||||
for entity in self.entities.iter() {
|
||||
let code_block = Self::gen_mod(entity);
|
||||
file.write_all(code_block.to_string().as_bytes())?;
|
||||
}
|
||||
Self::format_file(output_dir, file_name)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_prelude(&self, output_dir: &str) -> io::Result<()> {
|
||||
let file_name = "prelude.rs";
|
||||
let dir = Self::create_dir(output_dir)?;
|
||||
let file_path = dir.join(file_name);
|
||||
let mut file = fs::File::create(file_path)?;
|
||||
Self::write_doc_comment(&mut file)?;
|
||||
for entity in self.entities.iter() {
|
||||
let code_block = Self::gen_prelude_use(entity);
|
||||
file.write_all(code_block.to_string().as_bytes())?;
|
||||
}
|
||||
Self::format_file(output_dir, file_name)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write(
|
||||
output_dir: &str,
|
||||
entity: &Entity,
|
||||
code_blocks: Vec<TokenStream>,
|
||||
) -> io::Result<()> {
|
||||
let dir = Self::create_dir(output_dir)?;
|
||||
let file_path = dir.join(format!("{}.rs", entity.table_name));
|
||||
let mut file = fs::File::create(file_path)?;
|
||||
Self::write_doc_comment(&mut file)?;
|
||||
for code_block in code_blocks {
|
||||
file.write_all(code_block.to_string().as_bytes())?;
|
||||
file.write_all(b"\n\n")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_doc_comment(file: &mut File) -> io::Result<()> {
|
||||
let ver = env!("CARGO_PKG_VERSION");
|
||||
let comments = vec![format!(
|
||||
"//! SeaORM Entity. Generated by sea-orm-codegen {}",
|
||||
ver
|
||||
)];
|
||||
for comment in comments {
|
||||
file.write_all(comment.as_bytes())?;
|
||||
file.write_all(b"\n\n")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_dir(output_dir: &str) -> io::Result<&Path> {
|
||||
let dir = Path::new(output_dir);
|
||||
fs::create_dir_all(dir)?;
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
pub fn format_entity(output_dir: &str, entity: &Entity) -> io::Result<()> {
|
||||
Self::format_file(output_dir, &format!("{}.rs", entity.table_name))
|
||||
}
|
||||
|
||||
pub fn format_file(output_dir: &str, file_name: &str) -> io::Result<()> {
|
||||
Command::new("rustfmt")
|
||||
.arg(Path::new(output_dir).join(file_name))
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gen_code_blocks(entity: &Entity) -> Vec<TokenStream> {
|
||||
let mut code_blocks = vec![
|
||||
Self::gen_import(),
|
||||
Self::gen_entity_struct(),
|
||||
Self::gen_impl_entity_name(entity),
|
||||
Self::gen_model_struct(entity),
|
||||
Self::gen_column_enum(entity),
|
||||
Self::gen_primary_key_enum(entity),
|
||||
Self::gen_impl_primary_key(entity),
|
||||
Self::gen_relation_enum(entity),
|
||||
Self::gen_impl_column_trait(entity),
|
||||
Self::gen_impl_relation_trait(entity),
|
||||
];
|
||||
code_blocks.extend(Self::gen_impl_related(entity));
|
||||
code_blocks.extend(vec![
|
||||
Self::gen_impl_model(entity),
|
||||
Self::gen_impl_active_model_behavior(),
|
||||
]);
|
||||
code_blocks
|
||||
}
|
||||
|
||||
pub fn gen_import() -> TokenStream {
|
||||
quote! {
|
||||
use sea_orm::entity::prelude::*;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_entity_struct() -> TokenStream {
|
||||
quote! {
|
||||
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
||||
pub struct Entity;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_impl_entity_name(entity: &Entity) -> TokenStream {
|
||||
let table_name_snake_case = entity.get_table_name_snake_case();
|
||||
quote! {
|
||||
impl EntityName for Entity {
|
||||
fn table_name(&self) -> &str {
|
||||
#table_name_snake_case
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_model_struct(entity: &Entity) -> TokenStream {
|
||||
let column_names_snake_case = entity.get_column_names_snake_case();
|
||||
let column_rs_types = entity.get_column_rs_types();
|
||||
quote! {
|
||||
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
|
||||
pub struct Model {
|
||||
#(pub #column_names_snake_case: #column_rs_types),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_column_enum(entity: &Entity) -> TokenStream {
|
||||
let column_names_camel_case = entity.get_column_names_camel_case();
|
||||
quote! {
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
pub enum Column {
|
||||
#(#column_names_camel_case),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_primary_key_enum(entity: &Entity) -> TokenStream {
|
||||
let primary_key_names_camel_case = entity.get_primary_key_names_camel_case();
|
||||
quote! {
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
|
||||
pub enum PrimaryKey {
|
||||
#(#primary_key_names_camel_case),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_impl_primary_key(entity: &Entity) -> TokenStream {
|
||||
let primary_key_auto_increment = entity.get_primary_key_auto_increment();
|
||||
quote! {
|
||||
impl PrimaryKeyTrait for PrimaryKey {
|
||||
fn auto_increment() -> bool {
|
||||
#primary_key_auto_increment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_relation_enum(entity: &Entity) -> TokenStream {
|
||||
let relation_ref_tables_camel_case = entity.get_relation_ref_tables_camel_case();
|
||||
quote! {
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {
|
||||
#(#relation_ref_tables_camel_case),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_impl_column_trait(entity: &Entity) -> TokenStream {
|
||||
let column_names_camel_case = entity.get_column_names_camel_case();
|
||||
let column_types = entity.get_column_types();
|
||||
quote! {
|
||||
impl ColumnTrait for Column {
|
||||
type EntityName = Entity;
|
||||
|
||||
fn def(&self) -> ColumnType {
|
||||
match self {
|
||||
#(Self::#column_names_camel_case => #column_types),*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_impl_relation_trait(entity: &Entity) -> TokenStream {
|
||||
let relation_ref_tables_camel_case = entity.get_relation_ref_tables_camel_case();
|
||||
let relation_rel_types = entity.get_relation_rel_types();
|
||||
let relation_ref_tables_snake_case = entity.get_relation_ref_tables_snake_case();
|
||||
let relation_columns_camel_case = entity.get_relation_columns_camel_case();
|
||||
let relation_ref_columns_camel_case = entity.get_relation_ref_columns_camel_case();
|
||||
let quoted = if relation_ref_tables_camel_case.is_empty() {
|
||||
quote! {
|
||||
_ => panic!("No RelationDef"),
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#(Self::#relation_ref_tables_camel_case => Entity::#relation_rel_types(super::#relation_ref_tables_snake_case::Entity)
|
||||
.from(Column::#relation_columns_camel_case)
|
||||
.to(super::#relation_ref_tables_snake_case::Column::#relation_ref_columns_camel_case)
|
||||
.into()),*
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
#quoted
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_impl_related(entity: &Entity) -> Vec<TokenStream> {
|
||||
let camel = entity.get_relation_ref_tables_camel_case();
|
||||
let snake = entity.get_relation_ref_tables_snake_case();
|
||||
camel
|
||||
.iter()
|
||||
.zip(snake)
|
||||
.map(|(c, s)| {
|
||||
quote! {
|
||||
impl Related<super::#s::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::#c.def()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn gen_impl_model(entity: &Entity) -> TokenStream {
|
||||
let relation_ref_tables_snake_case = entity.get_relation_ref_tables_snake_case();
|
||||
let relation_rel_find_helpers = entity.get_relation_rel_find_helpers();
|
||||
quote! {
|
||||
impl Model {
|
||||
#(pub fn #relation_rel_find_helpers(&self) -> Select<super::#relation_ref_tables_snake_case::Entity> {
|
||||
Entity::find_related().belongs_to::<Entity>(self)
|
||||
})*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_impl_active_model_behavior() -> TokenStream {
|
||||
quote! {
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_mod(entity: &Entity) -> TokenStream {
|
||||
let table_name_snake_case_ident = entity.get_table_name_snake_case_ident();
|
||||
quote! {
|
||||
pub mod #table_name_snake_case_ident;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_prelude_use(entity: &Entity) -> TokenStream {
|
||||
let table_name_snake_case_ident = entity.get_table_name_snake_case_ident();
|
||||
let table_name_camel_case_ident = entity.get_table_name_camel_case_ident();
|
||||
quote! {
|
||||
pub use super::#table_name_snake_case_ident::Entity as #table_name_camel_case_ident;
|
||||
}
|
||||
}
|
||||
}
|
40
sea-orm-codegen/src/error.rs
Normal file
40
sea-orm-codegen/src/error.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use std::{error, fmt, io};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
StdIoError(io::Error),
|
||||
SqlxError(sqlx::Error),
|
||||
TransformError(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::StdIoError(e) => write!(f, "{:?}", e),
|
||||
Self::SqlxError(e) => write!(f, "{:?}", e),
|
||||
Self::TransformError(e) => write!(f, "{:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
match self {
|
||||
Self::StdIoError(e) => Some(e),
|
||||
Self::SqlxError(e) => Some(e),
|
||||
Self::TransformError(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(io_err: io::Error) -> Self {
|
||||
Self::StdIoError(io_err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sqlx::Error> for Error {
|
||||
fn from(sqlx_err: sqlx::Error) -> Self {
|
||||
Self::SqlxError(sqlx_err)
|
||||
}
|
||||
}
|
5
sea-orm-codegen/src/lib.rs
Normal file
5
sea-orm-codegen/src/lib.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod entity;
|
||||
mod error;
|
||||
|
||||
pub use entity::*;
|
||||
pub use error::*;
|
@ -3,7 +3,7 @@ use core::marker::PhantomData;
|
||||
use sea_query::{DynIden, IntoIden, JoinType};
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RelationType {
|
||||
HasOne,
|
||||
HasMany,
|
||||
|
Loading…
x
Reference in New Issue
Block a user