Merge branch 'master' into linked-api

This commit is contained in:
Billy Chan 2021-09-03 12:14:39 +08:00
commit 8cde6e46e4
No known key found for this signature in database
GPG Key ID: A2D690CAC7DF3CC7
16 changed files with 274 additions and 44 deletions

View File

@ -32,7 +32,7 @@ futures = { version = "^0.3" }
futures-util = { version = "^0.3" }
rust_decimal = { version = "^1", optional = true }
sea-orm-macros = { version = "^0.1.1", optional = true }
sea-query = { version = "^0.15", features = ["thread-safe"] }
sea-query = { version = "^0.16", features = ["thread-safe"] }
sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] }
serde = { version = "^1.0", features = ["derive"] }
sqlx = { version = "^0.5", optional = true }
@ -50,6 +50,7 @@ actix-rt = { version = "2.2.0" }
maplit = { version = "^1" }
rust_decimal_macros = { version = "^1" }
sea-orm = { path = ".", features = ["debug-print"] }
pretty_assertions = { version = "^0.7" }
[features]
debug-print = []

View File

@ -55,21 +55,21 @@ pub trait EntityTrait: EntityName {
where
R: EntityTrait,
{
RelationBuilder::new(RelationType::HasOne, Self::default(), related)
RelationBuilder::new(RelationType::HasOne, Self::default(), related, false)
}
fn has_one<R>(_: R) -> RelationBuilder<Self, R>
where
R: EntityTrait + Related<Self>,
{
RelationBuilder::from_rel(RelationType::HasOne, R::to().rev())
RelationBuilder::from_rel(RelationType::HasOne, R::to().rev(), true)
}
fn has_many<R>(_: R) -> RelationBuilder<Self, R>
where
R: EntityTrait + Related<Self>,
{
RelationBuilder::from_rel(RelationType::HasMany, R::to().rev())
RelationBuilder::from_rel(RelationType::HasMany, R::to().rev(), true)
}
/// Construct select statement to find one / all models

View File

@ -6,6 +6,7 @@ mod model;
pub mod prelude;
mod primary_key;
mod relation;
mod schema;
pub use active_model::*;
pub use base_entity::*;
@ -15,3 +16,4 @@ pub use model::*;
// pub use prelude::*;
pub use primary_key::*;
pub use relation::*;
pub use schema::*;

View File

@ -1,9 +1,9 @@
pub use crate::{
error::*, ActiveModelBehavior, ActiveModelTrait, ColumnDef, ColumnTrait, ColumnType,
DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveCustomColumn, DeriveEntity,
DeriveModel, DerivePrimaryKey, EntityName, EntityTrait, EnumIter, Iden, IdenStatic, Linked,
ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult, Related,
RelationDef, RelationTrait, Select, Value,
DeriveModel, DerivePrimaryKey, EntityName, EntityTrait, EnumIter, ForeignKeyAction, Iden,
IdenStatic, Linked, ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult,
Related, RelationDef, RelationTrait, Select, Value,
};
#[cfg(feature = "with-json")]

View File

@ -9,6 +9,8 @@ pub enum RelationType {
HasMany,
}
pub type ForeignKeyAction = sea_query::ForeignKeyAction;
pub trait RelationTrait: Iterable + Debug + 'static {
fn def(&self) -> RelationDef;
}
@ -50,6 +52,9 @@ pub struct RelationDef {
pub to_tbl: TableRef,
pub from_col: Identity,
pub to_col: Identity,
pub is_owner: bool,
pub on_delete: Option<ForeignKeyAction>,
pub on_update: Option<ForeignKeyAction>,
}
pub struct RelationBuilder<E, R>
@ -63,6 +68,9 @@ where
to_tbl: TableRef,
from_col: Option<Identity>,
to_col: Option<Identity>,
is_owner: bool,
on_delete: Option<ForeignKeyAction>,
on_update: Option<ForeignKeyAction>,
}
impl RelationDef {
@ -74,6 +82,9 @@ impl RelationDef {
to_tbl: self.from_tbl,
from_col: self.to_col,
to_col: self.from_col,
is_owner: !self.is_owner,
on_delete: self.on_delete,
on_update: self.on_update,
}
}
}
@ -83,7 +94,7 @@ where
E: EntityTrait,
R: EntityTrait,
{
pub(crate) fn new(rel_type: RelationType, from: E, to: R) -> Self {
pub(crate) fn new(rel_type: RelationType, from: E, to: R, is_owner: bool) -> Self {
Self {
entities: PhantomData,
rel_type,
@ -91,10 +102,13 @@ where
to_tbl: to.table_ref(),
from_col: None,
to_col: None,
is_owner,
on_delete: None,
on_update: None,
}
}
pub(crate) fn from_rel(rel_type: RelationType, rel: RelationDef) -> Self {
pub(crate) fn from_rel(rel_type: RelationType, rel: RelationDef, is_owner: bool) -> Self {
Self {
entities: PhantomData,
rel_type,
@ -102,6 +116,9 @@ where
to_tbl: rel.to_tbl,
from_col: Some(rel.from_col),
to_col: Some(rel.to_col),
is_owner,
on_delete: None,
on_update: None,
}
}
@ -120,6 +137,16 @@ where
self.to_col = Some(identifier.identity_of());
self
}
pub fn on_delete(mut self, action: ForeignKeyAction) -> Self {
self.on_delete = Some(action);
self
}
pub fn on_update(mut self, action: ForeignKeyAction) -> Self {
self.on_update = Some(action);
self
}
}
impl<E, R> From<RelationBuilder<E, R>> for RelationDef
@ -134,6 +161,9 @@ where
to_tbl: b.to_tbl,
from_col: b.from_col.unwrap(),
to_col: b.to_col.unwrap(),
is_owner: b.is_owner,
on_delete: b.on_delete,
on_update: b.on_update,
}
}
}

161
src/entity/schema.rs Normal file
View File

@ -0,0 +1,161 @@
use crate::{
unpack_table_ref, ColumnTrait, EntityTrait, Identity, Iterable, PrimaryKeyToColumn,
PrimaryKeyTrait, RelationTrait,
};
use sea_query::{ColumnDef, ForeignKeyCreateStatement, Iden, Index, TableCreateStatement};
pub fn entity_to_table_create_statement<E>(entity: E) -> TableCreateStatement
where
E: EntityTrait,
{
let mut stmt = TableCreateStatement::new();
for column in E::Column::iter() {
let orm_column_def = column.def();
let types = orm_column_def.col_type.into();
let mut column_def = ColumnDef::new_with_type(column, types);
if !orm_column_def.null {
column_def.not_null();
}
if orm_column_def.unique {
column_def.unique_key();
}
for primary_key in E::PrimaryKey::iter() {
if column.to_string() == primary_key.into_column().to_string()
&& E::PrimaryKey::auto_increment()
{
column_def.auto_increment();
if E::PrimaryKey::iter().count() == 1 {
column_def.primary_key();
}
}
}
if orm_column_def.indexed {
stmt.index(
Index::create()
.name(&format!(
"idx-{}-{}",
entity.to_string(),
column.to_string()
))
.table(entity)
.col(column),
);
}
stmt.col(&mut column_def);
}
if E::PrimaryKey::iter().count() > 1 {
let mut idx_pk = Index::create();
for primary_key in E::PrimaryKey::iter() {
idx_pk.col(primary_key);
}
stmt.primary_key(idx_pk.name(&format!("pk-{}", entity.to_string())).primary());
}
for relation in E::Relation::iter() {
let relation = relation.def();
if relation.is_owner {
continue;
}
let mut foreign_key_stmt = ForeignKeyCreateStatement::new();
let from_tbl = unpack_table_ref(&relation.from_tbl);
let to_tbl = unpack_table_ref(&relation.to_tbl);
match relation.from_col {
Identity::Unary(o1) => {
foreign_key_stmt.from_col(o1);
}
Identity::Binary(o1, o2) => {
foreign_key_stmt.from_col(o1);
foreign_key_stmt.from_col(o2);
}
Identity::Ternary(o1, o2, o3) => {
foreign_key_stmt.from_col(o1);
foreign_key_stmt.from_col(o2);
foreign_key_stmt.from_col(o3);
}
}
match relation.to_col {
Identity::Unary(o1) => {
foreign_key_stmt.to_col(o1);
}
Identity::Binary(o1, o2) => {
foreign_key_stmt.to_col(o1);
foreign_key_stmt.to_col(o2);
}
crate::Identity::Ternary(o1, o2, o3) => {
foreign_key_stmt.to_col(o1);
foreign_key_stmt.to_col(o2);
foreign_key_stmt.to_col(o3);
}
}
if let Some(action) = relation.on_delete {
foreign_key_stmt.on_delete(action);
}
if let Some(action) = relation.on_update {
foreign_key_stmt.on_update(action);
}
stmt.foreign_key(
foreign_key_stmt
.name(&format!(
"fk-{}-{}",
from_tbl.to_string(),
to_tbl.to_string()
))
.from_tbl(from_tbl)
.to_tbl(to_tbl),
);
}
stmt.table(entity).if_not_exists().take()
}
#[cfg(test)]
mod tests {
use crate::{entity_to_table_create_statement, tests_cfg::*};
use pretty_assertions::assert_eq;
use sea_query::*;
#[test]
fn test_entity_to_table_create_statement() {
assert_eq!(
entity_to_table_create_statement(CakeFillingPrice).to_string(MysqlQueryBuilder),
Table::create()
.table(CakeFillingPrice)
.if_not_exists()
.col(
ColumnDef::new(cake_filling_price::Column::CakeId)
.integer()
.not_null()
)
.col(
ColumnDef::new(cake_filling_price::Column::FillingId)
.integer()
.not_null()
)
.col(
ColumnDef::new(cake_filling_price::Column::Price)
.decimal()
.not_null()
)
.primary_key(
Index::create()
.name("pk-cake_filling_price")
.col(cake_filling_price::Column::CakeId)
.col(cake_filling_price::Column::FillingId)
.primary()
)
.foreign_key(
ForeignKeyCreateStatement::new()
.name("fk-cake_filling_price-cake_filling")
.from_tbl(CakeFillingPrice)
.from_col(cake_filling_price::Column::CakeId)
.from_col(cake_filling_price::Column::FillingId)
.to_tbl(CakeFilling)
.to_col(cake_filling::Column::CakeId)
.to_col(cake_filling::Column::FillingId)
)
.to_string(MysqlQueryBuilder)
);
}
}

View File

@ -264,9 +264,12 @@ impl TryGetable for Decimal {
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))?;
use rust_decimal::prelude::FromPrimitive;
match val {
Some(v) => Decimal::from_f64(v)
.ok_or_else(|| TryGetError::DbErr(DbErr::Query("Failed to convert f64 into Decimal".to_owned()))),
None => Err(TryGetError::Null)
Some(v) => Decimal::from_f64(v).ok_or_else(|| {
TryGetError::DbErr(DbErr::Query(
"Failed to convert f64 into Decimal".to_owned(),
))
}),
None => Err(TryGetError::Null),
}
}
#[cfg(feature = "mock")]

View File

@ -294,7 +294,7 @@ fn join_condition(rel: RelationDef) -> SimpleExpr {
}
}
fn unpack_table_ref(table_ref: &TableRef) -> DynIden {
pub(crate) fn unpack_table_ref(table_ref: &TableRef) -> DynIden {
match table_ref {
TableRef::Table(tbl) => SeaRc::clone(tbl),
TableRef::SchemaTable(_, tbl) => SeaRc::clone(tbl),

View File

@ -49,7 +49,7 @@ impl ColumnTrait for Column {
Self::Id => ColumnType::Integer.def(),
Self::Name => ColumnType::String(None).def(),
Self::ContactDetails => ColumnType::Json.def(),
Self::BakeryId => ColumnType::Integer.def(),
Self::BakeryId => ColumnType::Integer.def().null(),
}
}
}
@ -60,6 +60,8 @@ impl RelationTrait for Relation {
Self::Bakery => Entity::belongs_to(super::bakery::Entity)
.from(Column::BakeryId)
.to(super::bakery::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade)
.into(),
}
}

View File

@ -48,7 +48,7 @@ impl ColumnTrait for Column {
match self {
Self::Id => ColumnType::Integer.def(),
Self::Name => ColumnType::String(None).def(),
Self::ProfitMargin => ColumnType::Float.def(),
Self::ProfitMargin => ColumnType::Double.def(),
}
}
}

View File

@ -54,9 +54,9 @@ impl ColumnTrait for Column {
Self::Id => ColumnType::Integer.def(),
Self::Name => ColumnType::String(None).def(),
Self::Price => ColumnType::Decimal(Some((19, 4))).def(),
Self::BakeryId => ColumnType::Integer.def(),
Self::BakeryId => ColumnType::Integer.def().null(),
Self::GlutenFree => ColumnType::Boolean.def(),
Self::Serial => ColumnType::String(None).def(),
Self::Serial => ColumnType::Uuid.def(),
}
}
}
@ -67,6 +67,8 @@ impl RelationTrait for Relation {
Self::Bakery => Entity::belongs_to(super::bakery::Entity)
.from(Column::BakeryId)
.to(super::bakery::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade)
.into(),
Self::Lineitem => Entity::has_many(super::lineitem::Entity).into(),
}

View File

@ -56,10 +56,14 @@ impl RelationTrait for Relation {
Self::Cake => Entity::belongs_to(super::cake::Entity)
.from(Column::CakeId)
.to(super::cake::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade)
.into(),
Self::Baker => Entity::belongs_to(super::baker::Entity)
.from(Column::BakerId)
.to(super::baker::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade)
.into(),
}
}

View File

@ -46,7 +46,7 @@ impl ColumnTrait for Column {
match self {
Self::Id => ColumnType::Integer.def(),
Self::Name => ColumnType::String(None).def(),
Self::Notes => ColumnType::Text.def(),
Self::Notes => ColumnType::Text.def().null(),
}
}
}

View File

@ -64,10 +64,14 @@ impl RelationTrait for Relation {
Self::Order => Entity::belongs_to(super::order::Entity)
.from(Column::OrderId)
.to(super::order::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade)
.into(),
Self::Cake => Entity::belongs_to(super::cake::Entity)
.from(Column::CakeId)
.to(super::cake::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade)
.into(),
}
}

View File

@ -65,10 +65,14 @@ impl RelationTrait for Relation {
Self::Bakery => Entity::belongs_to(super::bakery::Entity)
.from(Column::BakeryId)
.to(super::bakery::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade)
.into(),
Self::Customer => Entity::belongs_to(super::customer::Entity)
.from(Column::CustomerId)
.to(super::customer::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade)
.into(),
Self::Lineitem => Entity::has_many(super::lineitem::Entity).into(),
}

View File

@ -1,15 +1,30 @@
use sea_orm::{error::*, sea_query, DbConn, ExecResult};
use sea_query::{ColumnDef, ForeignKey, ForeignKeyAction, Index, TableCreateStatement};
use pretty_assertions::assert_eq;
use sea_orm::{
entity_to_table_create_statement, error::*, sea_query, DbConn, EntityTrait, ExecResult,
};
use sea_query::{ColumnDef, ForeignKey, ForeignKeyAction, Index, Table, TableCreateStatement};
pub use super::super::bakery_chain::*;
async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result<ExecResult, DbErr> {
async fn create_table<E>(
db: &DbConn,
stmt: &TableCreateStatement,
entity: E,
) -> Result<ExecResult, DbErr>
where
E: EntityTrait,
{
let builder = db.get_database_backend();
db.execute(builder.build(stmt)).await
let stmt = builder.build(stmt);
assert_eq!(
builder.build(&entity_to_table_create_statement(entity)),
stmt
);
db.execute(stmt).await
}
pub async fn create_bakery_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create()
let stmt = Table::create()
.table(bakery::Entity)
.if_not_exists()
.col(
@ -27,16 +42,17 @@ pub async fn create_bakery_table(db: &DbConn) -> Result<ExecResult, DbErr> {
)
.to_owned();
create_table(db, &stmt).await
create_table(db, &stmt, Bakery).await
}
pub async fn create_baker_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create()
let stmt = Table::create()
.table(baker::Entity)
.if_not_exists()
.col(
ColumnDef::new(baker::Column::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
@ -49,7 +65,7 @@ pub async fn create_baker_table(db: &DbConn) -> Result<ExecResult, DbErr> {
.col(ColumnDef::new(baker::Column::BakeryId).integer())
.foreign_key(
ForeignKey::create()
.name("FK_baker_bakery")
.name("fk-baker-bakery")
.from(baker::Entity, baker::Column::BakeryId)
.to(bakery::Entity, bakery::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
@ -57,11 +73,11 @@ pub async fn create_baker_table(db: &DbConn) -> Result<ExecResult, DbErr> {
)
.to_owned();
create_table(db, &stmt).await
create_table(db, &stmt, Baker).await
}
pub async fn create_customer_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create()
let stmt = Table::create()
.table(customer::Entity)
.if_not_exists()
.col(
@ -75,11 +91,11 @@ pub async fn create_customer_table(db: &DbConn) -> Result<ExecResult, DbErr> {
.col(ColumnDef::new(customer::Column::Notes).text())
.to_owned();
create_table(db, &stmt).await
create_table(db, &stmt, Customer).await
}
pub async fn create_order_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create()
let stmt = Table::create()
.table(order::Entity)
.if_not_exists()
.col(
@ -107,7 +123,7 @@ pub async fn create_order_table(db: &DbConn) -> Result<ExecResult, DbErr> {
)
.foreign_key(
ForeignKey::create()
.name("FK_order_bakery")
.name("fk-order-bakery")
.from(order::Entity, order::Column::BakeryId)
.to(bakery::Entity, bakery::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
@ -115,7 +131,7 @@ pub async fn create_order_table(db: &DbConn) -> Result<ExecResult, DbErr> {
)
.foreign_key(
ForeignKey::create()
.name("FK_order_customer")
.name("fk-order-customer")
.from(order::Entity, order::Column::CustomerId)
.to(customer::Entity, customer::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
@ -123,11 +139,11 @@ pub async fn create_order_table(db: &DbConn) -> Result<ExecResult, DbErr> {
)
.to_owned();
create_table(db, &stmt).await
create_table(db, &stmt, Order).await
}
pub async fn create_lineitem_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create()
let stmt = Table::create()
.table(lineitem::Entity)
.if_not_exists()
.col(
@ -159,7 +175,7 @@ pub async fn create_lineitem_table(db: &DbConn) -> Result<ExecResult, DbErr> {
)
.foreign_key(
ForeignKey::create()
.name("FK_lineitem_order")
.name("fk-lineitem-order")
.from(lineitem::Entity, lineitem::Column::OrderId)
.to(order::Entity, order::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
@ -167,7 +183,7 @@ pub async fn create_lineitem_table(db: &DbConn) -> Result<ExecResult, DbErr> {
)
.foreign_key(
ForeignKey::create()
.name("FK_lineitem_cake")
.name("fk-lineitem-cake")
.from(lineitem::Entity, lineitem::Column::CakeId)
.to(cake::Entity, cake::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
@ -175,11 +191,11 @@ pub async fn create_lineitem_table(db: &DbConn) -> Result<ExecResult, DbErr> {
)
.to_owned();
create_table(db, &stmt).await
create_table(db, &stmt, Lineitem).await
}
pub async fn create_cakes_bakers_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create()
let stmt = Table::create()
.table(cakes_bakers::Entity)
.if_not_exists()
.col(
@ -194,12 +210,13 @@ pub async fn create_cakes_bakers_table(db: &DbConn) -> Result<ExecResult, DbErr>
)
.primary_key(
Index::create()
.name("pk-cakes_bakers")
.col(cakes_bakers::Column::CakeId)
.col(cakes_bakers::Column::BakerId),
)
.foreign_key(
ForeignKey::create()
.name("FK_cakes_bakers_cake")
.name("fk-cakes_bakers-cake")
.from(cakes_bakers::Entity, cakes_bakers::Column::CakeId)
.to(cake::Entity, cake::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
@ -207,7 +224,7 @@ pub async fn create_cakes_bakers_table(db: &DbConn) -> Result<ExecResult, DbErr>
)
.foreign_key(
ForeignKey::create()
.name("FK_cakes_bakers_baker")
.name("fk-cakes_bakers-baker")
.from(cakes_bakers::Entity, cakes_bakers::Column::BakerId)
.to(baker::Entity, baker::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
@ -215,11 +232,11 @@ pub async fn create_cakes_bakers_table(db: &DbConn) -> Result<ExecResult, DbErr>
)
.to_owned();
create_table(db, &stmt).await
create_table(db, &stmt, CakesBakers).await
}
pub async fn create_cake_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create()
let stmt = Table::create()
.table(cake::Entity)
.if_not_exists()
.col(
@ -238,7 +255,7 @@ pub async fn create_cake_table(db: &DbConn) -> Result<ExecResult, DbErr> {
.col(ColumnDef::new(cake::Column::BakeryId).integer())
.foreign_key(
ForeignKey::create()
.name("FK_cake_bakery")
.name("fk-cake-bakery")
.from(cake::Entity, cake::Column::BakeryId)
.to(bakery::Entity, bakery::Column::Id)
.on_delete(ForeignKeyAction::Cascade)
@ -252,5 +269,5 @@ pub async fn create_cake_table(db: &DbConn) -> Result<ExecResult, DbErr> {
.col(ColumnDef::new(cake::Column::Serial).uuid().not_null())
.to_owned();
create_table(db, &stmt).await
create_table(db, &stmt, Cake).await
}