diff --git a/Cargo.toml b/Cargo.toml index 88bdfccc..f8388030 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ async-stream = { version = "^0.3" } chrono = { version = "^0", optional = true } futures = { version = "^0.3" } futures-util = { version = "^0.3" } +rust_decimal = { version = "^1", optional = true } sea-query = { version = "^0.12" } sea-orm-macros = { path = "sea-orm-macros", optional = true } serde = { version = "^1.0", features = [ "derive" ] } @@ -45,6 +46,7 @@ serde_json = { version = "^1", optional = true } [dev-dependencies] async-std = { version = "^1.9", features = [ "attributes" ] } maplit = { version = "^1" } +rust_decimal_macros = { version = "^1" } sea-orm = { path = ".", features = ["sqlx-sqlite", "sqlx-json", "sqlx-chrono", "sqlx-decimal", "runtime-async-std-native-tls"] } [features] @@ -54,7 +56,7 @@ macros = [ "sea-orm-macros" ] mock = [] with-json = [ "serde_json", "sea-query/with-json" ] with-chrono = [ "chrono", "sea-query/with-chrono" ] -with-rust_decimal = [ "sea-query/with-rust_decimal" ] +with-rust_decimal = [ "rust_decimal", "sea-query/with-rust_decimal" ] sqlx-dep = [ "sqlx" ] sqlx-json = [ "sqlx/json", "with-json" ] sqlx-chrono = [ "sqlx/chrono", "with-chrono" ] diff --git a/sea-orm-codegen/src/entity/writer.rs b/sea-orm-codegen/src/entity/writer.rs index 3bd8fb59..136d86e1 100644 --- a/sea-orm-codegen/src/entity/writer.rs +++ b/sea-orm-codegen/src/entity/writer.rs @@ -116,9 +116,7 @@ impl EntityWriter { Self::gen_impl_relation_trait(entity), ]; code_blocks.extend(Self::gen_impl_related(entity)); - code_blocks.extend(vec![ - Self::gen_impl_active_model_behavior(), - ]); + code_blocks.extend(vec![Self::gen_impl_active_model_behavior()]); code_blocks } diff --git a/sea-orm-macros/src/derives/active_model.rs b/sea-orm-macros/src/derives/active_model.rs index 76e80a02..4d18a169 100644 --- a/sea-orm-macros/src/derives/active_model.rs +++ b/sea-orm-macros/src/derives/active_model.rs @@ -71,7 +71,11 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result::Column) -> sea_orm::ActiveValue { match c { - #(::Column::#name => std::mem::take(&mut self.#field).into_wrapped_value(),)* + #(::Column::#name => { + let mut value = sea_orm::ActiveValue::unset(); + std::mem::swap(&mut value, &mut self.#field); + value.into_wrapped_value() + },)* _ => sea_orm::ActiveValue::unset(), } } diff --git a/src/database/mock.rs b/src/database/mock.rs index 7b208a72..ab002acb 100644 --- a/src/database/mock.rs +++ b/src/database/mock.rs @@ -79,11 +79,7 @@ impl MockDatabaseTrait for MockDatabase { } } - fn query( - &mut self, - counter: usize, - statement: Statement, - ) -> Result, DbErr> { + fn query(&mut self, counter: usize, statement: Statement) -> Result, DbErr> { self.transaction_log.push(Transaction::one(statement)); if counter < self.query_results.len() { Ok(std::mem::take(&mut self.query_results[counter]) diff --git a/src/database/mod.rs b/src/database/mod.rs index 4ad1b347..a5182a34 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -29,6 +29,9 @@ impl Database { if crate::MockDatabaseConnector::accepts(string) { return crate::MockDatabaseConnector::connect(string).await; } - Err(DbErr::Conn(format!("The connection string '{}' has no supporting driver.", string))) + Err(DbErr::Conn(format!( + "The connection string '{}' has no supporting driver.", + string + ))) } } diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs index ebea0a32..48da88a0 100644 --- a/src/driver/sqlx_mysql.rs +++ b/src/driver/sqlx_mysql.rs @@ -49,7 +49,9 @@ impl SqlxMySqlPoolConnection { Err(err) => Err(sqlx_error_to_exec_err(err)), } } else { - Err(DbErr::Exec("Failed to acquire connection from pool.".to_owned())) + Err(DbErr::Exec( + "Failed to acquire connection from pool.".to_owned(), + )) } } @@ -66,7 +68,9 @@ impl SqlxMySqlPoolConnection { }, } } else { - Err(DbErr::Query("Failed to acquire connection from pool.".to_owned())) + Err(DbErr::Query( + "Failed to acquire connection from pool.".to_owned(), + )) } } @@ -80,7 +84,9 @@ impl SqlxMySqlPoolConnection { Err(err) => Err(sqlx_error_to_query_err(err)), } } else { - Err(DbErr::Query("Failed to acquire connection from pool.".to_owned())) + Err(DbErr::Query( + "Failed to acquire connection from pool.".to_owned(), + )) } } } diff --git a/src/driver/sqlx_sqlite.rs b/src/driver/sqlx_sqlite.rs index ecd0ba4f..ac275b72 100644 --- a/src/driver/sqlx_sqlite.rs +++ b/src/driver/sqlx_sqlite.rs @@ -49,7 +49,9 @@ impl SqlxSqlitePoolConnection { Err(err) => Err(sqlx_error_to_exec_err(err)), } } else { - Err(DbErr::Exec("Failed to acquire connection from pool.".to_owned())) + Err(DbErr::Exec( + "Failed to acquire connection from pool.".to_owned(), + )) } } @@ -66,7 +68,9 @@ impl SqlxSqlitePoolConnection { }, } } else { - Err(DbErr::Query("Failed to acquire connection from pool.".to_owned())) + Err(DbErr::Query( + "Failed to acquire connection from pool.".to_owned(), + )) } } @@ -80,7 +84,9 @@ impl SqlxSqlitePoolConnection { Err(err) => Err(sqlx_error_to_query_err(err)), } } else { - Err(DbErr::Query("Failed to acquire connection from pool.".to_owned())) + Err(DbErr::Query( + "Failed to acquire connection from pool.".to_owned(), + )) } } } diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index 8eb26504..54bf037b 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -227,7 +227,11 @@ where let model: Option = found?; match model { Some(model) => Ok(model.into_active_model()), - None => Err(DbErr::Exec(format!("Failed to find inserted item: {} {}", E::default().to_string(), res.last_insert_id))), + None => Err(DbErr::Exec(format!( + "Failed to find inserted item: {} {}", + E::default().to_string(), + res.last_insert_id + ))), } } else { Ok(A::default()) diff --git a/src/entity/model.rs b/src/entity/model.rs index 73dd66ae..15ebdb58 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -1,4 +1,4 @@ -use crate::{EntityTrait, DbErr, QueryFilter, QueryResult, Related, Select}; +use crate::{DbErr, EntityTrait, QueryFilter, QueryResult, Related, Select}; pub use sea_query::Value; use std::fmt::Debug; diff --git a/src/executor/delete.rs b/src/executor/delete.rs index eebd0fc9..bbdc3bb5 100644 --- a/src/executor/delete.rs +++ b/src/executor/delete.rs @@ -62,10 +62,7 @@ async fn exec_delete_only( } // Only Statement impl Send -async fn exec_delete( - statement: Statement, - db: &DatabaseConnection, -) -> Result { +async fn exec_delete(statement: Statement, db: &DatabaseConnection) -> Result { let result = db.execute(statement).await?; Ok(DeleteResult { rows_affected: result.rows_affected(), diff --git a/src/executor/insert.rs b/src/executor/insert.rs index cf36d5ff..784d95a0 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -40,10 +40,7 @@ impl Inserter { } // Only Statement impl Send -async fn exec_insert( - statement: Statement, - db: &DatabaseConnection, -) -> Result { +async fn exec_insert(statement: Statement, db: &DatabaseConnection) -> Result { let result = db.execute(statement).await?; // TODO: Postgres instead use query_one + returning clause Ok(InsertResult { diff --git a/src/executor/query.rs b/src/executor/query.rs index 1d23a013..75a73463 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -1,4 +1,5 @@ use crate::DbErr; +use chrono::NaiveDateTime; use std::fmt; #[derive(Debug)] @@ -56,12 +57,14 @@ macro_rules! try_getable_all { #[cfg(feature = "sqlx-mysql")] QueryResultRow::SqlxMySql(row) => { use sqlx::Row; - row.try_get(column.as_str()).map_err(crate::sqlx_error_to_query_err) + row.try_get(column.as_str()) + .map_err(crate::sqlx_error_to_query_err) } #[cfg(feature = "sqlx-sqlite")] QueryResultRow::SqlxSqlite(row) => { use sqlx::Row; - row.try_get(column.as_str()).map_err(crate::sqlx_error_to_query_err) + row.try_get(column.as_str()) + .map_err(crate::sqlx_error_to_query_err) } #[cfg(feature = "mock")] QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?), @@ -109,7 +112,8 @@ macro_rules! try_getable_mysql { #[cfg(feature = "sqlx-mysql")] QueryResultRow::SqlxMySql(row) => { use sqlx::Row; - row.try_get(column.as_str()).map_err(crate::sqlx_error_to_query_err) + row.try_get(column.as_str()) + .map_err(crate::sqlx_error_to_query_err) } #[cfg(feature = "sqlx-sqlite")] QueryResultRow::SqlxSqlite(_) => { @@ -160,3 +164,64 @@ try_getable_mysql!(u64); try_getable_all!(f32); try_getable_all!(f64); try_getable_all!(String); +try_getable_all!(NaiveDateTime); + +#[cfg(feature = "with-rust_decimal")] +use rust_decimal::Decimal; + +#[cfg(feature = "with-rust_decimal")] +impl TryGetable for Decimal { + fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { + let column = format!("{}{}", pre, col); + match &res.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => { + use sqlx::Row; + row.try_get(column.as_str()) + .map_err(crate::sqlx_error_to_query_err) + } + #[cfg(feature = "sqlx-sqlite")] + QueryResultRow::SqlxSqlite(row) => { + use sqlx::Row; + let val: f64 = row + .try_get(column.as_str()) + .map_err(crate::sqlx_error_to_query_err)?; + use rust_decimal::prelude::FromPrimitive; + Decimal::from_f64(val) + .ok_or_else(|| DbErr::Query("Failed to convert f64 into Decimal".to_owned())) + } + #[cfg(feature = "mock")] + QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?), + } + } +} + +#[cfg(feature = "with-rust_decimal")] +impl TryGetable for Option { + fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { + let column = format!("{}{}", pre, col); + match &res.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => { + use sqlx::Row; + match row.try_get(column.as_str()) { + Ok(v) => Ok(Some(v)), + Err(_) => Ok(None), + } + } + #[cfg(feature = "sqlx-sqlite")] + QueryResultRow::SqlxSqlite(_) => { + let result: Result = TryGetable::try_get(res, pre, col); + match result { + Ok(v) => Ok(Some(v)), + Err(_) => Ok(None), + } + } + #[cfg(feature = "mock")] + QueryResultRow::Mock(row) => match row.try_get(column.as_str()) { + Ok(v) => Ok(Some(v)), + Err(_) => Ok(None), + }, + } + } +} diff --git a/src/executor/update.rs b/src/executor/update.rs index 0a486afc..0f33c914 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -18,10 +18,7 @@ impl<'a, A: 'a> UpdateOne where A: ActiveModelTrait, { - pub fn exec( - self, - db: &'a DatabaseConnection, - ) -> impl Future> + 'a { + pub fn exec(self, db: &'a DatabaseConnection) -> impl Future> + 'a { // so that self is dropped before entering await exec_update_and_return_original(self.query, self.model, db) } @@ -74,10 +71,7 @@ where } // Only Statement impl Send -async fn exec_update( - statement: Statement, - db: &DatabaseConnection, -) -> Result { +async fn exec_update(statement: Statement, db: &DatabaseConnection) -> Result { let result = db.execute(statement).await?; Ok(UpdateResult { rows_affected: result.rows_affected(), diff --git a/src/lib.rs b/src/lib.rs index 3e49958b..e80fb521 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -173,18 +173,18 @@ //! # } //! ``` //! ## License -//! +//! //! Licensed under either of -//! +//! //! - Apache License, Version 2.0 //! ([LICENSE-APACHE](LICENSE-APACHE) or ) //! - MIT license //! ([LICENSE-MIT](LICENSE-MIT) or ) -//! +//! //! at your option. -//! +//! //! ## Contribution -//! +//! //! Unless you explicitly state otherwise, any contribution intentionally submitted //! for inclusion in the work by you, as defined in the Apache-2.0 license, shall be //! dual licensed as above, without any additional terms or conditions. @@ -215,5 +215,5 @@ pub use sea_orm_macros::{ }; pub use sea_query; pub use sea_query::Iden; -pub use strum::EnumIter; pub use strum; +pub use strum::EnumIter; diff --git a/src/query/json.rs b/src/query/json.rs index aa3d9c6c..98cf49b9 100644 --- a/src/query/json.rs +++ b/src/query/json.rs @@ -1,4 +1,4 @@ -use crate::{FromQueryResult, DbErr, QueryResult, QueryResultRow}; +use crate::{DbErr, FromQueryResult, QueryResult, QueryResultRow}; use serde_json::Map; pub use serde_json::Value as JsonValue; diff --git a/tests/bakery_chain/baker.rs b/tests/bakery_chain/baker.rs new file mode 100644 index 00000000..b359e7ef --- /dev/null +++ b/tests/bakery_chain/baker.rs @@ -0,0 +1,81 @@ +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "baker" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub name: String, + pub bakery_id: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, + BakeryId, +} + +#[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 { + Bakery, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::String(None).def(), + Self::BakeryId => ColumnType::Integer.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Bakery => Entity::belongs_to(super::bakery::Entity) + .from(Column::BakeryId) + .to(super::bakery::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Bakery.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::cakes_bakers::Relation::Cake.def() + } + + fn via() -> Option { + Some(super::cakes_bakers::Relation::Baker.def().rev()) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/tests/bakery_chain/bakery.rs b/tests/bakery_chain/bakery.rs new file mode 100644 index 00000000..61803329 --- /dev/null +++ b/tests/bakery_chain/bakery.rs @@ -0,0 +1,84 @@ +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "bakery" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub name: String, + pub profit_margin: f64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, + ProfitMargin, +} + +#[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 { + Baker, + Order, + Cake, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::String(None).def(), + Self::ProfitMargin => ColumnType::Float.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Baker => Entity::has_many(super::baker::Entity).into(), + Self::Order => Entity::has_many(super::order::Entity).into(), + Self::Cake => Entity::has_many(super::cake::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Baker.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Order.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Cake.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/tests/bakery_chain/bakery_chain_erd.drawio b/tests/bakery_chain/bakery_chain_erd.drawio new file mode 100644 index 00000000..10084d0a --- /dev/null +++ b/tests/bakery_chain/bakery_chain_erd.drawio @@ -0,0 +1 @@ +7Zxtc5s4EIB/jT82w5sBf4yd5Nq75K6TdKbtp44Csq0zIB/Itd1ff6sgjI1EYoe3uxnNZDLWWgihZ1fsLmtG9ize/Zai9fKBhjgaWUa4G9k3I8uaOC7854J9LjB9w84li5SEQlYKnsgvLISGkG5IiLOTjozSiJH1qTCgSYIDdiJDaUq3p93mNDo96xotsCR4ClAkS7+SkC1zqT82SvlHTBbL4symIb6JUdFZCLIlCun2RIR37I4mTEzxM05jlOCEwTcPKF3hdDS+XTLGr/R6ZN3B35z3vlpQuogwWpPsKqAxiIMMutzNUUwivs5HA03FQHA6+3Zkz1JKWf4p3s1wxFkVGPI53dV8e1iHlI97xgGr6S77vnKC36eJP/m6/LBa7v/+IEb5iaKNWN/p9R+3j99BNvt4/elPsVJsXyw/gyUa2dMliyMQmPAxYyld4RmNaAqShCbQczonUVQRoYgsEmgGMF9YAHv6E6eMANhr8UVMwpCfZrpdEoaf1ijg59yCGoMspZskxPxSDD48LLtQTdsq2mKSxSXB6HhXu1bmgQBYCqYxZukeuogDLEcohLCSDxNXCLalztmekC2P9K3QQyR0aHEYu0QDHwSdS0iNZVQINGkvIcq2JI5QjuFoXfi6BUsShfdoTzd8uhlDwapoTZc0Jb+gPyrBorRYZcs96fHEjxRjpjiDPp+LtTUroge0O+l4jzJWzIZGEVpn5Flws6cxShckmVLGwIzyTpLaHLE3HWi3gNu2K7h9BW7TUeC2DLcr3q7Em/eHfcf4BCuxyPcQlW2ea5AZWBhJFvd4zi/GKSWP4vq4iMLCzqOXbXIJ9omTF1tkiKHng1atKRF75ngKf7BMM+NqPBrDnGbQNss2/PHuKZvRBKaJyAsfDCqxxVwtTuBa58J9xV5k5AKx5Z4H2O7Mnj2Jb4JinBN+Yilw0IAbAB4PDtiXAD/zDTvTWBtg9YfG6lsSVpqGGmszrKY5NFdzInENwFw11kZYnaGx+vJtdp3SOWE/cl8zv9/eRRQxTboJaW9o0pYilkUrhZus46NL46Oxd058ZCpoHzaA9nHL92EdH11gz3XIX4mPVIC7M2dbx0ddAlbFR/0CdtTxEdE+VyOwqgipX7ByplJ70k2pKgOkfrHKCckZYNXuVXP3qpp+HvsK92qsgG3ZnblXctyk3atLzNm92L1SAe7OmuXspHavWgSscq96BezK+ax1SgJB+AEQyU8ONeDzAavcrF4Be/IO/ax+IKyxno9V6Wf1yrUYWD83apWrKhPdr73KCauIABg4jOFY021EV5V97pWuL0dHi2jDcPJjnmJx051SGmGUaNJNHGdjaNKOvD9LRHG4wEXgi6Nnur0tBdJaHdXS4SS85jWS0Lx9jFGyL+JgSQqLmO6/HQ6DxndOFXCJ5k0R9+atvWjlU+Xzq8UiRBndpAF+jZTI88D8Fvg1pDkKGekxQkOB8CBMcYQY+Xk6ZRVYcY7PXLHL+NqxxldirkWE7RgV9civVhxYaog01lgea1wdK18QaSxgiPZH3YT51U7bs6ppgclJhSd8yIcsdfmwuA3UW75N9afexQ7HZXeET7xtpS2qct5U2jw9PZzS2q5RVTSvuqedq7SOPJZfHatGaVtTK/lxzl+8bEbSLZ0+rFHu2vSh6Vb2CbfICx/rqqXQ1e6qV335JqnThxd4Qbm5XJQ+VAHuzgmqeXinkw9NsKqShv1iVTy622SwnWl7bQRWlSzsFawn2yujLzdLnQ1uA7Aybdiv6coJCJ1eaouuKnnYK11f8TQnQgEOfyCW2/ANYvgLieXn8Rr0BaBVecReQbuy43xfmrGOlhpHS9Jv/WxVsYUysve6ipZcuXRZR0sXWLVbk2B8JVpSAe7O+5LvzYGqdEpDPR+qKlbqFaorJ0p1gUWLgFUxU7+A5ZTlPxuUMML2em9ui7IycOp3c5ZvvjTPTMPMbLN8/qAJv4+wKnjq15Dl5MesLqulHerGDrXy8YPSoe7sx2GunMfUDvUlJl3z8PM/41DLfHX1couAh3eu5YhJv2WhMdbBXWpf8aNOynD+PjXjC0ep+b6f7/DOtCv/rEQi2mb1EN4R9k0Q45+PiuCgVdbA8UYHJXDFNvVmNZFT8/aE3qqJ/IqTZk/eW0tUHcmpjtRxJVHxLoK+Vcy8QMUO1ZmHRu/Vmc65qunVuPdaNS9XzYF2v/+ZahavWnpbNb2BVbP63hPn3RWY1ZHGPddf+nKWq1vVfKeaDXxDL97u9vYNfeDyYKu613nvrWiXVbP6DtuW6tlNKVF0+sbiburZJ5cq/kuC//GFDk0aWECLWumdq5VezY9netswq+9Ntqu6dLZWVkdyztTKt/UGmuVbt/Pu5avS7dt/AQ== \ No newline at end of file diff --git a/tests/bakery_chain/bakery_chain_erd.jpg b/tests/bakery_chain/bakery_chain_erd.jpg new file mode 100644 index 00000000..f44da9aa Binary files /dev/null and b/tests/bakery_chain/bakery_chain_erd.jpg differ diff --git a/tests/bakery_chain/cake.rs b/tests/bakery_chain/cake.rs new file mode 100644 index 00000000..1e134de5 --- /dev/null +++ b/tests/bakery_chain/cake.rs @@ -0,0 +1,96 @@ +use rust_decimal::prelude::*; +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: String, + pub price: Decimal, + pub bakery_id: Option, + pub gluten_free: bool, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, + Price, + BakeryId, + GlutenFree, +} + +#[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 { + Bakery, + Lineitem, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + 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::GlutenFree => ColumnType::Boolean.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Bakery => Entity::belongs_to(super::bakery::Entity) + .from(Column::BakeryId) + .to(super::bakery::Column::Id) + .into(), + Self::Lineitem => Entity::has_many(super::lineitem::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Bakery.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::cakes_bakers::Relation::Baker.def() + } + + fn via() -> Option { + Some(super::cakes_bakers::Relation::Cake.def().rev()) + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Lineitem.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/tests/bakery_chain/cakes_bakers.rs b/tests/bakery_chain/cakes_bakers.rs new file mode 100644 index 00000000..8106bbdf --- /dev/null +++ b/tests/bakery_chain/cakes_bakers.rs @@ -0,0 +1,68 @@ +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "cakes_bakers" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub cake_id: i32, + pub baker_id: i32, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + CakeId, + BakerId, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + CakeId, + BakerId, +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + false + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Cake, + Baker, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Self::CakeId => ColumnType::Integer.def(), + Self::BakerId => ColumnType::Integer.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Cake => Entity::belongs_to(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + Self::Baker => Entity::belongs_to(super::baker::Entity) + .from(Column::BakerId) + .to(super::baker::Column::Id) + .into(), + } + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/tests/bakery_chain/customer.rs b/tests/bakery_chain/customer.rs new file mode 100644 index 00000000..a7464082 --- /dev/null +++ b/tests/bakery_chain/customer.rs @@ -0,0 +1,68 @@ +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "customer" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub name: String, + pub notes: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, + Notes, +} + +#[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 { + Order, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::String(None).def(), + Self::Notes => ColumnType::Text.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Order => Entity::has_many(super::order::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Order.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/tests/bakery_chain/lineitem.rs b/tests/bakery_chain/lineitem.rs new file mode 100644 index 00000000..94b15cac --- /dev/null +++ b/tests/bakery_chain/lineitem.rs @@ -0,0 +1,89 @@ +use rust_decimal::prelude::*; +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "lineitem" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub price: Decimal, + pub quantity: i32, + pub order_id: Option, + pub cake_id: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Price, + Quantity, + OrderId, + 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 { + Order, + Cake, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Price => ColumnType::Money(Some((19, 4))).def(), + Self::Quantity => ColumnType::Integer.def(), + Self::OrderId => ColumnType::Integer.def(), + Self::CakeId => ColumnType::Integer.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Order => Entity::belongs_to(super::order::Entity) + .from(Column::OrderId) + .to(super::order::Column::Id) + .into(), + Self::Cake => Entity::belongs_to(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Order.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Cake.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/tests/bakery_chain/mod.rs b/tests/bakery_chain/mod.rs new file mode 100644 index 00000000..89028aab --- /dev/null +++ b/tests/bakery_chain/mod.rs @@ -0,0 +1,15 @@ +pub mod baker; +pub mod bakery; +pub mod cake; +pub mod cakes_bakers; +pub mod customer; +pub mod lineitem; +pub mod order; + +pub use super::baker::Entity as Baker; +pub use super::bakery::Entity as Bakery; +pub use super::cake::Entity as Cake; +pub use super::cakes_bakers::Entity as CakesBakers; +pub use super::customer::Entity as Customer; +pub use super::lineitem::Entity as Lineitem; +pub use super::order::Entity as Order; diff --git a/tests/bakery_chain/order.rs b/tests/bakery_chain/order.rs new file mode 100644 index 00000000..41bfb03f --- /dev/null +++ b/tests/bakery_chain/order.rs @@ -0,0 +1,98 @@ +use chrono::NaiveDateTime; +use rust_decimal::prelude::*; +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "order" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub total: Decimal, + pub bakery_id: Option, + pub customer_id: Option, + pub placed_at: NaiveDateTime, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Total, + BakeryId, + CustomerId, + PlacedAt, +} + +#[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 { + Bakery, + Customer, + Lineitem, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Total => ColumnType::Decimal(Some((19, 4))).def(), + Self::BakeryId => ColumnType::Integer.def(), + Self::CustomerId => ColumnType::Integer.def(), + Self::PlacedAt => ColumnType::DateTime.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Bakery => Entity::belongs_to(super::bakery::Entity) + .from(Column::BakeryId) + .to(super::bakery::Column::Id) + .into(), + Self::Customer => Entity::belongs_to(super::customer::Entity) + .from(Column::CustomerId) + .to(super::customer::Column::Id) + .into(), + Self::Lineitem => Entity::has_many(super::lineitem::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Bakery.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Customer.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Lineitem.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/tests/bakery_chain_tests.rs b/tests/bakery_chain_tests.rs new file mode 100644 index 00000000..c58be9eb --- /dev/null +++ b/tests/bakery_chain_tests.rs @@ -0,0 +1,34 @@ +use sea_orm::DbConn; + +pub mod bakery_chain; +mod setup; +pub use bakery_chain::*; +mod crud; +mod schema; + +#[async_std::test] +// cargo test --test bakery_chain_tests -- --nocapture +async fn main() { + let db: DbConn = setup::setup().await; + setup_schema(&db).await; + create_entities(&db).await; +} + +async fn setup_schema(db: &DbConn) { + assert!(schema::create_bakery_table(db).await.is_ok()); + assert!(schema::create_baker_table(db).await.is_ok()); + assert!(schema::create_customer_table(db).await.is_ok()); + assert!(schema::create_order_table(db).await.is_ok()); + assert!(schema::create_lineitem_table(db).await.is_ok()); + assert!(schema::create_cake_table(db).await.is_ok()); + assert!(schema::create_cakes_bakers_table(db).await.is_ok()); +} + +async fn create_entities(db: &DbConn) { + crud::test_create_bakery(db).await; + crud::test_create_baker(db).await; + crud::test_create_customer(db).await; + crud::create_cake::test_create_cake(db).await; + crud::create_lineitem::test_create_lineitem(db).await; + crud::create_order::test_create_order(db).await; +} diff --git a/tests/crud/create_cake.rs b/tests/crud/create_cake.rs new file mode 100644 index 00000000..675b9c14 --- /dev/null +++ b/tests/crud/create_cake.rs @@ -0,0 +1,90 @@ +pub use super::*; +use rust_decimal_macros::dec; + +pub async fn test_create_cake(db: &DbConn) { + let seaside_bakery = bakery::ActiveModel { + name: Set("SeaSide Bakery".to_owned()), + profit_margin: Set(10.4), + ..Default::default() + }; + let bakery_insert_res: InsertResult = Bakery::insert(seaside_bakery) + .exec(db) + .await + .expect("could not insert bakery"); + + let baker_bob = baker::ActiveModel { + name: Set("Baker Bob".to_owned()), + bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), + ..Default::default() + }; + let baker_insert_res: InsertResult = Baker::insert(baker_bob) + .exec(db) + .await + .expect("could not insert baker"); + + let mud_cake = cake::ActiveModel { + name: Set("Mud Cake".to_owned()), + price: Set(dec!(10.25)), + gluten_free: Set(false), + bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), + ..Default::default() + }; + + let cake_insert_res: InsertResult = Cake::insert(mud_cake) + .exec(db) + .await + .expect("could not insert cake"); + + let cake: Option = Cake::find_by_id(cake_insert_res.last_insert_id) + .one(db) + .await + .expect("could not find cake"); + + let cake_baker = cakes_bakers::ActiveModel { + cake_id: Set(cake_insert_res.last_insert_id as i32), + baker_id: Set(baker_insert_res.last_insert_id as i32), + ..Default::default() + }; + let _cake_baker_res: InsertResult = CakesBakers::insert(cake_baker) + .exec(db) + .await + .expect("could not insert cake_baker"); + + assert!(cake.is_some()); + let cake_model = cake.unwrap(); + assert_eq!(cake_model.name, "Mud Cake"); + assert_eq!(cake_model.price, dec!(10.25)); + assert_eq!(cake_model.gluten_free, false); + assert_eq!( + cake_model + .find_related(Bakery) + .one(db) + .await + .expect("Bakery not found") + .unwrap() + .name, + "SeaSide Bakery" + ); + + let related_bakers: Vec = cake_model + .find_related(Baker) + .all(db) + .await + .expect("could not find related bakers"); + assert_eq!(related_bakers.len(), 1); + assert_eq!(related_bakers[0].name, "Baker Bob"); + + let baker: Option = Baker::find_by_id(baker_insert_res.last_insert_id) + .one(db) + .await + .expect("could not find baker"); + + let related_cakes: Vec = baker + .unwrap() + .find_related(Cake) + .all(db) + .await + .expect("could not find related cakes"); + assert_eq!(related_cakes.len(), 1); + assert_eq!(related_cakes[0].name, "Mud Cake") +} diff --git a/tests/crud/create_lineitem.rs b/tests/crud/create_lineitem.rs new file mode 100644 index 00000000..46411801 --- /dev/null +++ b/tests/crud/create_lineitem.rs @@ -0,0 +1,116 @@ +pub use super::*; +use chrono::offset::Utc; +use rust_decimal_macros::dec; + +pub async fn test_create_lineitem(db: &DbConn) { + // Bakery + let seaside_bakery = bakery::ActiveModel { + name: Set("SeaSide Bakery".to_owned()), + profit_margin: Set(10.4), + ..Default::default() + }; + let bakery_insert_res: InsertResult = Bakery::insert(seaside_bakery) + .exec(db) + .await + .expect("could not insert bakery"); + + // Baker + let baker_bob = baker::ActiveModel { + name: Set("Baker Bob".to_owned()), + bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), + ..Default::default() + }; + let baker_insert_res: InsertResult = Baker::insert(baker_bob) + .exec(db) + .await + .expect("could not insert baker"); + + // Cake + let mud_cake = cake::ActiveModel { + name: Set("Mud Cake".to_owned()), + price: Set(dec!(10.25)), + gluten_free: Set(false), + bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), + ..Default::default() + }; + + let cake_insert_res: InsertResult = Cake::insert(mud_cake) + .exec(db) + .await + .expect("could not insert cake"); + + // Cake_Baker + let cake_baker = cakes_bakers::ActiveModel { + cake_id: Set(cake_insert_res.last_insert_id as i32), + baker_id: Set(baker_insert_res.last_insert_id as i32), + ..Default::default() + }; + let _cake_baker_res: InsertResult = CakesBakers::insert(cake_baker) + .exec(db) + .await + .expect("could not insert cake_baker"); + + // Customer + let customer_kate = customer::ActiveModel { + name: Set("Kate".to_owned()), + notes: Set("Loves cheese cake".to_owned()), + ..Default::default() + }; + let customer_insert_res: InsertResult = Customer::insert(customer_kate) + .exec(db) + .await + .expect("could not insert customer"); + + // Order + let order_1 = order::ActiveModel { + bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), + customer_id: Set(Some(customer_insert_res.last_insert_id as i32)), + placed_at: Set(Utc::now().naive_utc()), + ..Default::default() + }; + let order_insert_res: InsertResult = Order::insert(order_1) + .exec(db) + .await + .expect("could not insert order"); + + // Lineitem + let lineitem_1 = lineitem::ActiveModel { + cake_id: Set(Some(cake_insert_res.last_insert_id as i32)), + order_id: Set(Some(order_insert_res.last_insert_id as i32)), + price: Set(dec!(7.55)), + ..Default::default() + }; + let lineitem_insert_res: InsertResult = Lineitem::insert(lineitem_1) + .exec(db) + .await + .expect("could not insert lineitem"); + + let lineitem: Option = + Lineitem::find_by_id(lineitem_insert_res.last_insert_id) + .one(db) + .await + .expect("could not find lineitem"); + + assert!(lineitem.is_some()); + let lineitem_model = lineitem.unwrap(); + assert_eq!(lineitem_model.price, dec!(7.55)); + + let cake: Option = Cake::find_by_id(lineitem_model.cake_id) + .one(db) + .await + .expect("could not find cake"); + + let cake_model = cake.unwrap(); + assert_eq!(cake_model.name, "Mud Cake"); + + let order: Option = Order::find_by_id(lineitem_model.order_id) + .one(db) + .await + .expect("could not find order"); + + let order_model = order.unwrap(); + assert_eq!( + order_model.customer_id.unwrap(), + customer_insert_res.last_insert_id as i32 + ); +} diff --git a/tests/crud/create_order.rs b/tests/crud/create_order.rs new file mode 100644 index 00000000..5b0cd42d --- /dev/null +++ b/tests/crud/create_order.rs @@ -0,0 +1,123 @@ +pub use super::*; +use chrono::offset::Utc; +use rust_decimal_macros::dec; + +pub async fn test_create_order(db: &DbConn) { + // Bakery + let seaside_bakery = bakery::ActiveModel { + name: Set("SeaSide Bakery".to_owned()), + profit_margin: Set(10.4), + ..Default::default() + }; + let bakery_insert_res: InsertResult = Bakery::insert(seaside_bakery) + .exec(db) + .await + .expect("could not insert bakery"); + + // Baker + let baker_bob = baker::ActiveModel { + name: Set("Baker Bob".to_owned()), + bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), + ..Default::default() + }; + let baker_insert_res: InsertResult = Baker::insert(baker_bob) + .exec(db) + .await + .expect("could not insert baker"); + + // Cake + let mud_cake = cake::ActiveModel { + name: Set("Mud Cake".to_owned()), + price: Set(dec!(10.25)), + gluten_free: Set(false), + bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), + ..Default::default() + }; + + let cake_insert_res: InsertResult = Cake::insert(mud_cake) + .exec(db) + .await + .expect("could not insert cake"); + + // Cake_Baker + let cake_baker = cakes_bakers::ActiveModel { + cake_id: Set(cake_insert_res.last_insert_id as i32), + baker_id: Set(baker_insert_res.last_insert_id as i32), + ..Default::default() + }; + let _cake_baker_res: InsertResult = CakesBakers::insert(cake_baker) + .exec(db) + .await + .expect("could not insert cake_baker"); + + // Customer + let customer_kate = customer::ActiveModel { + name: Set("Kate".to_owned()), + notes: Set("Loves cheese cake".to_owned()), + ..Default::default() + }; + let customer_insert_res: InsertResult = Customer::insert(customer_kate) + .exec(db) + .await + .expect("could not insert customer"); + + // Order + let order_1 = order::ActiveModel { + bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), + customer_id: Set(Some(customer_insert_res.last_insert_id as i32)), + total: Set(dec!(15.10)), + placed_at: Set(Utc::now().naive_utc()), + ..Default::default() + }; + let order_insert_res: InsertResult = Order::insert(order_1) + .exec(db) + .await + .expect("could not insert order"); + + // Lineitem + let lineitem_1 = lineitem::ActiveModel { + cake_id: Set(Some(cake_insert_res.last_insert_id as i32)), + order_id: Set(Some(order_insert_res.last_insert_id as i32)), + price: Set(dec!(7.55)), + quantity: Set(2), + ..Default::default() + }; + let _lineitem_insert_res: InsertResult = Lineitem::insert(lineitem_1) + .exec(db) + .await + .expect("could not insert lineitem"); + + let order: Option = Order::find_by_id(order_insert_res.last_insert_id) + .one(db) + .await + .expect("could not find order"); + + assert!(order.is_some()); + let order_model = order.unwrap(); + assert_eq!(order_model.total, dec!(15.10)); + + let customer: Option = Customer::find_by_id(order_model.customer_id) + .one(db) + .await + .expect("could not find customer"); + + let customer_model = customer.unwrap(); + assert_eq!(customer_model.name, "Kate"); + + let bakery: Option = Bakery::find_by_id(order_model.bakery_id) + .one(db) + .await + .expect("could not find bakery"); + + let bakery_model = bakery.unwrap(); + assert_eq!(bakery_model.name, "SeaSide Bakery"); + + let related_lineitems: Vec = order_model + .find_related(Lineitem) + .all(db) + .await + .expect("could not find related lineitems"); + assert_eq!(related_lineitems.len(), 1); + assert_eq!(related_lineitems[0].price, dec!(7.55)); + assert_eq!(related_lineitems[0].quantity, 2); +} diff --git a/tests/crud/mod.rs b/tests/crud/mod.rs new file mode 100644 index 00000000..9fbf27e6 --- /dev/null +++ b/tests/crud/mod.rs @@ -0,0 +1,106 @@ +use sea_orm::{entity::*, DbConn, InsertResult}; + +pub use super::bakery_chain::*; + +pub mod create_cake; +pub mod create_lineitem; +pub mod create_order; + +pub async fn test_create_bakery(db: &DbConn) { + let seaside_bakery = bakery::ActiveModel { + name: Set("SeaSide Bakery".to_owned()), + profit_margin: Set(10.4), + ..Default::default() + }; + let res: InsertResult = Bakery::insert(seaside_bakery) + .exec(db) + .await + .expect("could not insert bakery"); + + let bakery: Option = Bakery::find_by_id(res.last_insert_id) + .one(db) + .await + .expect("could not find bakery"); + + assert!(bakery.is_some()); + let bakery_model = bakery.unwrap(); + assert_eq!(bakery_model.name, "SeaSide Bakery"); + assert_eq!(bakery_model.profit_margin, 10.4); +} + +pub async fn test_create_baker(db: &DbConn) { + let seaside_bakery = bakery::ActiveModel { + name: Set("SeaSide Bakery".to_owned()), + profit_margin: Set(10.4), + ..Default::default() + }; + let bakery_insert_res: InsertResult = Bakery::insert(seaside_bakery) + .exec(db) + .await + .expect("could not insert bakery"); + + let baker_bob = baker::ActiveModel { + name: Set("Baker Bob".to_owned()), + bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), + ..Default::default() + }; + let res: InsertResult = Baker::insert(baker_bob) + .exec(db) + .await + .expect("could not insert baker"); + + let baker: Option = Baker::find_by_id(res.last_insert_id) + .one(db) + .await + .expect("could not find baker"); + + assert!(baker.is_some()); + let baker_model = baker.unwrap(); + assert_eq!(baker_model.name, "Baker Bob"); + assert_eq!( + baker_model + .find_related(Bakery) + .one(db) + .await + .expect("Bakery not found") + .unwrap() + .name, + "SeaSide Bakery" + ); + + let bakery: Option = Bakery::find_by_id(bakery_insert_res.last_insert_id) + .one(db) + .await + .unwrap(); + + let related_bakers: Vec = bakery + .unwrap() + .find_related(Baker) + .all(db) + .await + .expect("could not find related bakers"); + assert_eq!(related_bakers.len(), 1); + assert_eq!(related_bakers[0].name, "Baker Bob") +} + +pub async fn test_create_customer(db: &DbConn) { + let customer_kate = customer::ActiveModel { + name: Set("Kate".to_owned()), + notes: Set("Loves cheese cake".to_owned()), + ..Default::default() + }; + let res: InsertResult = Customer::insert(customer_kate) + .exec(db) + .await + .expect("could not insert customer"); + + let customer: Option = Customer::find_by_id(res.last_insert_id) + .one(db) + .await + .expect("could not find customer"); + + assert!(customer.is_some()); + let customer_model = customer.unwrap(); + assert_eq!(customer_model.name, "Kate"); + assert_eq!(customer_model.notes, "Loves cheese cake"); +} diff --git a/tests/schema/mod.rs b/tests/schema/mod.rs new file mode 100644 index 00000000..3fe859eb --- /dev/null +++ b/tests/schema/mod.rs @@ -0,0 +1,211 @@ +use sea_orm::{error::*, sea_query, DbConn, ExecResult}; +use sea_query::{ColumnDef, ForeignKey, ForeignKeyAction, Index, TableCreateStatement}; + +pub use super::bakery_chain::*; + +async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result { + let builder = db.get_schema_builder_backend(); + db.execute(builder.build(stmt)).await +} + +pub async fn create_bakery_table(db: &DbConn) -> Result { + let stmt = sea_query::Table::create() + .table(bakery::Entity) + .if_not_exists() + .col( + ColumnDef::new(bakery::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(bakery::Column::Name).string()) + .col(ColumnDef::new(bakery::Column::ProfitMargin).float()) + .to_owned(); + + create_table(db, &stmt).await +} + +pub async fn create_baker_table(db: &DbConn) -> Result { + let stmt = sea_query::Table::create() + .table(baker::Entity) + .if_not_exists() + .col( + ColumnDef::new(baker::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(baker::Column::Name).string()) + .col(ColumnDef::new(baker::Column::BakeryId).integer()) + .foreign_key( + ForeignKey::create() + .name("FK_baker_bakery") + .from(baker::Entity, baker::Column::BakeryId) + .to(bakery::Entity, bakery::Column::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(); + + create_table(db, &stmt).await +} + +pub async fn create_customer_table(db: &DbConn) -> Result { + let stmt = sea_query::Table::create() + .table(customer::Entity) + .if_not_exists() + .col( + ColumnDef::new(customer::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(customer::Column::Name).string()) + .col(ColumnDef::new(customer::Column::Notes).text()) + .to_owned(); + + create_table(db, &stmt).await +} + +pub async fn create_order_table(db: &DbConn) -> Result { + let stmt = sea_query::Table::create() + .table(order::Entity) + .if_not_exists() + .col( + ColumnDef::new(order::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(order::Column::Total).float()) + .col(ColumnDef::new(order::Column::BakeryId).integer().not_null()) + .col( + ColumnDef::new(order::Column::CustomerId) + .integer() + .not_null(), + ) + .col( + ColumnDef::new(order::Column::PlacedAt) + .date_time() + .not_null(), + ) + .foreign_key( + ForeignKey::create() + .name("FK_order_bakery") + .from(order::Entity, order::Column::BakeryId) + .to(bakery::Entity, bakery::Column::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .foreign_key( + ForeignKey::create() + .name("FK_order_customer") + .from(order::Entity, order::Column::CustomerId) + .to(customer::Entity, customer::Column::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(); + + create_table(db, &stmt).await +} + +pub async fn create_lineitem_table(db: &DbConn) -> Result { + let stmt = sea_query::Table::create() + .table(lineitem::Entity) + .if_not_exists() + .col( + ColumnDef::new(lineitem::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(lineitem::Column::Price).decimal()) + .col(ColumnDef::new(lineitem::Column::Quantity).integer()) + .col( + ColumnDef::new(lineitem::Column::OrderId) + .integer() + .not_null(), + ) + .col( + ColumnDef::new(lineitem::Column::CakeId) + .integer() + .not_null(), + ) + .foreign_key( + ForeignKey::create() + .name("FK_lineitem_order") + .from(lineitem::Entity, lineitem::Column::OrderId) + .to(order::Entity, order::Column::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .foreign_key( + ForeignKey::create() + .name("FK_lineitem_cake") + .from(lineitem::Entity, lineitem::Column::CakeId) + .to(cake::Entity, cake::Column::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(); + + create_table(db, &stmt).await +} + +pub async fn create_cakes_bakers_table(db: &DbConn) -> Result { + let stmt = sea_query::Table::create() + .table(cakes_bakers::Entity) + .if_not_exists() + .col( + ColumnDef::new(cakes_bakers::Column::CakeId) + .integer() + .not_null(), + ) + .col( + ColumnDef::new(cakes_bakers::Column::BakerId) + .integer() + .not_null(), + ) + .primary_key( + Index::create() + .col(cakes_bakers::Column::CakeId) + .col(cakes_bakers::Column::BakerId), + ) + .to_owned(); + + create_table(db, &stmt).await +} + +pub async fn create_cake_table(db: &DbConn) -> Result { + let stmt = sea_query::Table::create() + .table(cake::Entity) + .if_not_exists() + .col( + ColumnDef::new(cake::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(cake::Column::Name).string()) + .col(ColumnDef::new(cake::Column::Price).float()) + .col(ColumnDef::new(cake::Column::BakeryId).integer().not_null()) + .foreign_key( + ForeignKey::create() + .name("FK_cake_bakery") + .from(cake::Entity, cake::Column::BakeryId) + .to(bakery::Entity, bakery::Column::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .col(ColumnDef::new(cake::Column::GlutenFree).boolean()) + .to_owned(); + + create_table(db, &stmt).await +}