Codegen: Entity Generator (#23)
This commit is contained in:
parent
0b3ea685d5
commit
cab4b5a3f7
@ -2,7 +2,10 @@
|
|||||||
members = [
|
members = [
|
||||||
".",
|
".",
|
||||||
"sea-orm-macros",
|
"sea-orm-macros",
|
||||||
|
"sea-orm-codegen",
|
||||||
|
"sea-orm-cli",
|
||||||
"examples/sqlx-mysql",
|
"examples/sqlx-mysql",
|
||||||
|
"examples/codegen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
@ -28,7 +31,6 @@ futures = { version = "^0.3" }
|
|||||||
futures-util = { version = "^0.3" }
|
futures-util = { version = "^0.3" }
|
||||||
sea-query = { path = "../sea-query", version = "^0.12" }
|
sea-query = { path = "../sea-query", version = "^0.12" }
|
||||||
sea-orm-macros = { path = "sea-orm-macros", optional = true }
|
sea-orm-macros = { path = "sea-orm-macros", optional = true }
|
||||||
# sea-schema = { path = "../sea-schema" }
|
|
||||||
serde = { version = "^1.0", features = [ "derive" ] }
|
serde = { version = "^1.0", features = [ "derive" ] }
|
||||||
sqlx = { version = "^0.5", optional = true }
|
sqlx = { version = "^0.5", optional = true }
|
||||||
strum = { version = "^0.20", features = [ "derive" ] }
|
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 sea_query::{DynIden, IntoIden, JoinType};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum RelationType {
|
pub enum RelationType {
|
||||||
HasOne,
|
HasOne,
|
||||||
HasMany,
|
HasMany,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user