From 53bbf32229f49519269b1a8b5b0905d443e51664 Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Thu, 12 Aug 2021 22:05:06 +1000 Subject: [PATCH 01/89] Init Rocket example --- examples/rocket_example/Cargo.toml | 30 ++++++ examples/rocket_example/README.md | 3 + examples/rocket_example/Rocket.toml | 2 + examples/rocket_example/db/sqlx/db.sqlite | Bin 0 -> 20480 bytes .../20210331024424_create-posts-table.sql | 6 ++ examples/rocket_example/src/main.rs | 14 +++ examples/rocket_example/src/sqlx.rs | 90 ++++++++++++++++++ examples/rocket_example/src/tests.rs | 78 +++++++++++++++ 8 files changed, 223 insertions(+) create mode 100644 examples/rocket_example/Cargo.toml create mode 100644 examples/rocket_example/README.md create mode 100644 examples/rocket_example/Rocket.toml create mode 100644 examples/rocket_example/db/sqlx/db.sqlite create mode 100644 examples/rocket_example/db/sqlx/migrations/20210331024424_create-posts-table.sql create mode 100644 examples/rocket_example/src/main.rs create mode 100644 examples/rocket_example/src/sqlx.rs create mode 100644 examples/rocket_example/src/tests.rs diff --git a/examples/rocket_example/Cargo.toml b/examples/rocket_example/Cargo.toml new file mode 100644 index 00000000..44080a96 --- /dev/null +++ b/examples/rocket_example/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "rocket-example" +version = "0.1.0" +edition = "2018" +publish = false +[workspace] +[dependencies] +# rocket = { path = "../../../Rocket/core/lib", features = ["json"] } +rocket = { git = "https://github.com/SergioBenitez/Rocket.git", branch = "master", features = [ + "json", +] } +# async-std = { version = "^1.9", features = ["attributes"] } +sea-orm = { path = "../../", features = [ + "sqlx-all", + # "runtime-async-std-native-tls", +] } +serde_json = { version = "^1" } +futures = { version = "^0.3" } +async-stream = { version = "^0.3" } +futures-util = { version = "^0.3" } + +[dependencies.sqlx] +version = "0.5.1" +default-features = false +features = ["macros", "offline", "migrate"] + +[dependencies.rocket_db_pools] +git = "https://github.com/SergioBenitez/Rocket" +branch = "master" +features = ["sqlx_sqlite"] diff --git a/examples/rocket_example/README.md b/examples/rocket_example/README.md new file mode 100644 index 00000000..122bf09d --- /dev/null +++ b/examples/rocket_example/README.md @@ -0,0 +1,3 @@ +# Rocket with SeaOrm example app + +`cargo run` in the `rocket_example` folder diff --git a/examples/rocket_example/Rocket.toml b/examples/rocket_example/Rocket.toml new file mode 100644 index 00000000..300e46d3 --- /dev/null +++ b/examples/rocket_example/Rocket.toml @@ -0,0 +1,2 @@ +[default.databases.sqlx] +url = "db/sqlx/db.sqlite" diff --git a/examples/rocket_example/db/sqlx/db.sqlite b/examples/rocket_example/db/sqlx/db.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..a961169b0be0cf84bbad7a806bb1f0e842f1469c GIT binary patch literal 20480 zcmeI3&2JM&7>8&5k;Fm46-Dxa%ODgr5pm?T6A}<=vkqIZ>W|=TfLv;A))TVekKkRS zAgH2JdO&bRBE4|!q2h`}54}`srM8Dkz49+qs?=1KS_I>DV&ydxi4%wLo9re#o_#j+ zKK|KKHsi&MdDjpYt;Wqa4`j)@K7 zv^%q^_--&jk%4g*x1Q}-S(S7ztS(GqW3=%mM^uT&dY5??pl0{ z$qfw#U;qYS00v+H24DaNU;qYS00v;-T^kVEU{ue3RitDK}sf^rSdQTs(4ZMhb_x0$pheLCp-+OTXhY$7d@BeqZwfghsPtmUu zKOWz{aQU~hf6adL#gpe}?<;iv|1TH)vqOKr-PeKAfMQ?(24DaNU;qYS00v;-fE$P$8NE1ExWyObx^SN4 z=~PLsH0SMgxxU%vowddjTz(EXo%*QBom$hmJ~=tbSoj5j^wUa6KTV-;DKx8wW+~)R zXhsdqQ0QX{ol--mC={d6v>KWwQ0NZ|O{t+N3O%7vMh#^sbd5r3HI$~%G=+3Eq*I7d zNSz3sCPMq=L}^cq)PbOR&>j;+9S52QO%OyK2ATyeO%QbyXcE{ff~bQ)bHIKjh&l!| z1#F8T>JZQjuvLPnBR~_tG6dNt0DGSQ?)v|27T-SLZ+s{Z24DaNU;qYS00v+H24DaN TU;qZ*Hv=&(oX~=g5*&RE<&!)v literal 0 HcmV?d00001 diff --git a/examples/rocket_example/db/sqlx/migrations/20210331024424_create-posts-table.sql b/examples/rocket_example/db/sqlx/migrations/20210331024424_create-posts-table.sql new file mode 100644 index 00000000..11941923 --- /dev/null +++ b/examples/rocket_example/db/sqlx/migrations/20210331024424_create-posts-table.sql @@ -0,0 +1,6 @@ +CREATE TABLE posts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title VARCHAR NOT NULL, + text VARCHAR NOT NULL, + published BOOLEAN NOT NULL DEFAULT 0 +); diff --git a/examples/rocket_example/src/main.rs b/examples/rocket_example/src/main.rs new file mode 100644 index 00000000..18fc60b9 --- /dev/null +++ b/examples/rocket_example/src/main.rs @@ -0,0 +1,14 @@ +#[macro_use] +extern crate rocket; +#[macro_use] +extern crate rocket_db_pools; + +#[cfg(test)] +mod tests; + +mod sqlx; + +#[launch] +fn rocket() -> _ { + rocket::build().attach(sqlx::stage()) +} diff --git a/examples/rocket_example/src/sqlx.rs b/examples/rocket_example/src/sqlx.rs new file mode 100644 index 00000000..ffad0c73 --- /dev/null +++ b/examples/rocket_example/src/sqlx.rs @@ -0,0 +1,90 @@ +use rocket::{Rocket, Build, futures}; +use rocket::fairing::{self, AdHoc}; +use rocket::response::status::Created; +use rocket::serde::{Serialize, Deserialize, json::Json}; + +use rocket_db_pools::{sqlx, Database, Connection}; + +use futures::{stream::TryStreamExt, future::TryFutureExt}; + +#[derive(Database)] +#[database("sqlx")] +struct Db(sqlx::SqlitePool); + +type Result> = std::result::Result; + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(crate = "rocket::serde")] +struct Post { + #[serde(skip_deserializing, skip_serializing_if = "Option::is_none")] + id: Option, + title: String, + text: String, +} + +#[post("/", data = "")] +async fn create(mut db: Connection, post: Json) -> Result>> { + // There is no support for `RETURNING`. + sqlx::query!("INSERT INTO posts (title, text) VALUES (?, ?)", post.title, post.text) + .execute(&mut *db) + .await?; + + Ok(Created::new("/").body(post)) +} + +#[get("/")] +async fn list(mut db: Connection) -> Result>> { + let ids = sqlx::query!("SELECT id FROM posts") + .fetch(&mut *db) + .map_ok(|record| record.id) + .try_collect::>() + .await?; + + Ok(Json(ids)) +} + +#[get("/")] +async fn read(mut db: Connection, id: i64) -> Option> { + sqlx::query!("SELECT id, title, text FROM posts WHERE id = ?", id) + .fetch_one(&mut *db) + .map_ok(|r| Json(Post { id: Some(r.id), title: r.title, text: r.text })) + .await + .ok() +} + +#[delete("/")] +async fn delete(mut db: Connection, id: i64) -> Result> { + let result = sqlx::query!("DELETE FROM posts WHERE id = ?", id) + .execute(&mut *db) + .await?; + + Ok((result.rows_affected() == 1).then(|| ())) +} + +#[delete("/")] +async fn destroy(mut db: Connection) -> Result<()> { + sqlx::query!("DELETE FROM posts").execute(&mut *db).await?; + + Ok(()) +} + +async fn run_migrations(rocket: Rocket) -> fairing::Result { + match Db::fetch(&rocket) { + Some(db) => match sqlx::migrate!("db/sqlx/migrations").run(&**db).await { + Ok(_) => Ok(rocket), + Err(e) => { + error!("Failed to initialize SQLx database: {}", e); + Err(rocket) + } + } + None => Err(rocket), + } +} + +pub fn stage() -> AdHoc { + AdHoc::on_ignite("SQLx Stage", |rocket| async { + rocket.attach(Db::init()) + .attach(AdHoc::try_on_ignite("SQLx Migrations", run_migrations)) + .mount("/sqlx", routes![list, create, read, delete, destroy]) + }) +} diff --git a/examples/rocket_example/src/tests.rs b/examples/rocket_example/src/tests.rs new file mode 100644 index 00000000..98457e7f --- /dev/null +++ b/examples/rocket_example/src/tests.rs @@ -0,0 +1,78 @@ +use rocket::fairing::AdHoc; +use rocket::local::blocking::Client; +use rocket::serde::{Serialize, Deserialize}; +use rocket::http::Status; + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(crate = "rocket::serde")] +struct Post { + title: String, + text: String, +} + +fn test(base: &str, stage: AdHoc) { + // Number of posts we're going to create/read/delete. + const N: usize = 20; + + // NOTE: If we had more than one test running concurently that dispatches + // DB-accessing requests, we'd need transactions or to serialize all tests. + let client = Client::tracked(rocket::build().attach(stage)).unwrap(); + + // Clear everything from the database. + assert_eq!(client.delete(base).dispatch().status(), Status::Ok); + assert_eq!(client.get(base).dispatch().into_json::>(), Some(vec![])); + + // Add some random posts, ensure they're listable and readable. + for i in 1..=N{ + let title = format!("My Post - {}", i); + let text = format!("Once upon a time, at {}'o clock...", i); + let post = Post { title: title.clone(), text: text.clone() }; + + // Create a new post. + let response = client.post(base).json(&post).dispatch().into_json::(); + assert_eq!(response.unwrap(), post); + + // Ensure the index shows one more post. + let list = client.get(base).dispatch().into_json::>().unwrap(); + assert_eq!(list.len(), i); + + // The last in the index is the new one; ensure contents match. + let last = list.last().unwrap(); + let response = client.get(format!("{}/{}", base, last)).dispatch(); + assert_eq!(response.into_json::().unwrap(), post); + } + + // Now delete all of the posts. + for _ in 1..=N { + // Get a valid ID from the index. + let list = client.get(base).dispatch().into_json::>().unwrap(); + let id = list.get(0).expect("have post"); + + // Delete that post. + let response = client.delete(format!("{}/{}", base, id)).dispatch(); + assert_eq!(response.status(), Status::Ok); + } + + // Ensure they're all gone. + let list = client.get(base).dispatch().into_json::>().unwrap(); + assert!(list.is_empty()); + + // Trying to delete should now 404. + let response = client.delete(format!("{}/{}", base, 1)).dispatch(); + assert_eq!(response.status(), Status::NotFound); +} + +#[test] +fn test_sqlx() { + test("/sqlx", crate::sqlx::stage()) +} + +#[test] +fn test_diesel() { + test("/diesel", crate::diesel_sqlite::stage()) +} + +#[test] +fn test_rusqlite() { + test("/rusqlite", crate::rusqlite::stage()) +} From 73b72e3a31b4990c70a9284ecae57535b897cf7a Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Tue, 17 Aug 2021 22:37:28 +1000 Subject: [PATCH 02/89] WIP --- examples/rocket_example/db/sqlx/db.sqlite | Bin 20480 -> 24576 bytes examples/rocket_example/db/sqlx/db.sqlite-shm | Bin 0 -> 32768 bytes examples/rocket_example/db/sqlx/db.sqlite-wal | 0 examples/rocket_example/src/sqlx.rs | 90 -------------- examples/rocket_example/src/sqlx/mod.rs | 113 ++++++++++++++++++ examples/rocket_example/src/sqlx/post.rs | 51 ++++++++ examples/rocket_example/src/tests.rs | 26 ++-- 7 files changed, 175 insertions(+), 105 deletions(-) create mode 100644 examples/rocket_example/db/sqlx/db.sqlite-shm create mode 100644 examples/rocket_example/db/sqlx/db.sqlite-wal delete mode 100644 examples/rocket_example/src/sqlx.rs create mode 100644 examples/rocket_example/src/sqlx/mod.rs create mode 100644 examples/rocket_example/src/sqlx/post.rs diff --git a/examples/rocket_example/db/sqlx/db.sqlite b/examples/rocket_example/db/sqlx/db.sqlite index a961169b0be0cf84bbad7a806bb1f0e842f1469c..362447f5c77269de04bcdc876be3d77e0012ecbe 100644 GIT binary patch literal 24576 zcmeI4L2TPp7{~3lX`7-FG_*w%5_$+tTeYUV=jZs@(*#RL*a~UWr718?C3?1>*RV9{ zVh43XXkv`RG$c+)8wYmffVd*D0~aJF!461Vci_Mk35g*jK=7QbWv{s5(1=6%UhFi_ zpa0)`-_KwCZ0n^m_Oq}!G74&kUa`JScf)jKDOQXZzHV zTLY6Cj0e7xCl)cOjV;F}f{?%#)bwS|Bc{oWnTs9KzBG8Zz6iZ`-4!!ltI=6&hAZgB zrSihSdcEa-owxa#h(tS_snpM}Mk{D;p*r|xnw>@?K0<03)H7_y=Z?+fF<_h!;t<~z zzL)oy?c0{bZ*j|~7GuN>giDNoQo>!2TLyEyUaAwDy*{MGv)%e2v^V5P zF2!he?o>RGKNMaspk9ux#|@xr@l_d*5>veyM!X@fq-wE!ZoarwEzB=&?X=bVlgpKg zgjCzwrRP2Dc=9lY|A@M0^;U&<{0%_4)D4 z&nG5iTzcjgO)gM?01yBIKmZ5;0U!VbfB+Bx0zd!=0D(P4AU&K)_ns?EX1>>kGIzAh z51BiU{P^#$J%s^+0Rlh(2mk>f00e*l5C8&uoWRJwLubb4FQG*_FC68Nfy;8HIqIy- z`OO@~bB(8w{2j1?54VulXn7ZAW@h9sf$u3I;v|ZQql!LOMRu~tRz;yIV#y+=ik?+P zR|WKeTKHxx;7DyW&D{i#Tj6G6=b?HfgsoCazZ zXg3u}auTRHpaqH~IR%Wv@{IN5!R&#QcJR=6JU;*#yxEFYMH|I6X&pJZUmB;mia0@0xq_|L<#=`+NMv2jKw$AOHk_01yBIKmZ5;0U!VbfB+EqzY$35!&yCk8W0|c l@BigL{euDofB+Bx0zd!=00AHX1b_e#00KY&2<%}3e*uejcHaO1 delta 134 zcmZoTz}T>Wae}lU69WSSD-go~(?lI(Q6>gG{~BKY9}FzK^$dL7{M&gi^44z_6yWCF zT+iF2#12v{z`!tBz(8TLf&s^71B1!@GWiP0Ir+)idU|?5Sz~2?4&O?Jfc)YT1ziQh U$&5ClJcjBBC7T@n^D~M701Y}FsQ>@~ diff --git a/examples/rocket_example/db/sqlx/db.sqlite-shm b/examples/rocket_example/db/sqlx/db.sqlite-shm new file mode 100644 index 0000000000000000000000000000000000000000..fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10 GIT binary patch literal 32768 zcmeIuAr62r3> = std::result::Result; - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(crate = "rocket::serde")] -struct Post { - #[serde(skip_deserializing, skip_serializing_if = "Option::is_none")] - id: Option, - title: String, - text: String, -} - -#[post("/", data = "")] -async fn create(mut db: Connection, post: Json) -> Result>> { - // There is no support for `RETURNING`. - sqlx::query!("INSERT INTO posts (title, text) VALUES (?, ?)", post.title, post.text) - .execute(&mut *db) - .await?; - - Ok(Created::new("/").body(post)) -} - -#[get("/")] -async fn list(mut db: Connection) -> Result>> { - let ids = sqlx::query!("SELECT id FROM posts") - .fetch(&mut *db) - .map_ok(|record| record.id) - .try_collect::>() - .await?; - - Ok(Json(ids)) -} - -#[get("/")] -async fn read(mut db: Connection, id: i64) -> Option> { - sqlx::query!("SELECT id, title, text FROM posts WHERE id = ?", id) - .fetch_one(&mut *db) - .map_ok(|r| Json(Post { id: Some(r.id), title: r.title, text: r.text })) - .await - .ok() -} - -#[delete("/")] -async fn delete(mut db: Connection, id: i64) -> Result> { - let result = sqlx::query!("DELETE FROM posts WHERE id = ?", id) - .execute(&mut *db) - .await?; - - Ok((result.rows_affected() == 1).then(|| ())) -} - -#[delete("/")] -async fn destroy(mut db: Connection) -> Result<()> { - sqlx::query!("DELETE FROM posts").execute(&mut *db).await?; - - Ok(()) -} - -async fn run_migrations(rocket: Rocket) -> fairing::Result { - match Db::fetch(&rocket) { - Some(db) => match sqlx::migrate!("db/sqlx/migrations").run(&**db).await { - Ok(_) => Ok(rocket), - Err(e) => { - error!("Failed to initialize SQLx database: {}", e); - Err(rocket) - } - } - None => Err(rocket), - } -} - -pub fn stage() -> AdHoc { - AdHoc::on_ignite("SQLx Stage", |rocket| async { - rocket.attach(Db::init()) - .attach(AdHoc::try_on_ignite("SQLx Migrations", run_migrations)) - .mount("/sqlx", routes![list, create, read, delete, destroy]) - }) -} diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs new file mode 100644 index 00000000..178bb3ef --- /dev/null +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -0,0 +1,113 @@ +use rocket::fairing::{self, AdHoc}; +use rocket::response::status::Created; +use rocket::serde::{json::Json, Deserialize, Serialize}; +use rocket::{futures, Build, Rocket}; + +use rocket_db_pools::{sqlx, Connection, Database}; + +use futures::{future::TryFutureExt, stream::TryStreamExt}; + +// use post::*; +mod post; +pub use post::Entity as Post; + +#[derive(Database)] +#[database("sqlx")] +struct Db(sqlx::SqlitePool); + +type Result> = std::result::Result; + +// #[derive(Debug, Clone, Deserialize, Serialize)] +// #[serde(crate = "rocket::serde")] +// struct Post { +// #[serde(skip_deserializing, skip_serializing_if = "Option::is_none")] +// id: Option, +// title: String, +// text: String, +// } + +// #[post("/", data = "")] +// async fn create(mut db: Connection, post: Json) -> Result>> { +// // There is no support for `RETURNING`. +// sqlx::query!( +// "INSERT INTO posts (title, text) VALUES (?, ?)", +// post.title, +// post.text +// ) +// .execute(&mut *db) +// .await?; + +// Ok(Created::new("/").body(post)) +// } + +#[get("/")] +async fn list(mut db: Connection) -> Result>> { + // let ids = sqlx::query!("SELECT id FROM posts") + // .fetch(&mut *db) + // .map_ok(|record| record.id) + // .try_collect::>() + // .await?; + let ids = vec![]; + Ok(Json(ids)) +} + +#[get("/")] +async fn read(mut db: Connection, id: i64) -> Option> { + let post: Option = Post::find_by_id(id) + .one(db) + .await + .expect("could not find baker"); + println!("post: {:#?}", post); + + // sqlx::query!("SELECT id, title, text FROM posts WHERE id = ?", id) + // .fetch_one(&mut *db) + // .map_ok(|r| { + // Json(Post { + // id: Some(r.id), + // title: r.title, + // text: r.text, + // }) + // }) + // .await + // .ok() + + None +} + +// #[delete("/")] +// async fn delete(mut db: Connection, id: i64) -> Result> { +// let result = sqlx::query!("DELETE FROM posts WHERE id = ?", id) +// .execute(&mut *db) +// .await?; + +// Ok((result.rows_affected() == 1).then(|| ())) +// } + +// #[delete("/")] +// async fn destroy(mut db: Connection) -> Result<()> { +// sqlx::query!("DELETE FROM posts").execute(&mut *db).await?; + +// Ok(()) +// } + +async fn run_migrations(rocket: Rocket) -> fairing::Result { + match Db::fetch(&rocket) { + Some(db) => match sqlx::migrate!("db/sqlx/migrations").run(&**db).await { + Ok(_) => Ok(rocket), + Err(e) => { + error!("Failed to initialize SQLx database: {}", e); + Err(rocket) + } + }, + None => Err(rocket), + } +} + +pub fn stage() -> AdHoc { + AdHoc::on_ignite("SQLx Stage", |rocket| async { + rocket + .attach(Db::init()) + .attach(AdHoc::try_on_ignite("SQLx Migrations", run_migrations)) + .mount("/sqlx", routes![list, read]) + }) +} diff --git a/examples/rocket_example/src/sqlx/post.rs b/examples/rocket_example/src/sqlx/post.rs new file mode 100644 index 00000000..360792e1 --- /dev/null +++ b/examples/rocket_example/src/sqlx/post.rs @@ -0,0 +1,51 @@ +use rocket::serde::{json::Json, Deserialize, Serialize}; +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "post" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Deserialize, Serialize)] +#[serde(crate = "rocket::serde")] +pub struct Model { + pub id: Option, + pub title: String, + pub text: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Title, + Text, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + fn auto_increment() -> bool { + true + } +} + +impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Title => ColumnType::String(None).def(), + Self::Text => ColumnType::String(None).def(), + } + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/rocket_example/src/tests.rs b/examples/rocket_example/src/tests.rs index 98457e7f..6d88b1c0 100644 --- a/examples/rocket_example/src/tests.rs +++ b/examples/rocket_example/src/tests.rs @@ -1,7 +1,7 @@ use rocket::fairing::AdHoc; -use rocket::local::blocking::Client; -use rocket::serde::{Serialize, Deserialize}; use rocket::http::Status; +use rocket::local::blocking::Client; +use rocket::serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(crate = "rocket::serde")] @@ -20,13 +20,19 @@ fn test(base: &str, stage: AdHoc) { // Clear everything from the database. assert_eq!(client.delete(base).dispatch().status(), Status::Ok); - assert_eq!(client.get(base).dispatch().into_json::>(), Some(vec![])); + assert_eq!( + client.get(base).dispatch().into_json::>(), + Some(vec![]) + ); // Add some random posts, ensure they're listable and readable. - for i in 1..=N{ + for i in 1..=N { let title = format!("My Post - {}", i); let text = format!("Once upon a time, at {}'o clock...", i); - let post = Post { title: title.clone(), text: text.clone() }; + let post = Post { + title: title.clone(), + text: text.clone(), + }; // Create a new post. let response = client.post(base).json(&post).dispatch().into_json::(); @@ -66,13 +72,3 @@ fn test(base: &str, stage: AdHoc) { fn test_sqlx() { test("/sqlx", crate::sqlx::stage()) } - -#[test] -fn test_diesel() { - test("/diesel", crate::diesel_sqlite::stage()) -} - -#[test] -fn test_rusqlite() { - test("/rusqlite", crate::rusqlite::stage()) -} From c6480635c05745121b58856b82e200cb2c51ee7f Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Sat, 21 Aug 2021 21:58:20 +1000 Subject: [PATCH 03/89] WIP --- examples/rocket_example/Cargo.toml | 17 ++- examples/rocket_example/Rocket.toml | 7 +- examples/rocket_example/src/sqlx/mod.rs | 178 ++++++++++++++++++---- examples/rocket_example/src/sqlx/post.rs | 12 +- examples/rocket_example/src/sqlx/setup.rs | 37 +++++ examples/rocket_example/src/tests.rs | 74 --------- 6 files changed, 216 insertions(+), 109 deletions(-) create mode 100644 examples/rocket_example/src/sqlx/setup.rs delete mode 100644 examples/rocket_example/src/tests.rs diff --git a/examples/rocket_example/Cargo.toml b/examples/rocket_example/Cargo.toml index 44080a96..b53c613b 100644 --- a/examples/rocket_example/Cargo.toml +++ b/examples/rocket_example/Cargo.toml @@ -5,15 +5,17 @@ edition = "2018" publish = false [workspace] [dependencies] -# rocket = { path = "../../../Rocket/core/lib", features = ["json"] } -rocket = { git = "https://github.com/SergioBenitez/Rocket.git", branch = "master", features = [ - "json", -] } +rocket = { path = "../../../Rocket/core/lib", features = ["json"] } +# rocket = { git = "https://github.com/SergioBenitez/Rocket.git", branch = "master", features = [ +# "json", +# ] } # async-std = { version = "^1.9", features = ["attributes"] } sea-orm = { path = "../../", features = [ "sqlx-all", # "runtime-async-std-native-tls", ] } +sea-query = { version = "^0.12.8" } + serde_json = { version = "^1" } futures = { version = "^0.3" } async-stream = { version = "^0.3" } @@ -24,7 +26,10 @@ version = "0.5.1" default-features = false features = ["macros", "offline", "migrate"] +# [dependencies.rocket_db_pools] +# git = "https://github.com/SergioBenitez/Rocket" +# branch = "master" +# features = ["sea_orm"] [dependencies.rocket_db_pools] -git = "https://github.com/SergioBenitez/Rocket" -branch = "master" +path = "../../../Rocket/contrib/db_pools/lib" features = ["sqlx_sqlite"] diff --git a/examples/rocket_example/Rocket.toml b/examples/rocket_example/Rocket.toml index 300e46d3..0fe9f8eb 100644 --- a/examples/rocket_example/Rocket.toml +++ b/examples/rocket_example/Rocket.toml @@ -1,2 +1,5 @@ -[default.databases.sqlx] -url = "db/sqlx/db.sqlite" +# [default.databases.sqlx] +# url = "sqlite::memory:" + +[global.databases] +blog = { url = "sqlite::memory:" } diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index 178bb3ef..aec71324 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -6,17 +6,21 @@ use rocket::{futures, Build, Rocket}; use rocket_db_pools::{sqlx, Connection, Database}; use futures::{future::TryFutureExt, stream::TryStreamExt}; +use sea_orm::entity::*; +use sea_orm::QueryFilter; + +mod setup; + +#[derive(Database, Debug)] +#[database("blog")] +struct Db(rocket_db_pools::sqlx::SqlitePool); + +type Result> = std::result::Result; // use post::*; mod post; pub use post::Entity as Post; - -#[derive(Database)] -#[database("sqlx")] -struct Db(sqlx::SqlitePool); - -type Result> = std::result::Result; - +use sea_orm::DatabaseConnection; // #[derive(Debug, Clone, Deserialize, Serialize)] // #[serde(crate = "rocket::serde")] // struct Post { @@ -47,17 +51,58 @@ async fn list(mut db: Connection) -> Result>> { // .map_ok(|record| record.id) // .try_collect::>() // .await?; - let ids = vec![]; + // // let ids: Vec = vec![]; + + // let ids = sqlx::query( + // r#" + // SELECT id FROM posts + // "#, + // ) + // .execute(&mut *db) + // .await?; + // // .map_ok(|record| record.id); + // // .try_collect::>(); + // println!("ids: {:#?}", ids); + + // let ids: Vec = vec![]; + // Ok(Json(ids)) + + // let mut conn = db.acquire().await?; + // println!("conn: {:#?}", conn); + + // let ids = sqlx::query("SELECT id FROM posts") + // .fetch(&mut *db) + // .map_ok(|record| record.id) + // .try_collect::>() + // .await?; + + // Ok(Json(ids)) + + // let recs = sqlx::query( + // r#" + // SELECT id FROM posts + // "#, + // ) + // .fetch_all(&mut *db) + // .await?; + // let ids: Vec = recs.into(); + + // println!("recs: {:#?}", ids); + + let posts = Post::find().all(&mut *db).await.unwrap(); + assert_eq!(posts.len(), 0); + + let ids: Vec = vec![]; Ok(Json(ids)) } #[get("/")] async fn read(mut db: Connection, id: i64) -> Option> { - let post: Option = Post::find_by_id(id) - .one(db) - .await - .expect("could not find baker"); - println!("post: {:#?}", post); + // let post: Option = Post::find_by_id(id) + // .one(db) + // .await + // .expect("could not find baker"); + // println!("post: {:#?}", post); // sqlx::query!("SELECT id, title, text FROM posts WHERE id = ?", id) // .fetch_one(&mut *db) @@ -90,24 +135,105 @@ async fn read(mut db: Connection, id: i64) -> Option> { // Ok(()) // } -async fn run_migrations(rocket: Rocket) -> fairing::Result { - match Db::fetch(&rocket) { - Some(db) => match sqlx::migrate!("db/sqlx/migrations").run(&**db).await { - Ok(_) => Ok(rocket), - Err(e) => { - error!("Failed to initialize SQLx database: {}", e); - Err(rocket) - } - }, - None => Err(rocket), - } -} +// async fn run_migrations(rocket: Rocket) -> fairing::Result { +// use crate::rocket_db_pools::Pool; +// // match Db::fetch(&rocket) { +// // Some(db) => match sqlx::migrate!("db/sqlx/migrations").run(&**db).await { +// // Ok(_) => Ok(rocket), +// // Err(e) => { +// // error!("Failed to initialize SQLx database: {}", e); +// // Err(rocket) +// // } +// // }, +// // None => Err(rocket), +// // } +// // let conn = Db::get(&rocket).await.expect("database connection"); + +// match Db::fetch(&rocket) { +// Some(db) => match setup::create_post_table(db.get().await().expect("database connection")).await { +// Ok(_) => { +// println!("rocket: {:#?}", rocket); + +// Ok(rocket) +// } +// Err(e) => { +// error!("Failed to initialize SQLx database: {}", e); +// Err(rocket) +// } +// }, +// None => Err(rocket), +// } +// // Ok(rocket) +// } pub fn stage() -> AdHoc { AdHoc::on_ignite("SQLx Stage", |rocket| async { rocket .attach(Db::init()) - .attach(AdHoc::try_on_ignite("SQLx Migrations", run_migrations)) + .attach(AdHoc::try_on_ignite("Create init post", |rocket| async { + let db = Db::fetch(&rocket).expect("database mounted"); + let res = sqlx::query( + r#" + CREATE TABLE posts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title VARCHAR NOT NULL, + text VARCHAR NOT NULL, + published BOOLEAN NOT NULL DEFAULT 0 + )"#, + ) + .execute(&**db) + .await; + println!("res: {:#?}", res); + + let res2 = sqlx::query( + r#" + INSERT INTO posts (title, text) VALUES ('a post', 'content of a post') + "#, + ) + .execute(&**db) + .await; + println!("res2: {:#?}", res2); + + // Db::fetch(&rocket) + // .run(|db| { + // sqlx::query("DELETE FROM table").execute(&pool).await; + + // // conn.execute( + // // r#" + // // CREATE TABLE posts ( + // // id INTEGER PRIMARY KEY AUTOINCREMENT, + // // title VARCHAR NOT NULL, + // // text VARCHAR NOT NULL, + // // published BOOLEAN NOT NULL DEFAULT 0 + // // )"#, + // // params![], + // // ) + // }) + // .await + // .expect("can init rusqlite DB"); + Ok(rocket) + + // match Db::fetch(&rocket) { + // Some(db) => { + // println!("db: {:#?}", db); + // println!("&**db: {:#?}", &**db); + + // Ok(rocket) + // } + // None => Err(rocket), + // } + })) .mount("/sqlx", routes![list, read]) }) } + +// pub async fn create_post(db: &DbConn) { +// let post = post::ActiveModel { +// title: Set("Post One".to_owned()), +// text: Set("post content 1".to_owned()), +// ..Default::default() +// } +// .save(db) +// .await +// .expect("could not insert post"); +// } diff --git a/examples/rocket_example/src/sqlx/post.rs b/examples/rocket_example/src/sqlx/post.rs index 360792e1..96f694ac 100644 --- a/examples/rocket_example/src/sqlx/post.rs +++ b/examples/rocket_example/src/sqlx/post.rs @@ -1,7 +1,8 @@ use rocket::serde::{json::Json, Deserialize, Serialize}; use sea_orm::entity::prelude::*; -#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +#[derive(Copy, Clone, Default, Debug, DeriveEntity, Deserialize, Serialize)] +#[serde(crate = "rocket::serde")] pub struct Entity; impl EntityName for Entity { @@ -13,6 +14,7 @@ impl EntityName for Entity { #[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Deserialize, Serialize)] #[serde(crate = "rocket::serde")] pub struct Model { + #[serde(skip_deserializing, skip_serializing_if = "Option::is_none")] pub id: Option, pub title: String, pub text: String, @@ -36,6 +38,9 @@ impl PrimaryKeyTrait for PrimaryKey { } } +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation {} + impl ColumnTrait for Column { type EntityName = Entity; @@ -48,4 +53,9 @@ impl ColumnTrait for Column { } } +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + panic!() + } +} impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/rocket_example/src/sqlx/setup.rs b/examples/rocket_example/src/sqlx/setup.rs new file mode 100644 index 00000000..fb4882a5 --- /dev/null +++ b/examples/rocket_example/src/sqlx/setup.rs @@ -0,0 +1,37 @@ +use sea_orm::{error::*, sea_query, DbConn, ExecResult}; + +use sea_query::{ColumnDef, ForeignKey, ForeignKeyAction, Index, TableCreateStatement}; + +// mod post; +pub use super::post::*; + +async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result { + let builder = db.get_database_backend(); + db.execute(builder.build(stmt)).await +} + +pub async fn create_post_table(db: &DbConn) -> Result { + let stmt = sea_query::Table::create() + .table(super::post::Entity) + .if_not_exists() + .col( + ColumnDef::new(super::post::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col( + ColumnDef::new(super::post::Column::Title) + .string() + .not_null(), + ) + .col( + ColumnDef::new(super::post::Column::Text) + .string() + .not_null(), + ) + .to_owned(); + + create_table(db, &stmt).await +} diff --git a/examples/rocket_example/src/tests.rs b/examples/rocket_example/src/tests.rs deleted file mode 100644 index 6d88b1c0..00000000 --- a/examples/rocket_example/src/tests.rs +++ /dev/null @@ -1,74 +0,0 @@ -use rocket::fairing::AdHoc; -use rocket::http::Status; -use rocket::local::blocking::Client; -use rocket::serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -#[serde(crate = "rocket::serde")] -struct Post { - title: String, - text: String, -} - -fn test(base: &str, stage: AdHoc) { - // Number of posts we're going to create/read/delete. - const N: usize = 20; - - // NOTE: If we had more than one test running concurently that dispatches - // DB-accessing requests, we'd need transactions or to serialize all tests. - let client = Client::tracked(rocket::build().attach(stage)).unwrap(); - - // Clear everything from the database. - assert_eq!(client.delete(base).dispatch().status(), Status::Ok); - assert_eq!( - client.get(base).dispatch().into_json::>(), - Some(vec![]) - ); - - // Add some random posts, ensure they're listable and readable. - for i in 1..=N { - let title = format!("My Post - {}", i); - let text = format!("Once upon a time, at {}'o clock...", i); - let post = Post { - title: title.clone(), - text: text.clone(), - }; - - // Create a new post. - let response = client.post(base).json(&post).dispatch().into_json::(); - assert_eq!(response.unwrap(), post); - - // Ensure the index shows one more post. - let list = client.get(base).dispatch().into_json::>().unwrap(); - assert_eq!(list.len(), i); - - // The last in the index is the new one; ensure contents match. - let last = list.last().unwrap(); - let response = client.get(format!("{}/{}", base, last)).dispatch(); - assert_eq!(response.into_json::().unwrap(), post); - } - - // Now delete all of the posts. - for _ in 1..=N { - // Get a valid ID from the index. - let list = client.get(base).dispatch().into_json::>().unwrap(); - let id = list.get(0).expect("have post"); - - // Delete that post. - let response = client.delete(format!("{}/{}", base, id)).dispatch(); - assert_eq!(response.status(), Status::Ok); - } - - // Ensure they're all gone. - let list = client.get(base).dispatch().into_json::>().unwrap(); - assert!(list.is_empty()); - - // Trying to delete should now 404. - let response = client.delete(format!("{}/{}", base, 1)).dispatch(); - assert_eq!(response.status(), Status::NotFound); -} - -#[test] -fn test_sqlx() { - test("/sqlx", crate::sqlx::stage()) -} From 0998b64ed479a14c6ea98183488fff1355afc5e4 Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Sun, 22 Aug 2021 14:18:27 +1000 Subject: [PATCH 04/89] WIP --- examples/rocket_example/Cargo.toml | 7 +-- examples/rocket_example/src/sqlx/mod.rs | 83 ++++++++++++++++++------- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/examples/rocket_example/Cargo.toml b/examples/rocket_example/Cargo.toml index b53c613b..d6293676 100644 --- a/examples/rocket_example/Cargo.toml +++ b/examples/rocket_example/Cargo.toml @@ -10,10 +10,7 @@ rocket = { path = "../../../Rocket/core/lib", features = ["json"] } # "json", # ] } # async-std = { version = "^1.9", features = ["attributes"] } -sea-orm = { path = "../../", features = [ - "sqlx-all", - # "runtime-async-std-native-tls", -] } +sea-orm = { path = "../../", features = ["sqlx-all"] } sea-query = { version = "^0.12.8" } serde_json = { version = "^1" } @@ -32,4 +29,4 @@ features = ["macros", "offline", "migrate"] # features = ["sea_orm"] [dependencies.rocket_db_pools] path = "../../../Rocket/contrib/db_pools/lib" -features = ["sqlx_sqlite"] +features = ["seaorm_sqlx_sqlite"] diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index aec71324..e39b6165 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -7,13 +7,15 @@ use rocket_db_pools::{sqlx, Connection, Database}; use futures::{future::TryFutureExt, stream::TryStreamExt}; use sea_orm::entity::*; -use sea_orm::QueryFilter; +use sea_orm::{ + DatabaseBackend, QueryFilter, SqlxSqliteConnector, SqlxSqlitePoolConnection, Statement, +}; mod setup; #[derive(Database, Debug)] #[database("blog")] -struct Db(rocket_db_pools::sqlx::SqlitePool); +struct Db(sea_orm::DatabaseConnection); type Result> = std::result::Result; @@ -21,6 +23,7 @@ type Result> = std::result::Result) -> Result>> { // let ids: Vec = recs.into(); // println!("recs: {:#?}", ids); + println!("db: {:#?}", &*db); + let res = db + .execute(Statement::from_string( + DatabaseBackend::Sqlite, + "SELECT * from posts".to_owned(), + )) + .await; + println!("res: {:#?}", res); - let posts = Post::find().all(&mut *db).await.unwrap(); - assert_eq!(posts.len(), 0); + // let con = SqlxSqliteConnector::from_sqlx_sqlite_pool(db); + // let posts = Post::find().all(&db).await.unwrap(); + // assert_eq!(posts.len(), 0); let ids: Vec = vec![]; Ok(Json(ids)) @@ -172,27 +184,50 @@ pub fn stage() -> AdHoc { .attach(Db::init()) .attach(AdHoc::try_on_ignite("Create init post", |rocket| async { let db = Db::fetch(&rocket).expect("database mounted"); - let res = sqlx::query( - r#" - CREATE TABLE posts ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - title VARCHAR NOT NULL, - text VARCHAR NOT NULL, - published BOOLEAN NOT NULL DEFAULT 0 - )"#, - ) - .execute(&**db) - .await; - println!("res: {:#?}", res); + // let res = sqlx::query( + // r#" + // CREATE TABLE posts ( + // id INTEGER PRIMARY KEY AUTOINCREMENT, + // title VARCHAR NOT NULL, + // text VARCHAR NOT NULL, + // published BOOLEAN NOT NULL DEFAULT 0 + // )"#, + // ) + // .execute(&**db) + // .await; - let res2 = sqlx::query( - r#" - INSERT INTO posts (title, text) VALUES ('a post', 'content of a post') - "#, - ) - .execute(&**db) - .await; - println!("res2: {:#?}", res2); + let create_post_table = db + .execute(Statement::from_string( + DatabaseBackend::Sqlite, + r#" + CREATE TABLE posts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title VARCHAR NOT NULL, + text VARCHAR NOT NULL, + published BOOLEAN NOT NULL DEFAULT 0 + )"# + .to_owned(), + )) + .await; + println!("create_post_table: {:#?}", create_post_table); + + let create_post = db + .execute(Statement::from_string( + DatabaseBackend::Sqlite, + "INSERT INTO posts (title, text) VALUES ('a post', 'content of a post')" + .to_owned(), + )) + .await; + println!("create_post: {:#?}", create_post); + + // let res2 = sqlx::query( + // r#" + // INSERT INTO posts (title, text) VALUES ('a post', 'content of a post') + // "#, + // ) + // .execute(&**db) + // .await; + // println!("res2: {:#?}", res2); // Db::fetch(&rocket) // .run(|db| { From e009d2ffcfaa2c90e2b5bb7ff425d5eefd7e8702 Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Sun, 22 Aug 2021 15:30:04 +1000 Subject: [PATCH 05/89] WIP --- examples/rocket_example/src/sqlx/mod.rs | 52 +++++++++++++++++-------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index e39b6165..3229aa98 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -15,7 +15,7 @@ mod setup; #[derive(Database, Debug)] #[database("blog")] -struct Db(sea_orm::DatabaseConnection); +struct Db(sea_orm::Database); type Result> = std::result::Result; @@ -48,7 +48,7 @@ use sea_orm::DatabaseConnection; // } #[get("/")] -async fn list(mut db: Connection) -> Result>> { +async fn list(mut con: Connection) -> Result>> { // let ids = sqlx::query!("SELECT id FROM posts") // .fetch(&mut *db) // .map_ok(|record| record.id) @@ -91,17 +91,32 @@ async fn list(mut db: Connection) -> Result>> { // let ids: Vec = recs.into(); // println!("recs: {:#?}", ids); - println!("db: {:#?}", &*db); - let res = db - .execute(Statement::from_string( + // println!("db: {:#?}", &*db); + // let res = db + // .execute(Statement::from_string( + // DatabaseBackend::Sqlite, + // "SELECT * from posts".to_owned(), + // )) + // .await; + // println!("res: {:#?}", res); + + let all_posts = con + .query_all(Statement::from_string( DatabaseBackend::Sqlite, - "SELECT * from posts".to_owned(), + "select * from posts;".to_owned(), )) - .await; - println!("res: {:#?}", res); + .await + .unwrap(); + for post in all_posts.into_iter() { + // let p = Post::from_raw_query_result(post); + println!( + "p: {:#?}", + sea_orm::JsonValue::from_query_result(&post, "").unwrap() + ); + } // let con = SqlxSqliteConnector::from_sqlx_sqlite_pool(db); - // let posts = Post::find().all(&db).await.unwrap(); + // let posts = Post::find().all(&con).await.unwrap(); // assert_eq!(posts.len(), 0); let ids: Vec = vec![]; @@ -183,7 +198,9 @@ pub fn stage() -> AdHoc { rocket .attach(Db::init()) .attach(AdHoc::try_on_ignite("Create init post", |rocket| async { - let db = Db::fetch(&rocket).expect("database mounted"); + let con = sea_orm::Database::connect("mysql://root:@localhost/rocket_example") + .await + .unwrap(); // let res = sqlx::query( // r#" // CREATE TABLE posts ( @@ -195,23 +212,22 @@ pub fn stage() -> AdHoc { // ) // .execute(&**db) // .await; - - let create_post_table = db + let create_post_table = con .execute(Statement::from_string( DatabaseBackend::Sqlite, r#" CREATE TABLE posts ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - title VARCHAR NOT NULL, - text VARCHAR NOT NULL, - published BOOLEAN NOT NULL DEFAULT 0 + id int NOT NULL AUTO_INCREMENT, + title VARCHAR(255) NOT NULL, + text VARCHAR(255) NOT NULL, + PRIMARY KEY (id) )"# .to_owned(), )) .await; println!("create_post_table: {:#?}", create_post_table); - let create_post = db + let create_post = con .execute(Statement::from_string( DatabaseBackend::Sqlite, "INSERT INTO posts (title, text) VALUES ('a post', 'content of a post')" @@ -220,6 +236,8 @@ pub fn stage() -> AdHoc { .await; println!("create_post: {:#?}", create_post); + // println!("all_posts: {:#?}", all_posts); + // let res2 = sqlx::query( // r#" // INSERT INTO posts (title, text) VALUES ('a post', 'content of a post') From 83a2b540b960123e51d7c787e17be5c9d9fca8c7 Mon Sep 17 00:00:00 2001 From: Bobby Ng Date: Mon, 23 Aug 2021 18:19:20 +0800 Subject: [PATCH 06/89] Create Table Statement --- src/entity/create_stmt.rs | 151 ++++++++++++++++++++++++++++++++++++++ src/entity/mod.rs | 2 + 2 files changed, 153 insertions(+) create mode 100644 src/entity/create_stmt.rs diff --git a/src/entity/create_stmt.rs b/src/entity/create_stmt.rs new file mode 100644 index 00000000..581a8265 --- /dev/null +++ b/src/entity/create_stmt.rs @@ -0,0 +1,151 @@ +use crate::{ColumnDef as Sea_Orm_Column_Def, EntityTrait, PrimaryKeyTrait, RelationDef}; +use crate::entity::column::ColumnTrait; +use crate::entity::relation::RelationTrait; +use crate::entity::primary_key::PrimaryKeyToColumn; +use sea_query::{Alias, ColumnDef as Sea_Query_Column_Def, ColumnType as Sea_Query_Column_Type, ForeignKeyCreateStatement, IndexCreateStatement, SeaRc, TableCreateStatement, TableRef}; +pub use sea_strum::IntoEnumIterator; +pub trait CreateStatementOf +{ + fn create_table_statement_of(entity: E) -> TableCreateStatement where E: EntityTrait { + let mut stmt = TableCreateStatement::new(); + stmt.table(entity); + for relation in E::Relation::iter() { + let mut foreign_key_stmt = ForeignKeyCreateStatement::new(); + let relation_trait: RelationDef = relation.def(); + // foreign_key_stmt.name("Temp"); + match relation_trait.from_tbl { + TableRef::Table(tbl) => { foreign_key_stmt.from_tbl(tbl); }, + _ => todo!(), + } + match relation_trait.to_tbl { + TableRef::Table(tbl) => { foreign_key_stmt.to_tbl(tbl); }, + _ => todo!(), + } + match relation_trait.from_col { + crate::Identity::Unary(o1) => { + foreign_key_stmt.from_col(o1); + }, + crate::Identity::Binary(o1, o2) => { + foreign_key_stmt.from_col(o1); + foreign_key_stmt.from_col(o2); + }, + crate::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_trait.to_col { + crate::Identity::Unary(o1) => { + foreign_key_stmt.to_col(o1); + }, + crate::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); + }, + } + stmt.foreign_key(&mut foreign_key_stmt); + } + for col in E::Column::iter() { + let sea_orm_column_def: Sea_Orm_Column_Def = col.def().into(); + let mut index = IndexCreateStatement::new(); + let mut sea_query_column_def = Sea_Query_Column_Def::new(col); + for key in E::PrimaryKey::iter() { // enum: Id, Name ... + if sea_query_column_def.get_column_name() == Sea_Query_Column_Def::new(key.into_column()).get_column_name() { + sea_query_column_def.primary_key(); + if E::PrimaryKey::auto_increment() { + sea_query_column_def.auto_increment(); + } + index.primary(); + } + } + if !sea_orm_column_def.null { + sea_query_column_def.not_null(); + } + if sea_orm_column_def.unique { + sea_query_column_def.unique_key(); + index.unique(); + } + if sea_orm_column_def.indexed { + index.table(entity); + index.col(col); + stmt.index(&mut index); + } + match Sea_Query_Column_Type::from(sea_orm_column_def.col_type) { + Sea_Query_Column_Type::Char(length) => match length { + Some(length) => { sea_query_column_def.char_len(length); }, + None => { sea_query_column_def.char(); }, + }, + Sea_Query_Column_Type::String(length) => match length { + Some(length) => { sea_query_column_def.string_len(length); }, + None => { sea_query_column_def.string(); }, + }, + Sea_Query_Column_Type::Text => { sea_query_column_def.text(); }, + Sea_Query_Column_Type::TinyInteger(length) => match length { + Some(length) => { sea_query_column_def.tiny_integer_len(length); }, + None => { sea_query_column_def.tiny_integer(); }, + }, + // Sea_Query_Column_Type::TinyInteger => { sea_query_column_def.tiny_integer(); }, + Sea_Query_Column_Type::SmallInteger(length) => match length { + Some(length) => { sea_query_column_def.small_integer_len(length); }, + None => { sea_query_column_def.small_integer(); }, + }, + Sea_Query_Column_Type::Integer(length) => match length { + Some(length) => { sea_query_column_def.integer_len(length); }, + None => { sea_query_column_def.integer(); }, + }, + Sea_Query_Column_Type::BigInteger(length) => match length { + Some(length) => { sea_query_column_def.big_integer_len(length); }, + None => { sea_query_column_def.big_integer(); }, + }, + Sea_Query_Column_Type::Float(precision) => match precision { + Some(precision) => { sea_query_column_def.float_len(precision); }, + None => { sea_query_column_def.float(); }, + }, + Sea_Query_Column_Type::Double(precision) => match precision { + Some(precision) => { sea_query_column_def.double_len(precision); }, + None => { sea_query_column_def.double(); }, + }, + Sea_Query_Column_Type::Decimal(_) => { sea_query_column_def.decimal(); }, + Sea_Query_Column_Type::DateTime(precision) => match precision { + Some(precision) => { sea_query_column_def.date_time_len(precision); }, + None => { sea_query_column_def.date_time(); }, + }, + Sea_Query_Column_Type::Timestamp(precision) => match precision { + Some(precision) => { sea_query_column_def.timestamp_len(precision); }, + None => { sea_query_column_def.timestamp(); }, + }, + Sea_Query_Column_Type::Time(precision) => match precision { + Some(precision) => { sea_query_column_def.time_len(precision); }, + None => { sea_query_column_def.time(); }, + }, + Sea_Query_Column_Type::Date => { sea_query_column_def.date(); }, + Sea_Query_Column_Type::Binary(length) => match length { + Some(length) => { sea_query_column_def.binary_len(length); }, + None => { sea_query_column_def.binary(); }, + }, + Sea_Query_Column_Type::Boolean => { sea_query_column_def.boolean(); }, + Sea_Query_Column_Type::Money(_) => { sea_query_column_def.money(); }, + Sea_Query_Column_Type::Json => { sea_query_column_def.json(); }, + Sea_Query_Column_Type::JsonBinary => { sea_query_column_def.json_binary(); }, + Sea_Query_Column_Type::Custom(iden) => { sea_query_column_def.custom(Alias::new(&iden.to_string())); }, + Sea_Query_Column_Type::Uuid => { sea_query_column_def.uuid(); }, + Sea_Query_Column_Type::TimestampWithTimeZone(length) => match length { + Some(length) => { sea_query_column_def.timestamp_with_time_zone_len(length); }, + None => { sea_query_column_def.timestamp_with_time_zone(); }, + }, + } + stmt.col(&mut sea_query_column_def); + } + stmt.if_not_exists(); + + stmt + } +} + +impl CreateStatementOf for EntityTrait {} diff --git a/src/entity/mod.rs b/src/entity/mod.rs index 6da5e1b6..9d911d8c 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -6,6 +6,7 @@ mod model; pub mod prelude; mod primary_key; mod relation; +mod create_stmt; 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 create_stmt::*; From 31941d3af678eb46d510c31c941fa056d5d01015 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 23 Aug 2021 23:11:08 +0800 Subject: [PATCH 07/89] WIP: Linked (#89) --- src/entity/column.rs | 2 +- src/entity/model.rs | 9 ++++++- src/entity/prelude.rs | 6 ++--- src/entity/relation.rs | 16 ++++++++++++ src/query/join.rs | 55 +++++++++++++++++++++++++++++++++++++++++- src/tests_cfg/cake.rs | 15 ++++++++++++ 6 files changed, 97 insertions(+), 6 deletions(-) diff --git a/src/entity/column.rs b/src/entity/column.rs index 16546057..611950f5 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -1,6 +1,6 @@ -use std::str::FromStr; use crate::{EntityName, IdenStatic, Iterable}; use sea_query::{DynIden, Expr, SeaRc, SelectStatement, SimpleExpr, Value}; +use std::str::FromStr; #[derive(Debug, Clone)] pub struct ColumnDef { diff --git a/src/entity/model.rs b/src/entity/model.rs index 15ebdb58..2672d8cc 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -1,4 +1,4 @@ -use crate::{DbErr, EntityTrait, QueryFilter, QueryResult, Related, Select}; +use crate::{DbErr, EntityTrait, Linked, QueryFilter, QueryResult, Related, Select}; pub use sea_query::Value; use std::fmt::Debug; @@ -16,6 +16,13 @@ pub trait ModelTrait: Clone + Debug { { >::find_related().belongs_to(self) } + + fn find_linked(&self, _: L) -> Select + where + L: Linked, + { + L::find_linked() + } } pub trait FromQueryResult { diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs index 447117b7..cda217d9 100644 --- a/src/entity/prelude.rs +++ b/src/entity/prelude.rs @@ -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, ModelTrait, - PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult, Related, RelationDef, - RelationTrait, Select, Value, + DeriveModel, DerivePrimaryKey, EntityName, EntityTrait, EnumIter, Iden, IdenStatic, Linked, + ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult, Related, + RelationDef, RelationTrait, Select, Value, }; #[cfg(feature = "with-json")] diff --git a/src/entity/relation.rs b/src/entity/relation.rs index 955660e2..f75b3a89 100644 --- a/src/entity/relation.rs +++ b/src/entity/relation.rs @@ -28,6 +28,22 @@ where } } +pub trait Linked { + type FromEntity: EntityTrait; + + type ToEntity: EntityTrait; + + fn link() -> Vec; + + fn find_linked() -> Select { + let mut select = Select::new(); + for rel in Self::link() { + select = select.join(JoinType::InnerJoin, rel); + } + select + } +} + pub struct RelationDef { pub rel_type: RelationType, pub from_tbl: TableRef, diff --git a/src/query/join.rs b/src/query/join.rs index 72726d14..12999db8 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -1,4 +1,4 @@ -use crate::{EntityTrait, QuerySelect, Related, Select, SelectTwo, SelectTwoMany}; +use crate::{EntityTrait, Linked, QuerySelect, Related, Select, SelectTwo, SelectTwoMany}; pub use sea_query::JoinType; impl Select @@ -57,6 +57,19 @@ where { self.left_join(r).select_with(r) } + + /// Left Join with a Linked Entity and select both Entity. + pub fn find_also_linked(self, _: L) -> SelectTwo + where + L: Linked, + T: EntityTrait, + { + let mut slf = self; + for rel in L::link() { + slf = slf.join(JoinType::LeftJoin, rel); + } + slf.select_also(T::default()) + } } #[cfg(test)] @@ -220,4 +233,44 @@ mod tests { .join(" ") ); } + + #[test] + fn join_10() { + let cake_model = cake::Model { + id: 12, + name: "".to_owned(), + }; + + assert_eq!( + cake_model + .find_linked(cake::CakeToFilling) + .build(DbBackend::MySql) + .to_string(), + [ + r#"SELECT `filling`.`id`, `filling`.`name`"#, + r#"FROM `filling`"#, + r#"INNER JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id`"#, + r#"INNER JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id`"#, + ] + .join(" ") + ); + } + + #[test] + fn join_11() { + assert_eq!( + cake::Entity::find() + .find_also_linked(cake::CakeToFilling) + .build(DbBackend::MySql) + .to_string(), + [ + r#"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,"#, + r#"`filling`.`id` AS `B_id`, `filling`.`name` AS `B_name`"#, + r#"FROM `cake`"#, + r#"LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id`"#, + r#"LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id`"#, + ] + .join(" ") + ); + } } diff --git a/src/tests_cfg/cake.rs b/src/tests_cfg/cake.rs index f8a35d6c..56ca8797 100644 --- a/src/tests_cfg/cake.rs +++ b/src/tests_cfg/cake.rs @@ -73,4 +73,19 @@ impl Related for Entity { } } +pub struct CakeToFilling; + +impl Linked for CakeToFilling { + type FromEntity = Entity; + + type ToEntity = super::filling::Entity; + + fn link() -> Vec { + vec![ + super::cake_filling::Relation::Cake.def().rev(), + super::cake_filling::Relation::Filling.def(), + ] + } +} + impl ActiveModelBehavior for ActiveModel {} From cd9793417bbbb984c9feffdcb2001d5bf4b98647 Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Wed, 25 Aug 2021 22:17:11 +1000 Subject: [PATCH 08/89] WIP Runs --- examples/rocket_example/Cargo.toml | 2 ++ examples/rocket_example/src/main.rs | 3 --- examples/rocket_example/src/sqlx/setup.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/rocket_example/Cargo.toml b/examples/rocket_example/Cargo.toml index d6293676..d7c05921 100644 --- a/examples/rocket_example/Cargo.toml +++ b/examples/rocket_example/Cargo.toml @@ -29,4 +29,6 @@ features = ["macros", "offline", "migrate"] # features = ["sea_orm"] [dependencies.rocket_db_pools] path = "../../../Rocket/contrib/db_pools/lib" +# git = "https://github.com/samsamai/Rocket.git" +# branch = "ss/seaorm-contrib" features = ["seaorm_sqlx_sqlite"] diff --git a/examples/rocket_example/src/main.rs b/examples/rocket_example/src/main.rs index 18fc60b9..f22b2ca1 100644 --- a/examples/rocket_example/src/main.rs +++ b/examples/rocket_example/src/main.rs @@ -3,9 +3,6 @@ extern crate rocket; #[macro_use] extern crate rocket_db_pools; -#[cfg(test)] -mod tests; - mod sqlx; #[launch] diff --git a/examples/rocket_example/src/sqlx/setup.rs b/examples/rocket_example/src/sqlx/setup.rs index fb4882a5..b7ba2df3 100644 --- a/examples/rocket_example/src/sqlx/setup.rs +++ b/examples/rocket_example/src/sqlx/setup.rs @@ -1,6 +1,6 @@ use sea_orm::{error::*, sea_query, DbConn, ExecResult}; -use sea_query::{ColumnDef, ForeignKey, ForeignKeyAction, Index, TableCreateStatement}; +use sea_orm::sea_query::{ColumnDef, ForeignKey, ForeignKeyAction, Index, TableCreateStatement}; // mod post; pub use super::post::*; From f6994477eb5345e0e48459e3ad1463f95c040c4b Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Wed, 25 Aug 2021 22:42:30 +1000 Subject: [PATCH 09/89] DatabaseBackend::Sqlite -> DatabaseBackend::MySql --- examples/rocket_example/src/sqlx/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index 3229aa98..f6e16a0d 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -102,7 +102,7 @@ async fn list(mut con: Connection) -> Result>> { let all_posts = con .query_all(Statement::from_string( - DatabaseBackend::Sqlite, + DatabaseBackend::MySql, "select * from posts;".to_owned(), )) .await @@ -214,7 +214,7 @@ pub fn stage() -> AdHoc { // .await; let create_post_table = con .execute(Statement::from_string( - DatabaseBackend::Sqlite, + DatabaseBackend::MySql, r#" CREATE TABLE posts ( id int NOT NULL AUTO_INCREMENT, @@ -229,7 +229,7 @@ pub fn stage() -> AdHoc { let create_post = con .execute(Statement::from_string( - DatabaseBackend::Sqlite, + DatabaseBackend::MySql, "INSERT INTO posts (title, text) VALUES ('a post', 'content of a post')" .to_owned(), )) From 87d4f09931df6210e8745a2235ac1cc86303c053 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 25 Aug 2021 23:05:30 +0800 Subject: [PATCH 10/89] Add rocket-mysql driver --- Cargo.toml | 3 ++ examples/rocket_example/Cargo.toml | 19 ++------- examples/rocket_example/src/sqlx/setup.rs | 48 +++++++++++------------ src/driver/mod.rs | 5 +++ src/driver/rocket_mysql.rs | 39 ++++++++++++++++++ src/entity/column.rs | 2 +- 6 files changed, 75 insertions(+), 41 deletions(-) create mode 100644 src/driver/rocket_mysql.rs diff --git a/Cargo.toml b/Cargo.toml index ca61c819..bc90db55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,8 @@ sqlx-core = { version = "^0.5", optional = true } sqlx-macros = { version = "^0.5", optional = true } serde_json = { version = "^1", optional = true } uuid = { version = "0.8", features = ["serde", "v4"], optional = true } +rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = ["json"], optional = true } +rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", features = ["sqlx_mysql"], optional = true } [dev-dependencies] smol = { version = "^1.2" } @@ -91,3 +93,4 @@ runtime-actix-rustls = ["sqlx/runtime-actix-rustls", "runtime-actix"] runtime-tokio = [] runtime-tokio-native-tls = ["sqlx/runtime-tokio-native-tls", "runtime-tokio"] runtime-tokio-rustls = ["sqlx/runtime-tokio-rustls", "runtime-tokio"] +rocket-mysql = ["rocket", "rocket_db_pools"] diff --git a/examples/rocket_example/Cargo.toml b/examples/rocket_example/Cargo.toml index d7c05921..feb77d43 100644 --- a/examples/rocket_example/Cargo.toml +++ b/examples/rocket_example/Cargo.toml @@ -5,12 +5,9 @@ edition = "2018" publish = false [workspace] [dependencies] -rocket = { path = "../../../Rocket/core/lib", features = ["json"] } -# rocket = { git = "https://github.com/SergioBenitez/Rocket.git", branch = "master", features = [ -# "json", -# ] } -# async-std = { version = "^1.9", features = ["attributes"] } -sea-orm = { path = "../../", features = ["sqlx-all"] } +rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = ["json"] } +rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", features = ["sqlx_mysql"] } +sea-orm = { path = "../../", features = ["sqlx-all", "rocket-mysql"] } sea-query = { version = "^0.12.8" } serde_json = { version = "^1" } @@ -22,13 +19,3 @@ futures-util = { version = "^0.3" } version = "0.5.1" default-features = false features = ["macros", "offline", "migrate"] - -# [dependencies.rocket_db_pools] -# git = "https://github.com/SergioBenitez/Rocket" -# branch = "master" -# features = ["sea_orm"] -[dependencies.rocket_db_pools] -path = "../../../Rocket/contrib/db_pools/lib" -# git = "https://github.com/samsamai/Rocket.git" -# branch = "ss/seaorm-contrib" -features = ["seaorm_sqlx_sqlite"] diff --git a/examples/rocket_example/src/sqlx/setup.rs b/examples/rocket_example/src/sqlx/setup.rs index b7ba2df3..fac405be 100644 --- a/examples/rocket_example/src/sqlx/setup.rs +++ b/examples/rocket_example/src/sqlx/setup.rs @@ -6,32 +6,32 @@ use sea_orm::sea_query::{ColumnDef, ForeignKey, ForeignKeyAction, Index, TableCr pub use super::post::*; async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result { - let builder = db.get_database_backend(); - db.execute(builder.build(stmt)).await + let builder = db.get_database_backend(); + db.execute(builder.build(stmt)).await } pub async fn create_post_table(db: &DbConn) -> Result { - let stmt = sea_query::Table::create() - .table(super::post::Entity) - .if_not_exists() - .col( - ColumnDef::new(super::post::Column::Id) - .integer() - .not_null() - .auto_increment() - .primary_key(), - ) - .col( - ColumnDef::new(super::post::Column::Title) - .string() - .not_null(), - ) - .col( - ColumnDef::new(super::post::Column::Text) - .string() - .not_null(), - ) - .to_owned(); + let stmt = sea_query::Table::create() + .table(super::post::Entity) + .if_not_exists() + .col( + ColumnDef::new(super::post::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col( + ColumnDef::new(super::post::Column::Title) + .string() + .not_null(), + ) + .col( + ColumnDef::new(super::post::Column::Text) + .string() + .not_null(), + ) + .to_owned(); - create_table(db, &stmt).await + create_table(db, &stmt).await } diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 6f6cfb64..5786d299 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -9,8 +9,13 @@ mod sqlx_postgres; #[cfg(feature = "sqlx-sqlite")] mod sqlx_sqlite; +#[cfg(feature = "rocket-mysql")] +mod rocket_mysql; + #[cfg(feature = "mock")] pub use mock::*; +#[cfg(feature = "rocket-mysql")] +pub use rocket_mysql::*; #[cfg(feature = "sqlx-dep")] pub use sqlx_common::*; #[cfg(feature = "sqlx-mysql")] diff --git a/src/driver/rocket_mysql.rs b/src/driver/rocket_mysql.rs new file mode 100644 index 00000000..ef90f1ee --- /dev/null +++ b/src/driver/rocket_mysql.rs @@ -0,0 +1,39 @@ +use rocket::figment::Figment; +use rocket_db_pools::{Config, Error}; + +#[rocket::async_trait] +impl rocket_db_pools::Pool for crate::Database { + type Error = crate::DbErr; + + type Connection = crate::DatabaseConnection; + + async fn init(figment: &Figment) -> Result { + // let config = figment.extract::()?; + // let mut opts = config.url.parse::>().map_err(Error::Init)?; + // opts.disable_statement_logging(); + // specialize(&mut opts, &config); + + // sqlx::pool::PoolOptions::new() + // .max_connections(config.max_connections as u32) + // .connect_timeout(Duration::from_secs(config.connect_timeout)) + // .idle_timeout(config.idle_timeout.map(Duration::from_secs)) + // .min_connections(config.min_connections.unwrap_or_default()) + // .connect_with(opts) + // .await + // .map_err(Error::Init) + Ok(crate::Database {}) + } + + async fn get(&self) -> Result { + // self.acquire().await.map_err(Error::Get) + // let con = crate::Database::connect("sqlite::memory:").await; + + // Ok(crate::Database::connect("sqlite::memory:").await.unwrap()) + // "mysql://root:@localhost" + Ok( + crate::Database::connect("mysql://root:@localhost/rocket_example") + .await + .unwrap(), + ) + } +} diff --git a/src/entity/column.rs b/src/entity/column.rs index 16546057..611950f5 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -1,6 +1,6 @@ -use std::str::FromStr; use crate::{EntityName, IdenStatic, Iterable}; use sea_query::{DynIden, Expr, SeaRc, SelectStatement, SimpleExpr, Value}; +use std::str::FromStr; #[derive(Debug, Clone)] pub struct ColumnDef { From 30ca917316bc990b3f0cbbb364a33f89770fc3a3 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 25 Aug 2021 23:20:30 +0800 Subject: [PATCH 11/89] Update Workflow --- .github/workflows/rust.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2aa1f90d..380e1b35 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -89,6 +89,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] + path: [async-std, tokio, rocket_example] steps: - uses: actions/checkout@v2 @@ -102,13 +103,7 @@ jobs: with: command: build args: > - --manifest-path examples/async-std/Cargo.toml - - - uses: actions-rs/cargo@v1 - with: - command: build - args: > - --manifest-path examples/tokio/Cargo.toml + --manifest-path examples/${{ matrix.path }}/Cargo.toml sqlite: name: SQLite From 9565a2e9997621a666e3986e5b6187af5fa342d1 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 25 Aug 2021 23:52:38 +0800 Subject: [PATCH 12/89] Update Workflow --- .github/workflows/rust.yml | 81 +++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 380e1b35..03bbe23b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -11,12 +11,11 @@ env: jobs: - compile: - name: Compile + compile-sqlite: + name: Compile SQLite runs-on: ubuntu-20.04 strategy: matrix: - database: [sqlite, mysql, postgres] runtime: [async-std, actix, tokio] tls: [native-tls, rustls] steps: @@ -35,13 +34,77 @@ jobs: ~/.cargo/git Cargo.lock target - key: ${{ github.sha }}-${{ github.run_id }}-${{ runner.os }}-${{ matrix.database }}-${{ matrix.runtime }}-${{ matrix.tls }} + key: ${{ github.sha }}-${{ github.run_id }}-${{ runner.os }}-sqlite-${{ matrix.runtime }}-${{ matrix.tls }} - uses: actions-rs/cargo@v1 with: command: test args: > - --features default,sqlx-${{ matrix.database }},runtime-${{ matrix.runtime }}-${{ matrix.tls }} + --features default,sqlx-sqlite,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + --no-run + + compile-mysql: + name: Compile MySQL + runs-on: ubuntu-20.04 + strategy: + matrix: + runtime: [async-std, actix, tokio] + tls: [native-tls, rustls] + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + Cargo.lock + target + key: ${{ github.sha }}-${{ github.run_id }}-${{ runner.os }}-mysql-${{ matrix.runtime }}-${{ matrix.tls }} + + - uses: actions-rs/cargo@v1 + with: + command: test + args: > + --features default,sqlx-mysql,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + --no-run + + compile-postgres: + name: Compile PostgreSQL + runs-on: ubuntu-20.04 + strategy: + matrix: + runtime: [async-std, actix, tokio] + tls: [native-tls, rustls] + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + Cargo.lock + target + key: ${{ github.sha }}-${{ github.run_id }}-${{ runner.os }}-postgres-${{ matrix.runtime }}-${{ matrix.tls }} + + - uses: actions-rs/cargo@v1 + with: + command: test + args: > + --features default,sqlx-postgres,runtime-${{ matrix.runtime }}-${{ matrix.tls }} --no-run test: @@ -108,7 +171,7 @@ jobs: sqlite: name: SQLite runs-on: ubuntu-20.04 - needs: compile + needs: compile-sqlite env: DATABASE_URL: "sqlite::memory:" strategy: @@ -142,7 +205,7 @@ jobs: mysql: name: MySQL runs-on: ubuntu-20.04 - needs: compile + needs: compile-mysql env: DATABASE_URL: "mysql://root:@localhost" strategy: @@ -194,7 +257,7 @@ jobs: mariadb: name: MariaDB runs-on: ubuntu-20.04 - needs: compile + needs: compile-mysql env: DATABASE_URL: "mysql://root:@localhost" strategy: @@ -246,7 +309,7 @@ jobs: postgres: name: Postgres runs-on: ubuntu-20.04 - needs: compile + needs: compile-postgres env: DATABASE_URL: "postgres://root:root@localhost" strategy: From d80ea396abce30044e78b7e71a223d8999858436 Mon Sep 17 00:00:00 2001 From: Bobby Ng Date: Thu, 26 Aug 2021 18:15:29 +0800 Subject: [PATCH 13/89] test1 --- src/entity/create_stmt.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/entity/create_stmt.rs b/src/entity/create_stmt.rs index 581a8265..fbf77c02 100644 --- a/src/entity/create_stmt.rs +++ b/src/entity/create_stmt.rs @@ -149,3 +149,24 @@ pub trait CreateStatementOf } impl CreateStatementOf for EntityTrait {} + +#[cfg(test)] +mod tests { + + use crate::{CreateStatementOf, tests_cfg}; + + #[test] + fn test_create_statement_tests_cfg_cake() { + let create_statement = tests_cfg::cake::Entity::create_table_statement_of(tests_cfg::cake::Entity); + let table = format!("{:?}", create_statement.get_table_name()); + let columns = format!("{:?}", create_statement.get_columns()); + let relations = format!("{:?}", create_statement.get_foreign_key_create_stmts()); + let indexs = format!("{:?}", create_statement.get_indexes()); + let result = format!("{:?}", create_statement); + assert_eq!("TableCreateStatement { table: Some(cake), columns: [ColumnDef { table: Some(cake), name: id, types: Some(Integer(None)), spec: [PrimaryKey, AutoIncrement, NotNull] }, ColumnDef { table: Some(cake), name: name, types: Some(String(None)), spec: [NotNull] }], options: [], partitions: [], indexes: [], foreign_keys: [ForeignKeyCreateStatement { foreign_key: TableForeignKey { name: None, table: Some(cake), ref_table: Some(fruit), columns: [id], ref_columns: [cake_id], on_delete: None, on_update: None } }], if_not_exists: true }", result); + assert_eq!(r#"Some("cake")"#, table); + assert_eq!("[ForeignKeyCreateStatement { foreign_key: TableForeignKey { name: None, table: Some(cake), ref_table: Some(fruit), columns: [id], ref_columns: [cake_id], on_delete: None, on_update: None } }]", relations); + assert_eq!(r#"[ColumnDef { table: Some(cake), name: id, types: Some(Integer(None)), spec: [PrimaryKey, AutoIncrement, NotNull] }, ColumnDef { table: Some(cake), name: name, types: Some(String(None)), spec: [NotNull] }]"#, columns); + assert_eq!("[]", indexs); + } +} From 5adecc9088cc89383838bcd623e0606829462f7f Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Thu, 26 Aug 2021 21:42:50 +1000 Subject: [PATCH 14/89] List post ids #[get("/")] --- examples/rocket_example/src/sqlx/mod.rs | 75 ++---------------------- examples/rocket_example/src/sqlx/post.rs | 2 +- 2 files changed, 7 insertions(+), 70 deletions(-) diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index f6e16a0d..39f11241 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -49,77 +49,14 @@ use sea_orm::DatabaseConnection; #[get("/")] async fn list(mut con: Connection) -> Result>> { - // let ids = sqlx::query!("SELECT id FROM posts") - // .fetch(&mut *db) - // .map_ok(|record| record.id) - // .try_collect::>() - // .await?; - // // let ids: Vec = vec![]; - - // let ids = sqlx::query( - // r#" - // SELECT id FROM posts - // "#, - // ) - // .execute(&mut *db) - // .await?; - // // .map_ok(|record| record.id); - // // .try_collect::>(); - // println!("ids: {:#?}", ids); - - // let ids: Vec = vec![]; - // Ok(Json(ids)) - - // let mut conn = db.acquire().await?; - // println!("conn: {:#?}", conn); - - // let ids = sqlx::query("SELECT id FROM posts") - // .fetch(&mut *db) - // .map_ok(|record| record.id) - // .try_collect::>() - // .await?; - - // Ok(Json(ids)) - - // let recs = sqlx::query( - // r#" - // SELECT id FROM posts - // "#, - // ) - // .fetch_all(&mut *db) - // .await?; - // let ids: Vec = recs.into(); - - // println!("recs: {:#?}", ids); - // println!("db: {:#?}", &*db); - // let res = db - // .execute(Statement::from_string( - // DatabaseBackend::Sqlite, - // "SELECT * from posts".to_owned(), - // )) - // .await; - // println!("res: {:#?}", res); - - let all_posts = con - .query_all(Statement::from_string( - DatabaseBackend::MySql, - "select * from posts;".to_owned(), - )) + let ids = Post::find() + .all(&con) .await - .unwrap(); - for post in all_posts.into_iter() { - // let p = Post::from_raw_query_result(post); - println!( - "p: {:#?}", - sea_orm::JsonValue::from_query_result(&post, "").unwrap() - ); - } + .expect("could not retrieve posts") + .into_iter() + .map(|record| record.id.unwrap()) + .collect::>(); - // let con = SqlxSqliteConnector::from_sqlx_sqlite_pool(db); - // let posts = Post::find().all(&con).await.unwrap(); - // assert_eq!(posts.len(), 0); - - let ids: Vec = vec![]; Ok(Json(ids)) } diff --git a/examples/rocket_example/src/sqlx/post.rs b/examples/rocket_example/src/sqlx/post.rs index 96f694ac..58caf669 100644 --- a/examples/rocket_example/src/sqlx/post.rs +++ b/examples/rocket_example/src/sqlx/post.rs @@ -7,7 +7,7 @@ pub struct Entity; impl EntityName for Entity { fn table_name(&self) -> &str { - "post" + "posts" } } From a92260270084eca48bee948c962dab943fd2a30a Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Thu, 26 Aug 2021 22:07:38 +1000 Subject: [PATCH 15/89] Show post #[get("/")] --- examples/rocket_example/src/sqlx/mod.rs | 28 ++++++++----------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index 39f11241..fe089ba4 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -61,26 +61,16 @@ async fn list(mut con: Connection) -> Result>> { } #[get("/")] -async fn read(mut db: Connection, id: i64) -> Option> { - // let post: Option = Post::find_by_id(id) - // .one(db) - // .await - // .expect("could not find baker"); - // println!("post: {:#?}", post); +async fn read(mut con: Connection, id: i64) -> Option> { + let post: Option = Post::find_by_id(id) + .one(&con) + .await + .expect("could not find post"); - // sqlx::query!("SELECT id, title, text FROM posts WHERE id = ?", id) - // .fetch_one(&mut *db) - // .map_ok(|r| { - // Json(Post { - // id: Some(r.id), - // title: r.title, - // text: r.text, - // }) - // }) - // .await - // .ok() - - None + match post { + None => None, + Some(post) => Some(Json(post)), + } } // #[delete("/")] From c880ca57327cfd497409efa350289cbe1e5b94ab Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Thu, 26 Aug 2021 22:28:17 +1000 Subject: [PATCH 16/89] Delete post #[delete("/")] --- examples/rocket_example/src/sqlx/mod.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index fe089ba4..c74244c2 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -73,14 +73,18 @@ async fn read(mut con: Connection, id: i64) -> Option> { } } -// #[delete("/")] -// async fn delete(mut db: Connection, id: i64) -> Result> { -// let result = sqlx::query!("DELETE FROM posts WHERE id = ?", id) -// .execute(&mut *db) -// .await?; +#[delete("/")] +async fn delete(mut conn: Connection, id: i64) -> Result> { + let post: post::ActiveModel = Post::find_by_id(id) + .one(&conn) + .await + .unwrap() + .unwrap() + .into(); + let result = post.delete(&conn).await.unwrap(); -// Ok((result.rows_affected() == 1).then(|| ())) -// } + Ok((result.rows_affected == 1).then(|| ())) +} // #[delete("/")] // async fn destroy(mut db: Connection) -> Result<()> { @@ -203,7 +207,7 @@ pub fn stage() -> AdHoc { // None => Err(rocket), // } })) - .mount("/sqlx", routes![list, read]) + .mount("/sqlx", routes![list, read, delete]) }) } From 447947e3556c36ec441e844fd8e0db7bfa29d311 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 26 Aug 2021 23:28:27 +0800 Subject: [PATCH 17/89] Fix find_linked join direction --- src/entity/relation.rs | 4 ++-- src/query/join.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/entity/relation.rs b/src/entity/relation.rs index f75b3a89..441a0524 100644 --- a/src/entity/relation.rs +++ b/src/entity/relation.rs @@ -37,8 +37,8 @@ pub trait Linked { fn find_linked() -> Select { let mut select = Select::new(); - for rel in Self::link() { - select = select.join(JoinType::InnerJoin, rel); + for rel in Self::link().into_iter().rev() { + select = select.join_rev(JoinType::InnerJoin, rel); } select } diff --git a/src/query/join.rs b/src/query/join.rs index 12999db8..be759e97 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -249,8 +249,8 @@ mod tests { [ r#"SELECT `filling`.`id`, `filling`.`name`"#, r#"FROM `filling`"#, - r#"INNER JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id`"#, - r#"INNER JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id`"#, + r#"INNER JOIN `cake_filling` ON `cake_filling`.`filling_id` = `filling`.`id`"#, + r#"INNER JOIN `cake` ON `cake`.`id` = `cake_filling`.`cake_id`"#, ] .join(" ") ); From bfaa7d4539afc033cb3f240fa4fed4ff1344c7c0 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 26 Aug 2021 23:28:44 +0800 Subject: [PATCH 18/89] Test Linked --- tests/common/bakery_chain/baker.rs | 18 +++ tests/relational_tests.rs | 202 ++++++++++++++++++++++++++++- 2 files changed, 219 insertions(+), 1 deletion(-) diff --git a/tests/common/bakery_chain/baker.rs b/tests/common/bakery_chain/baker.rs index 0c63e721..287e5deb 100644 --- a/tests/common/bakery_chain/baker.rs +++ b/tests/common/bakery_chain/baker.rs @@ -81,4 +81,22 @@ impl Related for Entity { } } +pub struct BakedForCustomer; + +impl Linked for BakedForCustomer { + type FromEntity = Entity; + + type ToEntity = super::customer::Entity; + + fn link() -> Vec { + vec![ + super::cakes_bakers::Relation::Baker.def().rev(), + super::cakes_bakers::Relation::Cake.def(), + super::lineitem::Relation::Cake.def().rev(), + super::lineitem::Relation::Order.def(), + super::order::Relation::Customer.def(), + ] + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/tests/relational_tests.rs b/tests/relational_tests.rs index ae1236a2..1ddee902 100644 --- a/tests/relational_tests.rs +++ b/tests/relational_tests.rs @@ -1,7 +1,8 @@ use chrono::offset::Utc; use rust_decimal::prelude::*; use rust_decimal_macros::dec; -use sea_orm::{entity::*, query::*, FromQueryResult}; +use sea_orm::{entity::*, query::*, DbErr, FromQueryResult}; +use uuid::Uuid; pub mod common; pub use common::{bakery_chain::*, setup::*, TestContext}; @@ -474,3 +475,202 @@ pub async fn having() { ctx.delete().await; } + +#[sea_orm_macros::test] +#[cfg(any( + feature = "sqlx-mysql", + feature = "sqlx-sqlite", + feature = "sqlx-postgres" +))] +pub async fn linked() -> Result<(), DbErr> { + use common::bakery_chain::Order; + + let ctx = TestContext::new("test_linked").await; + + // SeaSide Bakery + let seaside_bakery = bakery::ActiveModel { + name: Set("SeaSide Bakery".to_owned()), + profit_margin: Set(10.4), + ..Default::default() + }; + let seaside_bakery_res: InsertResult = Bakery::insert(seaside_bakery).exec(&ctx.db).await?; + + // Bob's Baker, Cake & Cake Baker + let baker_bob = baker::ActiveModel { + name: Set("Baker Bob".to_owned()), + contact_details: Set(serde_json::json!({ + "mobile": "+61424000000", + "home": "0395555555", + "address": "12 Test St, Testville, Vic, Australia" + })), + bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), + ..Default::default() + }; + let baker_bob_res: InsertResult = Baker::insert(baker_bob).exec(&ctx.db).await?; + let mud_cake = cake::ActiveModel { + name: Set("Mud Cake".to_owned()), + price: Set(dec!(10.25)), + gluten_free: Set(false), + serial: Set(Uuid::new_v4()), + bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), + ..Default::default() + }; + let mud_cake_res: InsertResult = Cake::insert(mud_cake).exec(&ctx.db).await?; + let bob_cakes_bakers = cakes_bakers::ActiveModel { + cake_id: Set(mud_cake_res.last_insert_id as i32), + baker_id: Set(baker_bob_res.last_insert_id as i32), + ..Default::default() + }; + CakesBakers::insert(bob_cakes_bakers).exec(&ctx.db).await?; + + // Bobby's Baker, Cake & Cake Baker + let baker_bobby = baker::ActiveModel { + name: Set("Baker Bobby".to_owned()), + contact_details: Set(serde_json::json!({ + "mobile": "+85212345678", + })), + bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), + ..Default::default() + }; + let baker_bobby_res: InsertResult = Baker::insert(baker_bobby).exec(&ctx.db).await?; + let cheese_cake = cake::ActiveModel { + name: Set("Cheese Cake".to_owned()), + price: Set(dec!(20.5)), + gluten_free: Set(false), + serial: Set(Uuid::new_v4()), + bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), + ..Default::default() + }; + let cheese_cake_res: InsertResult = Cake::insert(cheese_cake).exec(&ctx.db).await?; + let bobby_cakes_bakers = cakes_bakers::ActiveModel { + cake_id: Set(cheese_cake_res.last_insert_id as i32), + baker_id: Set(baker_bobby_res.last_insert_id as i32), + ..Default::default() + }; + CakesBakers::insert(bobby_cakes_bakers) + .exec(&ctx.db) + .await?; + let chocolate_cake = cake::ActiveModel { + name: Set("Chocolate Cake".to_owned()), + price: Set(dec!(30.15)), + gluten_free: Set(false), + serial: Set(Uuid::new_v4()), + bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), + ..Default::default() + }; + let chocolate_cake_res: InsertResult = Cake::insert(chocolate_cake).exec(&ctx.db).await?; + let bobby_cakes_bakers = cakes_bakers::ActiveModel { + cake_id: Set(chocolate_cake_res.last_insert_id as i32), + baker_id: Set(baker_bobby_res.last_insert_id as i32), + ..Default::default() + }; + CakesBakers::insert(bobby_cakes_bakers) + .exec(&ctx.db) + .await?; + + // Kate's Customer, Order & Line Item + let customer_kate = customer::ActiveModel { + name: Set("Kate".to_owned()), + notes: Set(Some("Loves cheese cake".to_owned())), + ..Default::default() + }; + let customer_kate_res: InsertResult = Customer::insert(customer_kate).exec(&ctx.db).await?; + let kate_order_1 = order::ActiveModel { + bakery_id: Set(seaside_bakery_res.last_insert_id as i32), + customer_id: Set(customer_kate_res.last_insert_id as i32), + total: Set(dec!(15.10)), + placed_at: Set(Utc::now().naive_utc()), + ..Default::default() + }; + let kate_order_1_res: InsertResult = Order::insert(kate_order_1).exec(&ctx.db).await?; + lineitem::ActiveModel { + cake_id: Set(cheese_cake_res.last_insert_id as i32), + order_id: Set(kate_order_1_res.last_insert_id as i32), + price: Set(dec!(7.55)), + quantity: Set(2), + ..Default::default() + } + .save(&ctx.db) + .await?; + let kate_order_2 = order::ActiveModel { + bakery_id: Set(seaside_bakery_res.last_insert_id as i32), + customer_id: Set(customer_kate_res.last_insert_id as i32), + total: Set(dec!(29.7)), + placed_at: Set(Utc::now().naive_utc()), + ..Default::default() + }; + let kate_order_2_res: InsertResult = Order::insert(kate_order_2).exec(&ctx.db).await?; + lineitem::ActiveModel { + cake_id: Set(chocolate_cake_res.last_insert_id as i32), + order_id: Set(kate_order_2_res.last_insert_id as i32), + price: Set(dec!(9.9)), + quantity: Set(3), + ..Default::default() + } + .save(&ctx.db) + .await?; + + // Kara's Customer, Order & Line Item + let customer_kara = customer::ActiveModel { + name: Set("Kara".to_owned()), + notes: Set(Some("Loves all cakes".to_owned())), + ..Default::default() + }; + let customer_kara_res: InsertResult = Customer::insert(customer_kara).exec(&ctx.db).await?; + let kara_order_1 = order::ActiveModel { + bakery_id: Set(seaside_bakery_res.last_insert_id as i32), + customer_id: Set(customer_kara_res.last_insert_id as i32), + total: Set(dec!(15.10)), + placed_at: Set(Utc::now().naive_utc()), + ..Default::default() + }; + let kara_order_1_res: InsertResult = Order::insert(kara_order_1).exec(&ctx.db).await?; + lineitem::ActiveModel { + cake_id: Set(mud_cake_res.last_insert_id as i32), + order_id: Set(kara_order_1_res.last_insert_id as i32), + price: Set(dec!(7.55)), + quantity: Set(2), + ..Default::default() + } + .save(&ctx.db) + .await?; + let kara_order_2 = order::ActiveModel { + bakery_id: Set(seaside_bakery_res.last_insert_id as i32), + customer_id: Set(customer_kara_res.last_insert_id as i32), + total: Set(dec!(29.7)), + placed_at: Set(Utc::now().naive_utc()), + ..Default::default() + }; + let kara_order_2_res: InsertResult = Order::insert(kara_order_2).exec(&ctx.db).await?; + lineitem::ActiveModel { + cake_id: Set(cheese_cake_res.last_insert_id as i32), + order_id: Set(kara_order_2_res.last_insert_id as i32), + price: Set(dec!(9.9)), + quantity: Set(3), + ..Default::default() + } + .save(&ctx.db) + .await?; + + /* + SELECT `baker`.`id` AS `A_id`, `baker`.`name` AS `A_name`, + `baker`.`contact_details` AS `A_contact_details`, + `baker`.`bakery_id` AS `A_bakery_id`, `customer`.`id` AS `B_id`, + `customer`.`name` AS `B_name`, `customer`.`notes` AS `B_notes` + FROM `baker` + LEFT JOIN `cakes_bakers` ON `baker`.`id` = `cakes_bakers`.`baker_id` + LEFT JOIN `cake` ON `cakes_bakers`.`cake_id` = `cake`.`id` + LEFT JOIN `lineitem` ON `cake`.`id` = `lineitem`.`cake_id` + LEFT JOIN `order` ON `lineitem`.`order_id` = `order`.`id` + LEFT JOIN `customer` ON `order`.`customer_id` = `customer`.`id` + */ + let baked_for_customers = Baker::find() + .find_also_linked(baker::BakedForCustomer) + .all(&ctx.db) + .await?; + println!("{:#?}", baked_for_customers); + + ctx.delete().await; + + Ok(()) +} From 11c1e4e399097f2fc222488eedd936d2fd3fedb8 Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Fri, 27 Aug 2021 21:39:44 +1000 Subject: [PATCH 19/89] Destroy posts #[delete("/")] --- examples/rocket_example/src/sqlx/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index c74244c2..0d2fd30e 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -86,12 +86,11 @@ async fn delete(mut conn: Connection, id: i64) -> Result> { Ok((result.rows_affected == 1).then(|| ())) } -// #[delete("/")] -// async fn destroy(mut db: Connection) -> Result<()> { -// sqlx::query!("DELETE FROM posts").execute(&mut *db).await?; - -// Ok(()) -// } +#[delete("/")] +async fn destroy(mut conn: Connection) -> Result<()> { + let _result = Post::delete_many().exec(&conn).await.unwrap(); + Ok(()) +} // async fn run_migrations(rocket: Rocket) -> fairing::Result { // use crate::rocket_db_pools::Pool; @@ -207,7 +206,7 @@ pub fn stage() -> AdHoc { // None => Err(rocket), // } })) - .mount("/sqlx", routes![list, read, delete]) + .mount("/sqlx", routes![list, read, delete, destroy]) }) } From 41d25225e2847652b0fb4a1a6f5b3409952e75ae Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Fri, 27 Aug 2021 22:20:46 +1000 Subject: [PATCH 20/89] Create a post #[post("/", data = "")] --- examples/rocket_example/src/main.rs | 2 - examples/rocket_example/src/sqlx/mod.rs | 61 ++++++++++------------- examples/rocket_example/src/sqlx/post.rs | 2 +- examples/rocket_example/src/sqlx/setup.rs | 50 +++++++++---------- 4 files changed, 51 insertions(+), 64 deletions(-) diff --git a/examples/rocket_example/src/main.rs b/examples/rocket_example/src/main.rs index f22b2ca1..774f58a6 100644 --- a/examples/rocket_example/src/main.rs +++ b/examples/rocket_example/src/main.rs @@ -1,7 +1,5 @@ #[macro_use] extern crate rocket; -#[macro_use] -extern crate rocket_db_pools; mod sqlx; diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index 0d2fd30e..2909ed9d 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -1,15 +1,12 @@ -use rocket::fairing::{self, AdHoc}; +use rocket::fairing::AdHoc; use rocket::response::status::Created; -use rocket::serde::{json::Json, Deserialize, Serialize}; +use rocket::serde::json::Json; use rocket::{futures, Build, Rocket}; use rocket_db_pools::{sqlx, Connection, Database}; -use futures::{future::TryFutureExt, stream::TryStreamExt}; use sea_orm::entity::*; -use sea_orm::{ - DatabaseBackend, QueryFilter, SqlxSqliteConnector, SqlxSqlitePoolConnection, Statement, -}; +use sea_orm::{DatabaseBackend, Statement}; mod setup; @@ -19,38 +16,30 @@ struct Db(sea_orm::Database); type Result> = std::result::Result; -// use post::*; mod post; pub use post::Entity as Post; -use sea_orm::DatabaseConnection; -// #[derive(Debug, Clone, Deserialize, Serialize)] -// #[serde(crate = "rocket::serde")] -// struct Post { -// #[serde(skip_deserializing, skip_serializing_if = "Option::is_none")] -// id: Option, -// title: String, -// text: String, -// } +#[post("/", data = "")] +async fn create( + conn: Connection, + post: Json, +) -> Result>> { + let _post = post::ActiveModel { + title: Set(post.title.to_owned()), + text: Set(post.text.to_owned()), + ..Default::default() + } + .save(&conn) + .await + .expect("could not insert post"); -// #[post("/", data = "")] -// async fn create(mut db: Connection, post: Json) -> Result>> { -// // There is no support for `RETURNING`. -// sqlx::query!( -// "INSERT INTO posts (title, text) VALUES (?, ?)", -// post.title, -// post.text -// ) -// .execute(&mut *db) -// .await?; - -// Ok(Created::new("/").body(post)) -// } + Ok(Created::new("/").body(post)) +} #[get("/")] -async fn list(mut con: Connection) -> Result>> { +async fn list(conn: Connection) -> Result>> { let ids = Post::find() - .all(&con) + .all(&conn) .await .expect("could not retrieve posts") .into_iter() @@ -61,9 +50,9 @@ async fn list(mut con: Connection) -> Result>> { } #[get("/")] -async fn read(mut con: Connection, id: i64) -> Option> { +async fn read(conn: Connection, id: i64) -> Option> { let post: Option = Post::find_by_id(id) - .one(&con) + .one(&conn) .await .expect("could not find post"); @@ -74,7 +63,7 @@ async fn read(mut con: Connection, id: i64) -> Option> { } #[delete("/")] -async fn delete(mut conn: Connection, id: i64) -> Result> { +async fn delete(conn: Connection, id: i64) -> Result> { let post: post::ActiveModel = Post::find_by_id(id) .one(&conn) .await @@ -87,7 +76,7 @@ async fn delete(mut conn: Connection, id: i64) -> Result> { } #[delete("/")] -async fn destroy(mut conn: Connection) -> Result<()> { +async fn destroy(conn: Connection) -> Result<()> { let _result = Post::delete_many().exec(&conn).await.unwrap(); Ok(()) } @@ -206,7 +195,7 @@ pub fn stage() -> AdHoc { // None => Err(rocket), // } })) - .mount("/sqlx", routes![list, read, delete, destroy]) + .mount("/sqlx", routes![create, delete, destroy, list, read,]) }) } diff --git a/examples/rocket_example/src/sqlx/post.rs b/examples/rocket_example/src/sqlx/post.rs index 58caf669..05ec80fe 100644 --- a/examples/rocket_example/src/sqlx/post.rs +++ b/examples/rocket_example/src/sqlx/post.rs @@ -1,4 +1,4 @@ -use rocket::serde::{json::Json, Deserialize, Serialize}; +use rocket::serde::{Deserialize, Serialize}; use sea_orm::entity::prelude::*; #[derive(Copy, Clone, Default, Debug, DeriveEntity, Deserialize, Serialize)] diff --git a/examples/rocket_example/src/sqlx/setup.rs b/examples/rocket_example/src/sqlx/setup.rs index fac405be..2421d84f 100644 --- a/examples/rocket_example/src/sqlx/setup.rs +++ b/examples/rocket_example/src/sqlx/setup.rs @@ -1,37 +1,37 @@ use sea_orm::{error::*, sea_query, DbConn, ExecResult}; -use sea_orm::sea_query::{ColumnDef, ForeignKey, ForeignKeyAction, Index, TableCreateStatement}; +use sea_orm::sea_query::{ColumnDef, TableCreateStatement}; // mod post; pub use super::post::*; async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result { - let builder = db.get_database_backend(); - db.execute(builder.build(stmt)).await + let builder = db.get_database_backend(); + db.execute(builder.build(stmt)).await } pub async fn create_post_table(db: &DbConn) -> Result { - let stmt = sea_query::Table::create() - .table(super::post::Entity) - .if_not_exists() - .col( - ColumnDef::new(super::post::Column::Id) - .integer() - .not_null() - .auto_increment() - .primary_key(), - ) - .col( - ColumnDef::new(super::post::Column::Title) - .string() - .not_null(), - ) - .col( - ColumnDef::new(super::post::Column::Text) - .string() - .not_null(), - ) - .to_owned(); + let stmt = sea_query::Table::create() + .table(super::post::Entity) + .if_not_exists() + .col( + ColumnDef::new(super::post::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col( + ColumnDef::new(super::post::Column::Title) + .string() + .not_null(), + ) + .col( + ColumnDef::new(super::post::Column::Text) + .string() + .not_null(), + ) + .to_owned(); - create_table(db, &stmt).await + create_table(db, &stmt).await } From f191645467e3724ee685508ef92dd719f9d702e9 Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Fri, 27 Aug 2021 22:31:54 +1000 Subject: [PATCH 21/89] Run migration via try_on_ignite --- examples/rocket_example/src/sqlx/mod.rs | 143 ++++-------------------- 1 file changed, 22 insertions(+), 121 deletions(-) diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index 2909ed9d..42431920 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -1,4 +1,4 @@ -use rocket::fairing::AdHoc; +use rocket::fairing::{self, AdHoc}; use rocket::response::status::Created; use rocket::serde::json::Json; use rocket::{futures, Build, Rocket}; @@ -81,131 +81,32 @@ async fn destroy(conn: Connection) -> Result<()> { Ok(()) } -// async fn run_migrations(rocket: Rocket) -> fairing::Result { -// use crate::rocket_db_pools::Pool; -// // match Db::fetch(&rocket) { -// // Some(db) => match sqlx::migrate!("db/sqlx/migrations").run(&**db).await { -// // Ok(_) => Ok(rocket), -// // Err(e) => { -// // error!("Failed to initialize SQLx database: {}", e); -// // Err(rocket) -// // } -// // }, -// // None => Err(rocket), -// // } -// // let conn = Db::get(&rocket).await.expect("database connection"); - -// match Db::fetch(&rocket) { -// Some(db) => match setup::create_post_table(db.get().await().expect("database connection")).await { -// Ok(_) => { -// println!("rocket: {:#?}", rocket); - -// Ok(rocket) -// } -// Err(e) => { -// error!("Failed to initialize SQLx database: {}", e); -// Err(rocket) -// } -// }, -// None => Err(rocket), -// } -// // Ok(rocket) -// } +async fn run_migrations(rocket: Rocket) -> fairing::Result { + let con = sea_orm::Database::connect("mysql://root:@localhost/rocket_example") + .await + .unwrap(); + let create_post_table = con + .execute(Statement::from_string( + DatabaseBackend::MySql, + r#" + CREATE TABLE posts ( + id int NOT NULL AUTO_INCREMENT, + title VARCHAR(255) NOT NULL, + text VARCHAR(255) NOT NULL, + PRIMARY KEY (id) + )"# + .to_owned(), + )) + .await; + println!("create_post_table: {:#?}", create_post_table); + Ok(rocket) +} pub fn stage() -> AdHoc { AdHoc::on_ignite("SQLx Stage", |rocket| async { rocket .attach(Db::init()) - .attach(AdHoc::try_on_ignite("Create init post", |rocket| async { - let con = sea_orm::Database::connect("mysql://root:@localhost/rocket_example") - .await - .unwrap(); - // let res = sqlx::query( - // r#" - // CREATE TABLE posts ( - // id INTEGER PRIMARY KEY AUTOINCREMENT, - // title VARCHAR NOT NULL, - // text VARCHAR NOT NULL, - // published BOOLEAN NOT NULL DEFAULT 0 - // )"#, - // ) - // .execute(&**db) - // .await; - let create_post_table = con - .execute(Statement::from_string( - DatabaseBackend::MySql, - r#" - CREATE TABLE posts ( - id int NOT NULL AUTO_INCREMENT, - title VARCHAR(255) NOT NULL, - text VARCHAR(255) NOT NULL, - PRIMARY KEY (id) - )"# - .to_owned(), - )) - .await; - println!("create_post_table: {:#?}", create_post_table); - - let create_post = con - .execute(Statement::from_string( - DatabaseBackend::MySql, - "INSERT INTO posts (title, text) VALUES ('a post', 'content of a post')" - .to_owned(), - )) - .await; - println!("create_post: {:#?}", create_post); - - // println!("all_posts: {:#?}", all_posts); - - // let res2 = sqlx::query( - // r#" - // INSERT INTO posts (title, text) VALUES ('a post', 'content of a post') - // "#, - // ) - // .execute(&**db) - // .await; - // println!("res2: {:#?}", res2); - - // Db::fetch(&rocket) - // .run(|db| { - // sqlx::query("DELETE FROM table").execute(&pool).await; - - // // conn.execute( - // // r#" - // // CREATE TABLE posts ( - // // id INTEGER PRIMARY KEY AUTOINCREMENT, - // // title VARCHAR NOT NULL, - // // text VARCHAR NOT NULL, - // // published BOOLEAN NOT NULL DEFAULT 0 - // // )"#, - // // params![], - // // ) - // }) - // .await - // .expect("can init rusqlite DB"); - Ok(rocket) - - // match Db::fetch(&rocket) { - // Some(db) => { - // println!("db: {:#?}", db); - // println!("&**db: {:#?}", &**db); - - // Ok(rocket) - // } - // None => Err(rocket), - // } - })) + .attach(AdHoc::try_on_ignite("SQLx Migrations", run_migrations)) .mount("/sqlx", routes![create, delete, destroy, list, read,]) }) } - -// pub async fn create_post(db: &DbConn) { -// let post = post::ActiveModel { -// title: Set("Post One".to_owned()), -// text: Set("post content 1".to_owned()), -// ..Default::default() -// } -// .save(db) -// .await -// .expect("could not insert post"); -// } From 380f2a0c04775e1e6b8f1e933c0bf87713b79a32 Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Fri, 27 Aug 2021 22:36:56 +1000 Subject: [PATCH 22/89] Use create_post_table instead of SQL --- examples/rocket_example/src/sqlx/mod.rs | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index 42431920..48fe8ab3 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -82,23 +82,10 @@ async fn destroy(conn: Connection) -> Result<()> { } async fn run_migrations(rocket: Rocket) -> fairing::Result { - let con = sea_orm::Database::connect("mysql://root:@localhost/rocket_example") + let conn = sea_orm::Database::connect("mysql://root:@localhost/rocket_example") .await .unwrap(); - let create_post_table = con - .execute(Statement::from_string( - DatabaseBackend::MySql, - r#" - CREATE TABLE posts ( - id int NOT NULL AUTO_INCREMENT, - title VARCHAR(255) NOT NULL, - text VARCHAR(255) NOT NULL, - PRIMARY KEY (id) - )"# - .to_owned(), - )) - .await; - println!("create_post_table: {:#?}", create_post_table); + let create_post_table = setup::create_post_table(&conn); Ok(rocket) } From 484da8f6b61abf124be8ace694be95e731664f01 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Sat, 28 Aug 2021 20:00:59 +0800 Subject: [PATCH 23/89] WIP --- examples/async-std/src/example_cake.rs | 2 ++ .../async-std/src/example_cake_filling.rs | 2 ++ examples/async-std/src/example_filling.rs | 2 ++ examples/async-std/src/example_fruit.rs | 2 ++ examples/tokio/src/cake.rs | 2 ++ sea-orm-codegen/src/entity/base_entity.rs | 24 ++++++++++++++++++- sea-orm-codegen/src/entity/writer.rs | 3 +++ sea-orm-codegen/tests/entity/cake.rs | 2 ++ sea-orm-codegen/tests/entity/cake_filling.rs | 2 ++ sea-orm-codegen/tests/entity/filling.rs | 2 ++ sea-orm-codegen/tests/entity/fruit.rs | 2 ++ sea-orm-codegen/tests/entity/vendor.rs | 2 ++ src/entity/column.rs | 2 +- src/entity/primary_key.rs | 2 ++ src/tests_cfg/cake.rs | 2 ++ src/tests_cfg/cake_filling.rs | 2 ++ src/tests_cfg/cake_filling_price.rs | 2 ++ src/tests_cfg/filling.rs | 2 ++ src/tests_cfg/fruit.rs | 2 ++ tests/common/bakery_chain/baker.rs | 2 ++ tests/common/bakery_chain/bakery.rs | 2 ++ tests/common/bakery_chain/cake.rs | 2 ++ tests/common/bakery_chain/cakes_bakers.rs | 2 ++ tests/common/bakery_chain/customer.rs | 2 ++ tests/common/bakery_chain/lineitem.rs | 2 ++ tests/common/bakery_chain/order.rs | 2 ++ 26 files changed, 73 insertions(+), 2 deletions(-) diff --git a/examples/async-std/src/example_cake.rs b/examples/async-std/src/example_cake.rs index 475315e8..114347ee 100644 --- a/examples/async-std/src/example_cake.rs +++ b/examples/async-std/src/example_cake.rs @@ -27,6 +27,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/examples/async-std/src/example_cake_filling.rs b/examples/async-std/src/example_cake_filling.rs index 19de83e4..c24e7296 100644 --- a/examples/async-std/src/example_cake_filling.rs +++ b/examples/async-std/src/example_cake_filling.rs @@ -28,6 +28,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { false } diff --git a/examples/async-std/src/example_filling.rs b/examples/async-std/src/example_filling.rs index 925b92fc..2a39a7de 100644 --- a/examples/async-std/src/example_filling.rs +++ b/examples/async-std/src/example_filling.rs @@ -27,6 +27,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/examples/async-std/src/example_fruit.rs b/examples/async-std/src/example_fruit.rs index b875da24..8802e707 100644 --- a/examples/async-std/src/example_fruit.rs +++ b/examples/async-std/src/example_fruit.rs @@ -29,6 +29,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/examples/tokio/src/cake.rs b/examples/tokio/src/cake.rs index 0b1a4439..21b83331 100644 --- a/examples/tokio/src/cake.rs +++ b/examples/tokio/src/cake.rs @@ -27,6 +27,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/sea-orm-codegen/src/entity/base_entity.rs b/sea-orm-codegen/src/entity/base_entity.rs index f1e03b21..2cc6dbb4 100644 --- a/sea-orm-codegen/src/entity/base_entity.rs +++ b/sea-orm-codegen/src/entity/base_entity.rs @@ -117,6 +117,18 @@ impl Entity { format_ident!("{}", auto_increment) } + pub fn get_primary_key_rs_type(&self) -> TokenStream { + if let Some(primary_key) = self.primary_keys.first() { + self.columns + .iter() + .find(|col| col.name.eq(&primary_key.name)) + .unwrap() + .get_rs_type() + } else { + TokenStream::new() + } + } + pub fn get_conjunct_relations_via_snake_case(&self) -> Vec { self.conjunct_relations .iter() @@ -151,7 +163,7 @@ mod tests { columns: vec![ Column { name: "id".to_owned(), - col_type: ColumnType::String(None), + col_type: ColumnType::Integer(None), auto_increment: false, not_null: false, unique: false, @@ -373,6 +385,16 @@ mod tests { ); } + #[test] + fn test_get_primary_key_rs_type() { + let entity = setup(); + + assert_eq!( + entity.get_primary_key_rs_type().to_string(), + entity.columns[0].get_rs_type().to_string() + ); + } + #[test] fn test_get_conjunct_relations_via_snake_case() { let entity = setup(); diff --git a/sea-orm-codegen/src/entity/writer.rs b/sea-orm-codegen/src/entity/writer.rs index 207864ba..7accac2d 100644 --- a/sea-orm-codegen/src/entity/writer.rs +++ b/sea-orm-codegen/src/entity/writer.rs @@ -173,8 +173,11 @@ impl EntityWriter { pub fn gen_impl_primary_key(entity: &Entity) -> TokenStream { let primary_key_auto_increment = entity.get_primary_key_auto_increment(); + let value_type = entity.get_primary_key_rs_type(); quote! { impl PrimaryKeyTrait for PrimaryKey { + type ValueType = #value_type; + fn auto_increment() -> bool { #primary_key_auto_increment } diff --git a/sea-orm-codegen/tests/entity/cake.rs b/sea-orm-codegen/tests/entity/cake.rs index 29f55ac6..55fa279f 100644 --- a/sea-orm-codegen/tests/entity/cake.rs +++ b/sea-orm-codegen/tests/entity/cake.rs @@ -29,6 +29,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/sea-orm-codegen/tests/entity/cake_filling.rs b/sea-orm-codegen/tests/entity/cake_filling.rs index d0f00560..227740c1 100644 --- a/sea-orm-codegen/tests/entity/cake_filling.rs +++ b/sea-orm-codegen/tests/entity/cake_filling.rs @@ -30,6 +30,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { false } diff --git a/sea-orm-codegen/tests/entity/filling.rs b/sea-orm-codegen/tests/entity/filling.rs index bedb1ab4..73d58152 100644 --- a/sea-orm-codegen/tests/entity/filling.rs +++ b/sea-orm-codegen/tests/entity/filling.rs @@ -29,6 +29,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/sea-orm-codegen/tests/entity/fruit.rs b/sea-orm-codegen/tests/entity/fruit.rs index 72c37c1b..12919021 100644 --- a/sea-orm-codegen/tests/entity/fruit.rs +++ b/sea-orm-codegen/tests/entity/fruit.rs @@ -31,6 +31,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/sea-orm-codegen/tests/entity/vendor.rs b/sea-orm-codegen/tests/entity/vendor.rs index 320400f4..ffc211a8 100644 --- a/sea-orm-codegen/tests/entity/vendor.rs +++ b/sea-orm-codegen/tests/entity/vendor.rs @@ -31,6 +31,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/src/entity/column.rs b/src/entity/column.rs index 16546057..611950f5 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -1,6 +1,6 @@ -use std::str::FromStr; use crate::{EntityName, IdenStatic, Iterable}; use sea_query::{DynIden, Expr, SeaRc, SelectStatement, SimpleExpr, Value}; +use std::str::FromStr; #[derive(Debug, Clone)] pub struct ColumnDef { diff --git a/src/entity/primary_key.rs b/src/entity/primary_key.rs index 81a28915..a36b04e0 100644 --- a/src/entity/primary_key.rs +++ b/src/entity/primary_key.rs @@ -2,6 +2,8 @@ use super::{ColumnTrait, IdenStatic, Iterable}; //LINT: composite primary key cannot auto increment pub trait PrimaryKeyTrait: IdenStatic + Iterable { + type ValueType: Sized; + fn auto_increment() -> bool; } diff --git a/src/tests_cfg/cake.rs b/src/tests_cfg/cake.rs index f8a35d6c..b4306313 100644 --- a/src/tests_cfg/cake.rs +++ b/src/tests_cfg/cake.rs @@ -28,6 +28,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/src/tests_cfg/cake_filling.rs b/src/tests_cfg/cake_filling.rs index b1151ee4..4336d2fa 100644 --- a/src/tests_cfg/cake_filling.rs +++ b/src/tests_cfg/cake_filling.rs @@ -29,6 +29,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { false } diff --git a/src/tests_cfg/cake_filling_price.rs b/src/tests_cfg/cake_filling_price.rs index c0bcbea1..bd2bc8eb 100644 --- a/src/tests_cfg/cake_filling_price.rs +++ b/src/tests_cfg/cake_filling_price.rs @@ -35,6 +35,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { false } diff --git a/src/tests_cfg/filling.rs b/src/tests_cfg/filling.rs index b439af7b..5e691980 100644 --- a/src/tests_cfg/filling.rs +++ b/src/tests_cfg/filling.rs @@ -41,6 +41,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/src/tests_cfg/fruit.rs b/src/tests_cfg/fruit.rs index 0511ae58..b0b8bc79 100644 --- a/src/tests_cfg/fruit.rs +++ b/src/tests_cfg/fruit.rs @@ -30,6 +30,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/tests/common/bakery_chain/baker.rs b/tests/common/bakery_chain/baker.rs index 0c63e721..071a1fbc 100644 --- a/tests/common/bakery_chain/baker.rs +++ b/tests/common/bakery_chain/baker.rs @@ -31,6 +31,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/tests/common/bakery_chain/bakery.rs b/tests/common/bakery_chain/bakery.rs index 61803329..2baa7b6b 100644 --- a/tests/common/bakery_chain/bakery.rs +++ b/tests/common/bakery_chain/bakery.rs @@ -29,6 +29,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/tests/common/bakery_chain/cake.rs b/tests/common/bakery_chain/cake.rs index 72e649ce..f699773c 100644 --- a/tests/common/bakery_chain/cake.rs +++ b/tests/common/bakery_chain/cake.rs @@ -35,6 +35,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/tests/common/bakery_chain/cakes_bakers.rs b/tests/common/bakery_chain/cakes_bakers.rs index 8106bbdf..853cf6da 100644 --- a/tests/common/bakery_chain/cakes_bakers.rs +++ b/tests/common/bakery_chain/cakes_bakers.rs @@ -28,6 +28,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { false } diff --git a/tests/common/bakery_chain/customer.rs b/tests/common/bakery_chain/customer.rs index ce4319ff..51668131 100644 --- a/tests/common/bakery_chain/customer.rs +++ b/tests/common/bakery_chain/customer.rs @@ -29,6 +29,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/tests/common/bakery_chain/lineitem.rs b/tests/common/bakery_chain/lineitem.rs index 45a6037f..1d338ed1 100644 --- a/tests/common/bakery_chain/lineitem.rs +++ b/tests/common/bakery_chain/lineitem.rs @@ -33,6 +33,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/tests/common/bakery_chain/order.rs b/tests/common/bakery_chain/order.rs index 82b02dee..f2d48971 100644 --- a/tests/common/bakery_chain/order.rs +++ b/tests/common/bakery_chain/order.rs @@ -33,6 +33,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } From 9db0748c64844ac6d63b63aae691bd610125716d Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Sat, 28 Aug 2021 20:20:34 +0800 Subject: [PATCH 24/89] Non-static link --- src/entity/model.rs | 4 ++-- src/entity/relation.rs | 6 +++--- src/query/join.rs | 4 ++-- src/tests_cfg/cake.rs | 2 +- tests/common/bakery_chain/baker.rs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/entity/model.rs b/src/entity/model.rs index 2672d8cc..4774e1dc 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -17,11 +17,11 @@ pub trait ModelTrait: Clone + Debug { >::find_related().belongs_to(self) } - fn find_linked(&self, _: L) -> Select + fn find_linked(&self, l: L) -> Select where L: Linked, { - L::find_linked() + l.find_linked() } } diff --git a/src/entity/relation.rs b/src/entity/relation.rs index 441a0524..29378f2a 100644 --- a/src/entity/relation.rs +++ b/src/entity/relation.rs @@ -33,11 +33,11 @@ pub trait Linked { type ToEntity: EntityTrait; - fn link() -> Vec; + fn link(&self) -> Vec; - fn find_linked() -> Select { + fn find_linked(&self) -> Select { let mut select = Select::new(); - for rel in Self::link().into_iter().rev() { + for rel in self.link().into_iter().rev() { select = select.join_rev(JoinType::InnerJoin, rel); } select diff --git a/src/query/join.rs b/src/query/join.rs index be759e97..42bc993c 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -59,13 +59,13 @@ where } /// Left Join with a Linked Entity and select both Entity. - pub fn find_also_linked(self, _: L) -> SelectTwo + pub fn find_also_linked(self, l: L) -> SelectTwo where L: Linked, T: EntityTrait, { let mut slf = self; - for rel in L::link() { + for rel in l.link() { slf = slf.join(JoinType::LeftJoin, rel); } slf.select_also(T::default()) diff --git a/src/tests_cfg/cake.rs b/src/tests_cfg/cake.rs index 56ca8797..32cc22fd 100644 --- a/src/tests_cfg/cake.rs +++ b/src/tests_cfg/cake.rs @@ -80,7 +80,7 @@ impl Linked for CakeToFilling { type ToEntity = super::filling::Entity; - fn link() -> Vec { + fn link(&self) -> Vec { vec![ super::cake_filling::Relation::Cake.def().rev(), super::cake_filling::Relation::Filling.def(), diff --git a/tests/common/bakery_chain/baker.rs b/tests/common/bakery_chain/baker.rs index 287e5deb..d3b01651 100644 --- a/tests/common/bakery_chain/baker.rs +++ b/tests/common/bakery_chain/baker.rs @@ -88,7 +88,7 @@ impl Linked for BakedForCustomer { type ToEntity = super::customer::Entity; - fn link() -> Vec { + fn link(&self) -> Vec { vec![ super::cakes_bakers::Relation::Baker.def().rev(), super::cakes_bakers::Relation::Cake.def(), From ccfda51a5e5f1e50265d7607af193753579e4c89 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Sat, 28 Aug 2021 23:48:13 +0800 Subject: [PATCH 25/89] WIP --- src/driver/sqlx_postgres.rs | 7 +++-- src/entity/active_model.rs | 8 +++-- src/entity/primary_key.rs | 5 +++- src/executor/insert.rs | 59 +++++++++++++++++++++++++++---------- 4 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/driver/sqlx_postgres.rs b/src/driver/sqlx_postgres.rs index a283ddf3..9c45413f 100644 --- a/src/driver/sqlx_postgres.rs +++ b/src/driver/sqlx_postgres.rs @@ -110,8 +110,11 @@ impl From for ExecResult { } } -pub(crate) fn query_result_into_exec_result(res: QueryResult) -> Result { - let last_insert_id: i32 = res.try_get("", "last_insert_id")?; +pub(crate) fn query_result_into_exec_result(res: QueryResult) -> Result +where + T: TryGetable, +{ + let last_insert_id: T = res.try_get("", "last_insert_id")?; Ok(ExecResult { result: ExecResultHolder::SqlxPostgres { last_insert_id: last_insert_id as u64, diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index 54bf037b..612e285d 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -221,7 +221,11 @@ where let exec = E::insert(am).exec(db); let res = exec.await?; // TODO: if the entity does not have auto increment primary key, then last_insert_id is a wrong value - if ::auto_increment() && res.last_insert_id != 0 { + // FIXME: Assumed valid last_insert_id is not equals to Default::default() + if ::auto_increment() + && res.last_insert_id != ::ValueType::default() + { + let last_insert_id = res.last_insert_id.to_string(); let find = E::find_by_id(res.last_insert_id).one(db); let found = find.await; let model: Option = found?; @@ -230,7 +234,7 @@ where None => Err(DbErr::Exec(format!( "Failed to find inserted item: {} {}", E::default().to_string(), - res.last_insert_id + last_insert_id ))), } } else { diff --git a/src/entity/primary_key.rs b/src/entity/primary_key.rs index a36b04e0..1d434c13 100644 --- a/src/entity/primary_key.rs +++ b/src/entity/primary_key.rs @@ -1,8 +1,11 @@ use super::{ColumnTrait, IdenStatic, Iterable}; +use crate::TryGetable; +use sea_query::IntoValueTuple; +use std::fmt::{Debug, Display}; //LINT: composite primary key cannot auto increment pub trait PrimaryKeyTrait: IdenStatic + Iterable { - type ValueType: Sized; + type ValueType: Sized + Default + Debug + Display + PartialEq + IntoValueTuple + TryGetable; fn auto_increment() -> bool; } diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 9414fea6..a644d6e9 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,15 +1,24 @@ -use crate::{error::*, ActiveModelTrait, DatabaseConnection, Insert, Statement}; +use crate::{ + error::*, ActiveModelTrait, DatabaseConnection, EntityTrait, Insert, PrimaryKeyTrait, Statement, +}; use sea_query::InsertStatement; -use std::future::Future; +use std::{future::Future, marker::PhantomData}; #[derive(Clone, Debug)] -pub struct Inserter { +pub struct Inserter +where + A: ActiveModelTrait, +{ query: InsertStatement, + model: PhantomData, } -#[derive(Clone, Debug)] -pub struct InsertResult { - pub last_insert_id: u64, +#[derive(Debug)] +pub struct InsertResult +where + A: ActiveModelTrait, +{ + pub last_insert_id: <<::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType, } impl Insert @@ -17,10 +26,13 @@ where A: ActiveModelTrait, { #[allow(unused_mut)] - pub fn exec( + pub fn exec<'a>( self, - db: &DatabaseConnection, - ) -> impl Future> + '_ { + db: &'a DatabaseConnection, + ) -> impl Future, DbErr>> + 'a + where + A: 'a, + { // so that self is dropped before entering await let mut query = self.query; #[cfg(feature = "sqlx-postgres")] @@ -35,26 +47,41 @@ where ); } } - Inserter::new(query).exec(db) + Inserter::::new(query).exec(db) } } -impl Inserter { +impl Inserter +where + A: ActiveModelTrait, +{ pub fn new(query: InsertStatement) -> Self { - Self { query } + Self { + query, + model: PhantomData, + } } - pub fn exec( + pub fn exec<'a>( self, - db: &DatabaseConnection, - ) -> impl Future> + '_ { + db: &'a DatabaseConnection, + ) -> impl Future, DbErr>> + 'a + where + A: 'a, + { let builder = db.get_database_backend(); exec_insert(builder.build(&self.query), db) } } // Only Statement impl Send -async fn exec_insert(statement: Statement, db: &DatabaseConnection) -> Result { +async fn exec_insert( + statement: Statement, + db: &DatabaseConnection, +) -> Result, DbErr> +where + A: ActiveModelTrait, +{ // TODO: Postgres instead use query_one + returning clause let result = match db { #[cfg(feature = "sqlx-postgres")] From 3bada7fbc3befd5f3316611dff889c87fa7ff2e7 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Sun, 29 Aug 2021 12:05:55 +0800 Subject: [PATCH 26/89] WIP --- README.md | 2 +- examples/async-std/src/operation.rs | 2 +- src/driver/sqlx_postgres.rs | 20 +++++++------------ src/executor/execute.rs | 31 +++++++++++++++++++---------- src/lib.rs | 2 +- tests/crud/create_baker.rs | 4 ++-- tests/crud/create_cake.rs | 8 ++++---- tests/crud/create_lineitem.rs | 14 ++++++------- tests/crud/create_order.rs | 14 ++++++------- tests/crud/deletes.rs | 2 +- tests/crud/mod.rs | 4 ++-- tests/crud/updates.rs | 6 +++--- tests/sequential_op_tests.rs | 8 ++++---- 13 files changed, 61 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index f2d262e9..c625268d 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ let pear = fruit::ActiveModel { }; // insert one -let res: InsertResult = Fruit::insert(pear).exec(db).await?; +let res = Fruit::insert(pear).exec(db).await?; println!("InsertResult: {}", res.last_insert_id); diff --git a/examples/async-std/src/operation.rs b/examples/async-std/src/operation.rs index b1273e10..3fa5cc85 100644 --- a/examples/async-std/src/operation.rs +++ b/examples/async-std/src/operation.rs @@ -20,7 +20,7 @@ pub async fn insert_and_update(db: &DbConn) -> Result<(), DbErr> { name: Set("pear".to_owned()), ..Default::default() }; - let res: InsertResult = Fruit::insert(pear).exec(db).await?; + let res = Fruit::insert(pear).exec(db).await?; println!(); println!("Inserted: last_insert_id = {}\n", res.last_insert_id); diff --git a/src/driver/sqlx_postgres.rs b/src/driver/sqlx_postgres.rs index 9c45413f..5456d8f7 100644 --- a/src/driver/sqlx_postgres.rs +++ b/src/driver/sqlx_postgres.rs @@ -102,24 +102,18 @@ impl From for QueryResult { impl From for ExecResult { fn from(result: PgQueryResult) -> ExecResult { ExecResult { - result: ExecResultHolder::SqlxPostgres { - last_insert_id: 0, - rows_affected: result.rows_affected(), - }, + result: ExecResultHolder::SqlxPostgres(result), } } } -pub(crate) fn query_result_into_exec_result(res: QueryResult) -> Result -where - T: TryGetable, -{ - let last_insert_id: T = res.try_get("", "last_insert_id")?; +pub(crate) fn query_result_into_exec_result(res: QueryResult) -> Result { + let result = match res { + QueryResult::SqlxPostgres(result) => result, + _ => panic!("Should be QueryResult::SqlxPostgres"), + }; Ok(ExecResult { - result: ExecResultHolder::SqlxPostgres { - last_insert_id: last_insert_id as u64, - rows_affected: 0, - }, + result: ExecResultHolder::SqlxPostgres(result), }) } diff --git a/src/executor/execute.rs b/src/executor/execute.rs index 00375bb7..f522aa6c 100644 --- a/src/executor/execute.rs +++ b/src/executor/execute.rs @@ -1,3 +1,5 @@ +use crate::TryGetable; + #[derive(Debug)] pub struct ExecResult { pub(crate) result: ExecResultHolder, @@ -8,10 +10,7 @@ pub(crate) enum ExecResultHolder { #[cfg(feature = "sqlx-mysql")] SqlxMySql(sqlx::mysql::MySqlQueryResult), #[cfg(feature = "sqlx-postgres")] - SqlxPostgres { - last_insert_id: u64, - rows_affected: u64, - }, + SqlxPostgres(sqlx::postgres::PgQueryResult), #[cfg(feature = "sqlx-sqlite")] SqlxSqlite(sqlx::sqlite::SqliteQueryResult), #[cfg(feature = "mock")] @@ -21,23 +20,35 @@ pub(crate) enum ExecResultHolder { // ExecResult // impl ExecResult { - pub fn last_insert_id(&self) -> u64 { + pub fn last_insert_id(&self) -> T + where + T: TryGetable + Default, + { match &self.result { #[cfg(feature = "sqlx-mysql")] - ExecResultHolder::SqlxMySql(result) => result.last_insert_id(), + ExecResultHolder::SqlxMySql(result) => { + // result.last_insert_id() + T::default() + } #[cfg(feature = "sqlx-postgres")] - ExecResultHolder::SqlxPostgres { last_insert_id, .. } => last_insert_id.to_owned(), + ExecResultHolder::SqlxPostgres(result) => { + res.try_get("", "last_insert_id").unwrap_or_default() + } #[cfg(feature = "sqlx-sqlite")] ExecResultHolder::SqlxSqlite(result) => { let last_insert_rowid = result.last_insert_rowid(); if last_insert_rowid < 0 { panic!("negative last_insert_rowid") } else { - last_insert_rowid as u64 + // last_insert_rowid + T::default() } } #[cfg(feature = "mock")] - ExecResultHolder::Mock(result) => result.last_insert_id, + ExecResultHolder::Mock(result) => { + // result.last_insert_id + T::default() + } } } @@ -46,7 +57,7 @@ impl ExecResult { #[cfg(feature = "sqlx-mysql")] ExecResultHolder::SqlxMySql(result) => result.rows_affected(), #[cfg(feature = "sqlx-postgres")] - ExecResultHolder::SqlxPostgres { rows_affected, .. } => rows_affected.to_owned(), + ExecResultHolder::SqlxPostgres(result) => result.rows_affected(), #[cfg(feature = "sqlx-sqlite")] ExecResultHolder::SqlxSqlite(result) => result.rows_affected(), #[cfg(feature = "mock")] diff --git a/src/lib.rs b/src/lib.rs index 377bd62b..8469ede7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,7 +93,7 @@ //! }; //! //! // insert one -//! let res: InsertResult = Fruit::insert(pear).exec(db).await?; +//! let res = Fruit::insert(pear).exec(db).await?; //! //! println!("InsertResult: {}", res.last_insert_id); //! # Ok(()) diff --git a/tests/crud/create_baker.rs b/tests/crud/create_baker.rs index 8653b692..085b4005 100644 --- a/tests/crud/create_baker.rs +++ b/tests/crud/create_baker.rs @@ -7,7 +7,7 @@ pub async fn test_create_baker(db: &DbConn) { profit_margin: Set(10.4), ..Default::default() }; - let bakery_insert_res: InsertResult = Bakery::insert(seaside_bakery) + let bakery_insert_res = Bakery::insert(seaside_bakery) .exec(db) .await .expect("could not insert bakery"); @@ -30,7 +30,7 @@ pub async fn test_create_baker(db: &DbConn) { bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), ..Default::default() }; - let res: InsertResult = Baker::insert(baker_bob) + let res = Baker::insert(baker_bob) .exec(db) .await .expect("could not insert baker"); diff --git a/tests/crud/create_cake.rs b/tests/crud/create_cake.rs index 07f9c4b3..fb4566eb 100644 --- a/tests/crud/create_cake.rs +++ b/tests/crud/create_cake.rs @@ -8,7 +8,7 @@ pub async fn test_create_cake(db: &DbConn) { profit_margin: Set(10.4), ..Default::default() }; - let bakery_insert_res: InsertResult = Bakery::insert(seaside_bakery) + let bakery_insert_res = Bakery::insert(seaside_bakery) .exec(db) .await .expect("could not insert bakery"); @@ -23,7 +23,7 @@ pub async fn test_create_cake(db: &DbConn) { bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), ..Default::default() }; - let baker_insert_res: InsertResult = Baker::insert(baker_bob) + let baker_insert_res = Baker::insert(baker_bob) .exec(db) .await .expect("could not insert baker"); @@ -38,7 +38,7 @@ pub async fn test_create_cake(db: &DbConn) { ..Default::default() }; - let cake_insert_res: InsertResult = Cake::insert(mud_cake) + let cake_insert_res = Cake::insert(mud_cake) .exec(db) .await .expect("could not insert cake"); @@ -53,7 +53,7 @@ pub async fn test_create_cake(db: &DbConn) { baker_id: Set(baker_insert_res.last_insert_id as i32), ..Default::default() }; - let _cake_baker_res: InsertResult = CakesBakers::insert(cake_baker) + let _cake_baker_res = CakesBakers::insert(cake_baker) .exec(db) .await .expect("could not insert cake_baker"); diff --git a/tests/crud/create_lineitem.rs b/tests/crud/create_lineitem.rs index c82958f9..22810913 100644 --- a/tests/crud/create_lineitem.rs +++ b/tests/crud/create_lineitem.rs @@ -10,7 +10,7 @@ pub async fn test_create_lineitem(db: &DbConn) { profit_margin: Set(10.4), ..Default::default() }; - let bakery_insert_res: InsertResult = Bakery::insert(seaside_bakery) + let bakery_insert_res = Bakery::insert(seaside_bakery) .exec(db) .await .expect("could not insert bakery"); @@ -26,7 +26,7 @@ pub async fn test_create_lineitem(db: &DbConn) { bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), ..Default::default() }; - let baker_insert_res: InsertResult = Baker::insert(baker_bob) + let baker_insert_res = Baker::insert(baker_bob) .exec(db) .await .expect("could not insert baker"); @@ -41,7 +41,7 @@ pub async fn test_create_lineitem(db: &DbConn) { ..Default::default() }; - let cake_insert_res: InsertResult = Cake::insert(mud_cake) + let cake_insert_res = Cake::insert(mud_cake) .exec(db) .await .expect("could not insert cake"); @@ -52,7 +52,7 @@ pub async fn test_create_lineitem(db: &DbConn) { baker_id: Set(baker_insert_res.last_insert_id as i32), ..Default::default() }; - let _cake_baker_res: InsertResult = CakesBakers::insert(cake_baker) + let _cake_baker_res = CakesBakers::insert(cake_baker) .exec(db) .await .expect("could not insert cake_baker"); @@ -63,7 +63,7 @@ pub async fn test_create_lineitem(db: &DbConn) { notes: Set(Some("Loves cheese cake".to_owned())), ..Default::default() }; - let customer_insert_res: InsertResult = Customer::insert(customer_kate) + let customer_insert_res = Customer::insert(customer_kate) .exec(db) .await .expect("could not insert customer"); @@ -76,7 +76,7 @@ pub async fn test_create_lineitem(db: &DbConn) { placed_at: Set(Utc::now().naive_utc()), ..Default::default() }; - let order_insert_res: InsertResult = Order::insert(order_1) + let order_insert_res = Order::insert(order_1) .exec(db) .await .expect("could not insert order"); @@ -89,7 +89,7 @@ pub async fn test_create_lineitem(db: &DbConn) { quantity: Set(1), ..Default::default() }; - let lineitem_insert_res: InsertResult = Lineitem::insert(lineitem_1) + let lineitem_insert_res = Lineitem::insert(lineitem_1) .exec(db) .await .expect("could not insert lineitem"); diff --git a/tests/crud/create_order.rs b/tests/crud/create_order.rs index 46ebcf09..e613802b 100644 --- a/tests/crud/create_order.rs +++ b/tests/crud/create_order.rs @@ -10,7 +10,7 @@ pub async fn test_create_order(db: &DbConn) { profit_margin: Set(10.4), ..Default::default() }; - let bakery_insert_res: InsertResult = Bakery::insert(seaside_bakery) + let bakery_insert_res = Bakery::insert(seaside_bakery) .exec(db) .await .expect("could not insert bakery"); @@ -26,7 +26,7 @@ pub async fn test_create_order(db: &DbConn) { bakery_id: Set(Some(bakery_insert_res.last_insert_id as i32)), ..Default::default() }; - let baker_insert_res: InsertResult = Baker::insert(baker_bob) + let baker_insert_res = Baker::insert(baker_bob) .exec(db) .await .expect("could not insert baker"); @@ -41,7 +41,7 @@ pub async fn test_create_order(db: &DbConn) { ..Default::default() }; - let cake_insert_res: InsertResult = Cake::insert(mud_cake) + let cake_insert_res = Cake::insert(mud_cake) .exec(db) .await .expect("could not insert cake"); @@ -52,7 +52,7 @@ pub async fn test_create_order(db: &DbConn) { baker_id: Set(baker_insert_res.last_insert_id as i32), ..Default::default() }; - let _cake_baker_res: InsertResult = CakesBakers::insert(cake_baker) + let _cake_baker_res = CakesBakers::insert(cake_baker) .exec(db) .await .expect("could not insert cake_baker"); @@ -63,7 +63,7 @@ pub async fn test_create_order(db: &DbConn) { notes: Set(Some("Loves cheese cake".to_owned())), ..Default::default() }; - let customer_insert_res: InsertResult = Customer::insert(customer_kate) + let customer_insert_res = Customer::insert(customer_kate) .exec(db) .await .expect("could not insert customer"); @@ -76,7 +76,7 @@ pub async fn test_create_order(db: &DbConn) { placed_at: Set(Utc::now().naive_utc()), ..Default::default() }; - let order_insert_res: InsertResult = Order::insert(order_1) + let order_insert_res = Order::insert(order_1) .exec(db) .await .expect("could not insert order"); @@ -89,7 +89,7 @@ pub async fn test_create_order(db: &DbConn) { quantity: Set(2), ..Default::default() }; - let _lineitem_insert_res: InsertResult = Lineitem::insert(lineitem_1) + let _lineitem_insert_res = Lineitem::insert(lineitem_1) .exec(db) .await .expect("could not insert lineitem"); diff --git a/tests/crud/deletes.rs b/tests/crud/deletes.rs index ebff4b5f..4c34d36b 100644 --- a/tests/crud/deletes.rs +++ b/tests/crud/deletes.rs @@ -10,7 +10,7 @@ pub async fn test_delete_cake(db: &DbConn) { profit_margin: Set(10.4), ..Default::default() }; - let bakery_insert_res: InsertResult = Bakery::insert(seaside_bakery) + let bakery_insert_res = Bakery::insert(seaside_bakery) .exec(db) .await .expect("could not insert bakery"); diff --git a/tests/crud/mod.rs b/tests/crud/mod.rs index 457f639d..82bdbc0d 100644 --- a/tests/crud/mod.rs +++ b/tests/crud/mod.rs @@ -15,7 +15,7 @@ pub async fn test_create_bakery(db: &DbConn) { profit_margin: Set(10.4), ..Default::default() }; - let res: InsertResult = Bakery::insert(seaside_bakery) + let res = Bakery::insert(seaside_bakery) .exec(db) .await .expect("could not insert bakery"); @@ -37,7 +37,7 @@ pub async fn test_create_customer(db: &DbConn) { notes: Set(Some("Loves cheese cake".to_owned())), ..Default::default() }; - let res: InsertResult = Customer::insert(customer_kate) + let res = Customer::insert(customer_kate) .exec(db) .await .expect("could not insert customer"); diff --git a/tests/crud/updates.rs b/tests/crud/updates.rs index 505e4837..94c11aaf 100644 --- a/tests/crud/updates.rs +++ b/tests/crud/updates.rs @@ -8,7 +8,7 @@ pub async fn test_update_cake(db: &DbConn) { profit_margin: Set(10.4), ..Default::default() }; - let bakery_insert_res: InsertResult = Bakery::insert(seaside_bakery) + let bakery_insert_res = Bakery::insert(seaside_bakery) .exec(db) .await .expect("could not insert bakery"); @@ -22,7 +22,7 @@ pub async fn test_update_cake(db: &DbConn) { ..Default::default() }; - let cake_insert_res: InsertResult = Cake::insert(mud_cake) + let cake_insert_res = Cake::insert(mud_cake) .exec(db) .await .expect("could not insert cake"); @@ -62,7 +62,7 @@ pub async fn test_update_bakery(db: &DbConn) { profit_margin: Set(10.4), ..Default::default() }; - let bakery_insert_res: InsertResult = Bakery::insert(seaside_bakery) + let bakery_insert_res = Bakery::insert(seaside_bakery) .exec(db) .await .expect("could not insert bakery"); diff --git a/tests/sequential_op_tests.rs b/tests/sequential_op_tests.rs index a854c768..6e1377bc 100644 --- a/tests/sequential_op_tests.rs +++ b/tests/sequential_op_tests.rs @@ -67,7 +67,7 @@ async fn init_setup(db: &DatabaseConnection) { ..Default::default() }; - let cake_insert_res: InsertResult = Cake::insert(mud_cake) + let cake_insert_res = Cake::insert(mud_cake) .exec(db) .await .expect("could not insert cake"); @@ -78,7 +78,7 @@ async fn init_setup(db: &DatabaseConnection) { ..Default::default() }; - let _cake_baker_res: InsertResult = CakesBakers::insert(cake_baker) + let _cake_baker_res = CakesBakers::insert(cake_baker) .exec(db) .await .expect("could not insert cake_baker"); @@ -200,7 +200,7 @@ async fn create_cake(db: &DatabaseConnection, baker: baker::Model) -> Option Option Date: Sun, 29 Aug 2021 21:37:55 +1000 Subject: [PATCH 27/89] Generalize over MySQL and Postgres --- Cargo.toml | 16 +++++----- examples/rocket_example/Cargo.toml | 13 ++++++-- examples/rocket_example/src/sqlx/mod.rs | 23 +++++++------- examples/rocket_example/src/sqlx/post.rs | 2 +- src/driver/mod.rs | 10 +++--- src/driver/rocket_db.rs | 24 +++++++++++++++ src/driver/rocket_mysql.rs | 39 ------------------------ 7 files changed, 60 insertions(+), 67 deletions(-) create mode 100644 src/driver/rocket_db.rs delete mode 100644 src/driver/rocket_mysql.rs diff --git a/Cargo.toml b/Cargo.toml index bc90db55..2313f891 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,5 @@ [workspace] -members = [ - ".", - "sea-orm-macros", - "sea-orm-codegen", -] +members = [".", "sea-orm-macros", "sea-orm-codegen"] [package] name = "sea-orm" @@ -40,8 +36,12 @@ sqlx-core = { version = "^0.5", optional = true } sqlx-macros = { version = "^0.5", optional = true } serde_json = { version = "^1", optional = true } uuid = { version = "0.8", features = ["serde", "v4"], optional = true } -rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = ["json"], optional = true } -rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", features = ["sqlx_mysql"], optional = true } +rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = [ + "json", +], optional = true } +rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", features = [ + "sqlx_mysql", +], optional = true } [dev-dependencies] smol = { version = "^1.2" } @@ -93,4 +93,4 @@ runtime-actix-rustls = ["sqlx/runtime-actix-rustls", "runtime-actix"] runtime-tokio = [] runtime-tokio-native-tls = ["sqlx/runtime-tokio-native-tls", "runtime-tokio"] runtime-tokio-rustls = ["sqlx/runtime-tokio-rustls", "runtime-tokio"] -rocket-mysql = ["rocket", "rocket_db_pools"] +rocket-db = ["rocket", "rocket_db_pools"] diff --git a/examples/rocket_example/Cargo.toml b/examples/rocket_example/Cargo.toml index feb77d43..d9f5d738 100644 --- a/examples/rocket_example/Cargo.toml +++ b/examples/rocket_example/Cargo.toml @@ -5,9 +5,11 @@ edition = "2018" publish = false [workspace] [dependencies] -rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = ["json"] } -rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", features = ["sqlx_mysql"] } -sea-orm = { path = "../../", features = ["sqlx-all", "rocket-mysql"] } +rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = [ + "json", +] } +rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", features = [] } +sea-orm = { path = "../../", features = ["rocket-db"] } sea-query = { version = "^0.12.8" } serde_json = { version = "^1" } @@ -19,3 +21,8 @@ futures-util = { version = "^0.3" } version = "0.5.1" default-features = false features = ["macros", "offline", "migrate"] + +[features] +default = ["sqlx-postgres"] +sqlx-postgres = ["sea-orm/sqlx-postgres"] +sqlx-mysql = ["sea-orm/sqlx-mysql"] diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index 48fe8ab3..1b43ea67 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -1,17 +1,14 @@ use rocket::fairing::{self, AdHoc}; use rocket::response::status::Created; use rocket::serde::json::Json; -use rocket::{futures, Build, Rocket}; - +use rocket::{Build, Rocket}; use rocket_db_pools::{sqlx, Connection, Database}; - use sea_orm::entity::*; -use sea_orm::{DatabaseBackend, Statement}; mod setup; #[derive(Database, Debug)] -#[database("blog")] +#[database("rocket_example")] struct Db(sea_orm::Database); type Result> = std::result::Result; @@ -37,7 +34,7 @@ async fn create( } #[get("/")] -async fn list(conn: Connection) -> Result>> { +async fn list(conn: Connection) -> Result>> { let ids = Post::find() .all(&conn) .await @@ -63,7 +60,7 @@ async fn read(conn: Connection, id: i64) -> Option> { } #[delete("/")] -async fn delete(conn: Connection, id: i64) -> Result> { +async fn delete(conn: Connection, id: i32) -> Result> { let post: post::ActiveModel = Post::find_by_id(id) .one(&conn) .await @@ -82,10 +79,14 @@ async fn destroy(conn: Connection) -> Result<()> { } async fn run_migrations(rocket: Rocket) -> fairing::Result { - let conn = sea_orm::Database::connect("mysql://root:@localhost/rocket_example") - .await - .unwrap(); - let create_post_table = setup::create_post_table(&conn); + #[cfg(feature = "sqlx-mysql")] + let db_url = "mysql://root:@localhost/rocket_example"; + #[cfg(feature = "sqlx-postgres")] + let db_url = "postgres://root:root@localhost/rocket_example"; + + let conn = sea_orm::Database::connect(db_url).await.unwrap(); + + let _create_post_table = setup::create_post_table(&conn).await; Ok(rocket) } diff --git a/examples/rocket_example/src/sqlx/post.rs b/examples/rocket_example/src/sqlx/post.rs index 05ec80fe..bfba82aa 100644 --- a/examples/rocket_example/src/sqlx/post.rs +++ b/examples/rocket_example/src/sqlx/post.rs @@ -15,7 +15,7 @@ impl EntityName for Entity { #[serde(crate = "rocket::serde")] pub struct Model { #[serde(skip_deserializing, skip_serializing_if = "Option::is_none")] - pub id: Option, + pub id: Option, pub title: String, pub text: String, } diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 5786d299..9b30e6d4 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -9,13 +9,8 @@ mod sqlx_postgres; #[cfg(feature = "sqlx-sqlite")] mod sqlx_sqlite; -#[cfg(feature = "rocket-mysql")] -mod rocket_mysql; - #[cfg(feature = "mock")] pub use mock::*; -#[cfg(feature = "rocket-mysql")] -pub use rocket_mysql::*; #[cfg(feature = "sqlx-dep")] pub use sqlx_common::*; #[cfg(feature = "sqlx-mysql")] @@ -24,3 +19,8 @@ pub use sqlx_mysql::*; pub use sqlx_postgres::*; #[cfg(feature = "sqlx-sqlite")] pub use sqlx_sqlite::*; + +#[cfg(feature = "rocket-db")] +mod rocket_db; +#[cfg(feature = "rocket-db")] +pub use rocket_db::*; diff --git a/src/driver/rocket_db.rs b/src/driver/rocket_db.rs new file mode 100644 index 00000000..512f1fad --- /dev/null +++ b/src/driver/rocket_db.rs @@ -0,0 +1,24 @@ +use rocket::figment::Figment; +use rocket_db_pools::{Config, Error}; + +#[rocket::async_trait] +impl rocket_db_pools::Pool for crate::Database { + type Error = crate::DbErr; + + type Connection = crate::DatabaseConnection; + + async fn init(figment: &Figment) -> Result { + Ok(crate::Database {}) + } + + async fn get(&self) -> Result { + #[cfg(feature = "sqlx-mysql")] + let db_url = "mysql://root:@localhost/rocket_example"; + #[cfg(feature = "sqlx-postgres")] + let db_url = "postgres://root:root@localhost/rocket_example"; + + println!("db_url: {:#?}", db_url); + + Ok(crate::Database::connect(db_url).await.unwrap()) + } +} diff --git a/src/driver/rocket_mysql.rs b/src/driver/rocket_mysql.rs deleted file mode 100644 index ef90f1ee..00000000 --- a/src/driver/rocket_mysql.rs +++ /dev/null @@ -1,39 +0,0 @@ -use rocket::figment::Figment; -use rocket_db_pools::{Config, Error}; - -#[rocket::async_trait] -impl rocket_db_pools::Pool for crate::Database { - type Error = crate::DbErr; - - type Connection = crate::DatabaseConnection; - - async fn init(figment: &Figment) -> Result { - // let config = figment.extract::()?; - // let mut opts = config.url.parse::>().map_err(Error::Init)?; - // opts.disable_statement_logging(); - // specialize(&mut opts, &config); - - // sqlx::pool::PoolOptions::new() - // .max_connections(config.max_connections as u32) - // .connect_timeout(Duration::from_secs(config.connect_timeout)) - // .idle_timeout(config.idle_timeout.map(Duration::from_secs)) - // .min_connections(config.min_connections.unwrap_or_default()) - // .connect_with(opts) - // .await - // .map_err(Error::Init) - Ok(crate::Database {}) - } - - async fn get(&self) -> Result { - // self.acquire().await.map_err(Error::Get) - // let con = crate::Database::connect("sqlite::memory:").await; - - // Ok(crate::Database::connect("sqlite::memory:").await.unwrap()) - // "mysql://root:@localhost" - Ok( - crate::Database::connect("mysql://root:@localhost/rocket_example") - .await - .unwrap(), - ) - } -} From 752cc4a44e9691a893e459281155bd21b3a950f8 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Sun, 29 Aug 2021 19:39:01 +0800 Subject: [PATCH 28/89] WIP --- src/entity/primary_key.rs | 14 ++++++++++++-- src/executor/execute.rs | 24 +++++++++++++----------- tests/crud/mod.rs | 2 +- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/entity/primary_key.rs b/src/entity/primary_key.rs index 1d434c13..f24c62df 100644 --- a/src/entity/primary_key.rs +++ b/src/entity/primary_key.rs @@ -1,11 +1,21 @@ use super::{ColumnTrait, IdenStatic, Iterable}; use crate::TryGetable; use sea_query::IntoValueTuple; -use std::fmt::{Debug, Display}; +use std::{ + fmt::{Debug, Display}, + str::FromStr, +}; //LINT: composite primary key cannot auto increment pub trait PrimaryKeyTrait: IdenStatic + Iterable { - type ValueType: Sized + Default + Debug + Display + PartialEq + IntoValueTuple + TryGetable; + type ValueType: Sized + + Default + + Debug + + Display + + PartialEq + + IntoValueTuple + + TryGetable + + FromStr; fn auto_increment() -> bool; } diff --git a/src/executor/execute.rs b/src/executor/execute.rs index f522aa6c..db7e8a39 100644 --- a/src/executor/execute.rs +++ b/src/executor/execute.rs @@ -1,4 +1,5 @@ use crate::TryGetable; +use std::str::FromStr; #[derive(Debug)] pub struct ExecResult { @@ -22,14 +23,15 @@ pub(crate) enum ExecResultHolder { impl ExecResult { pub fn last_insert_id(&self) -> T where - T: TryGetable + Default, + T: TryGetable + Default + FromStr, { match &self.result { #[cfg(feature = "sqlx-mysql")] - ExecResultHolder::SqlxMySql(result) => { - // result.last_insert_id() - T::default() - } + ExecResultHolder::SqlxMySql(result) => result + .last_insert_id() + .to_string() + .parse() + .unwrap_or_default(), #[cfg(feature = "sqlx-postgres")] ExecResultHolder::SqlxPostgres(result) => { res.try_get("", "last_insert_id").unwrap_or_default() @@ -40,15 +42,15 @@ impl ExecResult { if last_insert_rowid < 0 { panic!("negative last_insert_rowid") } else { - // last_insert_rowid - T::default() + last_insert_rowid.to_string().parse().unwrap_or_default() } } #[cfg(feature = "mock")] - ExecResultHolder::Mock(result) => { - // result.last_insert_id - T::default() - } + ExecResultHolder::Mock(result) => result + .last_insert_id + .to_string() + .parse() + .unwrap_or_default(), } } diff --git a/tests/crud/mod.rs b/tests/crud/mod.rs index 82bdbc0d..7e024055 100644 --- a/tests/crud/mod.rs +++ b/tests/crud/mod.rs @@ -1,4 +1,4 @@ -use sea_orm::{entity::*, DbConn, InsertResult}; +use sea_orm::{entity::*, DbConn}; pub use super::common::bakery_chain::*; From 4e5a8d2dc553a26668c89b5740bb6a0d4b5e33f3 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Sun, 29 Aug 2021 20:57:25 +0800 Subject: [PATCH 29/89] WIP --- src/driver/sqlx_postgres.rs | 10 ---------- src/executor/execute.rs | 26 +++++--------------------- src/executor/insert.rs | 16 +++++++++++----- 3 files changed, 16 insertions(+), 36 deletions(-) diff --git a/src/driver/sqlx_postgres.rs b/src/driver/sqlx_postgres.rs index 5456d8f7..b3d71f9a 100644 --- a/src/driver/sqlx_postgres.rs +++ b/src/driver/sqlx_postgres.rs @@ -107,16 +107,6 @@ impl From for ExecResult { } } -pub(crate) fn query_result_into_exec_result(res: QueryResult) -> Result { - let result = match res { - QueryResult::SqlxPostgres(result) => result, - _ => panic!("Should be QueryResult::SqlxPostgres"), - }; - Ok(ExecResult { - result: ExecResultHolder::SqlxPostgres(result), - }) -} - fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, Postgres, PgArguments> { let mut query = sqlx::query(&stmt.sql); if let Some(values) = &stmt.values { diff --git a/src/executor/execute.rs b/src/executor/execute.rs index db7e8a39..f05ee963 100644 --- a/src/executor/execute.rs +++ b/src/executor/execute.rs @@ -1,6 +1,3 @@ -use crate::TryGetable; -use std::str::FromStr; - #[derive(Debug)] pub struct ExecResult { pub(crate) result: ExecResultHolder, @@ -21,36 +18,23 @@ pub(crate) enum ExecResultHolder { // ExecResult // impl ExecResult { - pub fn last_insert_id(&self) -> T - where - T: TryGetable + Default + FromStr, - { + pub fn last_insert_id(&self) -> u64 { match &self.result { #[cfg(feature = "sqlx-mysql")] - ExecResultHolder::SqlxMySql(result) => result - .last_insert_id() - .to_string() - .parse() - .unwrap_or_default(), + ExecResultHolder::SqlxMySql(result) => result.last_insert_id(), #[cfg(feature = "sqlx-postgres")] - ExecResultHolder::SqlxPostgres(result) => { - res.try_get("", "last_insert_id").unwrap_or_default() - } + ExecResultHolder::SqlxPostgres(_) => panic!("Should not retrieve last_insert_id this way"), #[cfg(feature = "sqlx-sqlite")] ExecResultHolder::SqlxSqlite(result) => { let last_insert_rowid = result.last_insert_rowid(); if last_insert_rowid < 0 { panic!("negative last_insert_rowid") } else { - last_insert_rowid.to_string().parse().unwrap_or_default() + last_insert_rowid as u64 } } #[cfg(feature = "mock")] - ExecResultHolder::Mock(result) => result - .last_insert_id - .to_string() - .parse() - .unwrap_or_default(), + ExecResultHolder::Mock(result) => result.last_insert_id, } } diff --git a/src/executor/insert.rs b/src/executor/insert.rs index a644d6e9..7e5e24aa 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -37,7 +37,7 @@ where let mut query = self.query; #[cfg(feature = "sqlx-postgres")] if let DatabaseConnection::SqlxPostgresPoolConnection(_) = db { - use crate::{EntityTrait, Iterable}; + use crate::Iterable; use sea_query::{Alias, Expr, Query}; for key in ::PrimaryKey::iter() { query.returning( @@ -83,15 +83,21 @@ where A: ActiveModelTrait, { // TODO: Postgres instead use query_one + returning clause - let result = match db { + let last_insert_id = match db { #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => { let res = conn.query_one(statement).await?.unwrap(); - crate::query_result_into_exec_result(res)? + res.try_get("", "last_insert_id").unwrap_or_default() } - _ => db.execute(statement).await?, + _ => { + db.execute(statement).await? + .last_insert_id() + .to_string() + .parse() + .unwrap_or_default() + }, }; Ok(InsertResult { - last_insert_id: result.last_insert_id(), + last_insert_id, }) } From 4d0c5f0b0f54a0b20986cb7cc69e54438123578c Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Sun, 29 Aug 2021 22:11:19 +0800 Subject: [PATCH 30/89] Hotfix - Postgres errors --- tests/crud/create_lineitem.rs | 2 +- tests/crud/create_order.rs | 4 ++-- tests/crud/updates.rs | 2 +- tests/sequential_op_tests.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/crud/create_lineitem.rs b/tests/crud/create_lineitem.rs index 22810913..1030651a 100644 --- a/tests/crud/create_lineitem.rs +++ b/tests/crud/create_lineitem.rs @@ -105,7 +105,7 @@ pub async fn test_create_lineitem(db: &DbConn) { assert_eq!(lineitem_model.price, dec!(7.55)); - let cake: Option = Cake::find_by_id(lineitem_model.cake_id as u64) + let cake: Option = Cake::find_by_id(lineitem_model.cake_id) .one(db) .await .expect("could not find cake"); diff --git a/tests/crud/create_order.rs b/tests/crud/create_order.rs index e613802b..e3b12b52 100644 --- a/tests/crud/create_order.rs +++ b/tests/crud/create_order.rs @@ -103,7 +103,7 @@ pub async fn test_create_order(db: &DbConn) { let order_model = order.unwrap(); assert_eq!(order_model.total, dec!(15.10)); - let customer: Option = Customer::find_by_id(order_model.customer_id as u64) + let customer: Option = Customer::find_by_id(order_model.customer_id) .one(db) .await .expect("could not find customer"); @@ -111,7 +111,7 @@ pub async fn test_create_order(db: &DbConn) { let customer_model = customer.unwrap(); assert_eq!(customer_model.name, "Kate"); - let bakery: Option = Bakery::find_by_id(order_model.bakery_id as i64) + let bakery: Option = Bakery::find_by_id(order_model.bakery_id) .one(db) .await .expect("could not find bakery"); diff --git a/tests/crud/updates.rs b/tests/crud/updates.rs index 94c11aaf..df797ed3 100644 --- a/tests/crud/updates.rs +++ b/tests/crud/updates.rs @@ -131,7 +131,7 @@ pub async fn test_update_deleted_customer(db: &DbConn) { assert_eq!(Customer::find().count(db).await.unwrap(), init_n_customers); let customer: Option = - Customer::find_by_id(customer_id.clone().unwrap() as i64) + Customer::find_by_id(customer_id.clone().unwrap()) .one(db) .await .expect("could not find customer"); diff --git a/tests/sequential_op_tests.rs b/tests/sequential_op_tests.rs index 6e1377bc..300812cf 100644 --- a/tests/sequential_op_tests.rs +++ b/tests/sequential_op_tests.rs @@ -183,7 +183,7 @@ async fn find_baker_least_sales(db: &DatabaseConnection) -> Option results.sort_by(|a, b| b.cakes_sold.cmp(&a.cakes_sold)); - Baker::find_by_id(results.last().unwrap().id as i64) + Baker::find_by_id(results.last().unwrap().id) .one(db) .await .unwrap() From 333f199c1a566066fb06e6bc6d1c5b4158101948 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Sun, 29 Aug 2021 22:18:53 +0800 Subject: [PATCH 31/89] cargo fmt --- src/executor/execute.rs | 4 +++- src/executor/insert.rs | 12 +++++------- tests/crud/updates.rs | 9 ++++----- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/executor/execute.rs b/src/executor/execute.rs index f05ee963..46ba2d69 100644 --- a/src/executor/execute.rs +++ b/src/executor/execute.rs @@ -23,7 +23,9 @@ impl ExecResult { #[cfg(feature = "sqlx-mysql")] ExecResultHolder::SqlxMySql(result) => result.last_insert_id(), #[cfg(feature = "sqlx-postgres")] - ExecResultHolder::SqlxPostgres(_) => panic!("Should not retrieve last_insert_id this way"), + ExecResultHolder::SqlxPostgres(_) => { + panic!("Should not retrieve last_insert_id this way") + } #[cfg(feature = "sqlx-sqlite")] ExecResultHolder::SqlxSqlite(result) => { let last_insert_rowid = result.last_insert_rowid(); diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 7e5e24aa..e806fe62 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -89,15 +89,13 @@ where let res = conn.query_one(statement).await?.unwrap(); res.try_get("", "last_insert_id").unwrap_or_default() } - _ => { - db.execute(statement).await? + _ => db + .execute(statement) + .await? .last_insert_id() .to_string() .parse() - .unwrap_or_default() - }, + .unwrap_or_default(), }; - Ok(InsertResult { - last_insert_id, - }) + Ok(InsertResult { last_insert_id }) } diff --git a/tests/crud/updates.rs b/tests/crud/updates.rs index df797ed3..172c9c21 100644 --- a/tests/crud/updates.rs +++ b/tests/crud/updates.rs @@ -130,11 +130,10 @@ pub async fn test_update_deleted_customer(db: &DbConn) { assert_eq!(Customer::find().count(db).await.unwrap(), init_n_customers); - let customer: Option = - Customer::find_by_id(customer_id.clone().unwrap()) - .one(db) - .await - .expect("could not find customer"); + let customer: Option = Customer::find_by_id(customer_id.clone().unwrap()) + .one(db) + .await + .expect("could not find customer"); assert_eq!(customer, None); } From dfe26f9b7c0230eea07c48cec5cfbbe7e674d4ab Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 30 Aug 2021 22:34:02 +0800 Subject: [PATCH 32/89] Without direct rocket dependency --- Cargo.toml | 10 +++------- examples/rocket_example/Cargo.toml | 6 ++---- src/driver/mod.rs | 4 ++-- src/driver/rocket_db.rs | 6 +++--- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4183bcd9..e9d0774e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,12 +36,8 @@ sqlx-core = { version = "^0.5", optional = true } sqlx-macros = { version = "^0.5", optional = true } serde_json = { version = "^1", optional = true } uuid = { version = "0.8", features = ["serde", "v4"], optional = true } -rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = [ - "json", -], optional = true } -rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", features = [ - "sqlx_mysql", -], optional = true } +rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", features = ["sqlx_mysql"], optional = true } +async-trait = { version = "0.1", optional = true } [dev-dependencies] smol = { version = "^1.2" } @@ -93,4 +89,4 @@ runtime-actix-rustls = ["sqlx/runtime-actix-rustls", "runtime-actix"] runtime-tokio = [] runtime-tokio-native-tls = ["sqlx/runtime-tokio-native-tls", "runtime-tokio"] runtime-tokio-rustls = ["sqlx/runtime-tokio-rustls", "runtime-tokio"] -rocket-db = ["rocket", "rocket_db_pools"] +web-api-rocket = ["rocket_db_pools", "async-trait"] diff --git a/examples/rocket_example/Cargo.toml b/examples/rocket_example/Cargo.toml index d9f5d738..a8d4e736 100644 --- a/examples/rocket_example/Cargo.toml +++ b/examples/rocket_example/Cargo.toml @@ -5,11 +5,9 @@ edition = "2018" publish = false [workspace] [dependencies] -rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = [ - "json", -] } +rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = ["json"] } rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", features = [] } -sea-orm = { path = "../../", features = ["rocket-db"] } +sea-orm = { path = "../../", features = ["web-api-rocket"] } sea-query = { version = "^0.12.8" } serde_json = { version = "^1" } diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 9b30e6d4..66ea7c79 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -20,7 +20,7 @@ pub use sqlx_postgres::*; #[cfg(feature = "sqlx-sqlite")] pub use sqlx_sqlite::*; -#[cfg(feature = "rocket-db")] +#[cfg(feature = "web-api-rocket")] mod rocket_db; -#[cfg(feature = "rocket-db")] +#[cfg(feature = "web-api-rocket")] pub use rocket_db::*; diff --git a/src/driver/rocket_db.rs b/src/driver/rocket_db.rs index 512f1fad..755a7fec 100644 --- a/src/driver/rocket_db.rs +++ b/src/driver/rocket_db.rs @@ -1,7 +1,7 @@ -use rocket::figment::Figment; -use rocket_db_pools::{Config, Error}; +use async_trait::async_trait; +use rocket_db_pools::{rocket::figment::Figment, Config, Error}; -#[rocket::async_trait] +#[async_trait] impl rocket_db_pools::Pool for crate::Database { type Error = crate::DbErr; From 8a81e43d2e507ff90aeeb54b095f3f055fc759ea Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 31 Aug 2021 12:49:07 +0800 Subject: [PATCH 33/89] Rename to schema --- src/entity/mod.rs | 4 ++-- src/entity/{create_stmt.rs => schema.rs} | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) rename src/entity/{create_stmt.rs => schema.rs} (99%) diff --git a/src/entity/mod.rs b/src/entity/mod.rs index 9d911d8c..47eb4fa5 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -6,7 +6,7 @@ mod model; pub mod prelude; mod primary_key; mod relation; -mod create_stmt; +mod schema; pub use active_model::*; pub use base_entity::*; @@ -16,4 +16,4 @@ pub use model::*; // pub use prelude::*; pub use primary_key::*; pub use relation::*; -pub use create_stmt::*; +pub use schema::*; diff --git a/src/entity/create_stmt.rs b/src/entity/schema.rs similarity index 99% rename from src/entity/create_stmt.rs rename to src/entity/schema.rs index fbf77c02..cbbae75c 100644 --- a/src/entity/create_stmt.rs +++ b/src/entity/schema.rs @@ -152,7 +152,6 @@ impl CreateStatementOf for EntityTrait {} #[cfg(test)] mod tests { - use crate::{CreateStatementOf, tests_cfg}; #[test] From d1d37fab070415123eeb90afbb62f8848379936b Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 31 Aug 2021 12:49:33 +0800 Subject: [PATCH 34/89] cargo fmt --- src/entity/column.rs | 2 +- src/entity/schema.rs | 194 +++++++++++++++++++++++++++++++------------ 2 files changed, 141 insertions(+), 55 deletions(-) diff --git a/src/entity/column.rs b/src/entity/column.rs index 16546057..611950f5 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -1,6 +1,6 @@ -use std::str::FromStr; use crate::{EntityName, IdenStatic, Iterable}; use sea_query::{DynIden, Expr, SeaRc, SelectStatement, SimpleExpr, Value}; +use std::str::FromStr; #[derive(Debug, Clone)] pub struct ColumnDef { diff --git a/src/entity/schema.rs b/src/entity/schema.rs index cbbae75c..6c334112 100644 --- a/src/entity/schema.rs +++ b/src/entity/schema.rs @@ -1,12 +1,17 @@ -use crate::{ColumnDef as Sea_Orm_Column_Def, EntityTrait, PrimaryKeyTrait, RelationDef}; use crate::entity::column::ColumnTrait; -use crate::entity::relation::RelationTrait; use crate::entity::primary_key::PrimaryKeyToColumn; -use sea_query::{Alias, ColumnDef as Sea_Query_Column_Def, ColumnType as Sea_Query_Column_Type, ForeignKeyCreateStatement, IndexCreateStatement, SeaRc, TableCreateStatement, TableRef}; +use crate::entity::relation::RelationTrait; +use crate::{ColumnDef as Sea_Orm_Column_Def, EntityTrait, PrimaryKeyTrait, RelationDef}; +use sea_query::{ + Alias, ColumnDef as Sea_Query_Column_Def, ColumnType as Sea_Query_Column_Type, + ForeignKeyCreateStatement, IndexCreateStatement, SeaRc, TableCreateStatement, TableRef, +}; pub use sea_strum::IntoEnumIterator; -pub trait CreateStatementOf -{ - fn create_table_statement_of(entity: E) -> TableCreateStatement where E: EntityTrait { +pub trait CreateStatementOf { + fn create_table_statement_of(entity: E) -> TableCreateStatement + where + E: EntityTrait, + { let mut stmt = TableCreateStatement::new(); stmt.table(entity); for relation in E::Relation::iter() { @@ -14,40 +19,44 @@ pub trait CreateStatementOf let relation_trait: RelationDef = relation.def(); // foreign_key_stmt.name("Temp"); match relation_trait.from_tbl { - TableRef::Table(tbl) => { foreign_key_stmt.from_tbl(tbl); }, + TableRef::Table(tbl) => { + foreign_key_stmt.from_tbl(tbl); + } _ => todo!(), } match relation_trait.to_tbl { - TableRef::Table(tbl) => { foreign_key_stmt.to_tbl(tbl); }, + TableRef::Table(tbl) => { + foreign_key_stmt.to_tbl(tbl); + } _ => todo!(), } match relation_trait.from_col { crate::Identity::Unary(o1) => { foreign_key_stmt.from_col(o1); - }, + } crate::Identity::Binary(o1, o2) => { foreign_key_stmt.from_col(o1); foreign_key_stmt.from_col(o2); - }, + } crate::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_trait.to_col { crate::Identity::Unary(o1) => { foreign_key_stmt.to_col(o1); - }, + } crate::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); - }, + } } stmt.foreign_key(&mut foreign_key_stmt); } @@ -55,8 +64,11 @@ pub trait CreateStatementOf let sea_orm_column_def: Sea_Orm_Column_Def = col.def().into(); let mut index = IndexCreateStatement::new(); let mut sea_query_column_def = Sea_Query_Column_Def::new(col); - for key in E::PrimaryKey::iter() { // enum: Id, Name ... - if sea_query_column_def.get_column_name() == Sea_Query_Column_Def::new(key.into_column()).get_column_name() { + for key in E::PrimaryKey::iter() { + // enum: Id, Name ... + if sea_query_column_def.get_column_name() + == Sea_Query_Column_Def::new(key.into_column()).get_column_name() + { sea_query_column_def.primary_key(); if E::PrimaryKey::auto_increment() { sea_query_column_def.auto_increment(); @@ -78,66 +90,136 @@ pub trait CreateStatementOf } match Sea_Query_Column_Type::from(sea_orm_column_def.col_type) { Sea_Query_Column_Type::Char(length) => match length { - Some(length) => { sea_query_column_def.char_len(length); }, - None => { sea_query_column_def.char(); }, + Some(length) => { + sea_query_column_def.char_len(length); + } + None => { + sea_query_column_def.char(); + } }, Sea_Query_Column_Type::String(length) => match length { - Some(length) => { sea_query_column_def.string_len(length); }, - None => { sea_query_column_def.string(); }, + Some(length) => { + sea_query_column_def.string_len(length); + } + None => { + sea_query_column_def.string(); + } }, - Sea_Query_Column_Type::Text => { sea_query_column_def.text(); }, + Sea_Query_Column_Type::Text => { + sea_query_column_def.text(); + } Sea_Query_Column_Type::TinyInteger(length) => match length { - Some(length) => { sea_query_column_def.tiny_integer_len(length); }, - None => { sea_query_column_def.tiny_integer(); }, + Some(length) => { + sea_query_column_def.tiny_integer_len(length); + } + None => { + sea_query_column_def.tiny_integer(); + } }, // Sea_Query_Column_Type::TinyInteger => { sea_query_column_def.tiny_integer(); }, Sea_Query_Column_Type::SmallInteger(length) => match length { - Some(length) => { sea_query_column_def.small_integer_len(length); }, - None => { sea_query_column_def.small_integer(); }, + Some(length) => { + sea_query_column_def.small_integer_len(length); + } + None => { + sea_query_column_def.small_integer(); + } }, Sea_Query_Column_Type::Integer(length) => match length { - Some(length) => { sea_query_column_def.integer_len(length); }, - None => { sea_query_column_def.integer(); }, + Some(length) => { + sea_query_column_def.integer_len(length); + } + None => { + sea_query_column_def.integer(); + } }, Sea_Query_Column_Type::BigInteger(length) => match length { - Some(length) => { sea_query_column_def.big_integer_len(length); }, - None => { sea_query_column_def.big_integer(); }, + Some(length) => { + sea_query_column_def.big_integer_len(length); + } + None => { + sea_query_column_def.big_integer(); + } }, Sea_Query_Column_Type::Float(precision) => match precision { - Some(precision) => { sea_query_column_def.float_len(precision); }, - None => { sea_query_column_def.float(); }, + Some(precision) => { + sea_query_column_def.float_len(precision); + } + None => { + sea_query_column_def.float(); + } }, Sea_Query_Column_Type::Double(precision) => match precision { - Some(precision) => { sea_query_column_def.double_len(precision); }, - None => { sea_query_column_def.double(); }, + Some(precision) => { + sea_query_column_def.double_len(precision); + } + None => { + sea_query_column_def.double(); + } }, - Sea_Query_Column_Type::Decimal(_) => { sea_query_column_def.decimal(); }, + Sea_Query_Column_Type::Decimal(_) => { + sea_query_column_def.decimal(); + } Sea_Query_Column_Type::DateTime(precision) => match precision { - Some(precision) => { sea_query_column_def.date_time_len(precision); }, - None => { sea_query_column_def.date_time(); }, + Some(precision) => { + sea_query_column_def.date_time_len(precision); + } + None => { + sea_query_column_def.date_time(); + } }, Sea_Query_Column_Type::Timestamp(precision) => match precision { - Some(precision) => { sea_query_column_def.timestamp_len(precision); }, - None => { sea_query_column_def.timestamp(); }, + Some(precision) => { + sea_query_column_def.timestamp_len(precision); + } + None => { + sea_query_column_def.timestamp(); + } }, Sea_Query_Column_Type::Time(precision) => match precision { - Some(precision) => { sea_query_column_def.time_len(precision); }, - None => { sea_query_column_def.time(); }, + Some(precision) => { + sea_query_column_def.time_len(precision); + } + None => { + sea_query_column_def.time(); + } }, - Sea_Query_Column_Type::Date => { sea_query_column_def.date(); }, + Sea_Query_Column_Type::Date => { + sea_query_column_def.date(); + } Sea_Query_Column_Type::Binary(length) => match length { - Some(length) => { sea_query_column_def.binary_len(length); }, - None => { sea_query_column_def.binary(); }, + Some(length) => { + sea_query_column_def.binary_len(length); + } + None => { + sea_query_column_def.binary(); + } }, - Sea_Query_Column_Type::Boolean => { sea_query_column_def.boolean(); }, - Sea_Query_Column_Type::Money(_) => { sea_query_column_def.money(); }, - Sea_Query_Column_Type::Json => { sea_query_column_def.json(); }, - Sea_Query_Column_Type::JsonBinary => { sea_query_column_def.json_binary(); }, - Sea_Query_Column_Type::Custom(iden) => { sea_query_column_def.custom(Alias::new(&iden.to_string())); }, - Sea_Query_Column_Type::Uuid => { sea_query_column_def.uuid(); }, + Sea_Query_Column_Type::Boolean => { + sea_query_column_def.boolean(); + } + Sea_Query_Column_Type::Money(_) => { + sea_query_column_def.money(); + } + Sea_Query_Column_Type::Json => { + sea_query_column_def.json(); + } + Sea_Query_Column_Type::JsonBinary => { + sea_query_column_def.json_binary(); + } + Sea_Query_Column_Type::Custom(iden) => { + sea_query_column_def.custom(Alias::new(&iden.to_string())); + } + Sea_Query_Column_Type::Uuid => { + sea_query_column_def.uuid(); + } Sea_Query_Column_Type::TimestampWithTimeZone(length) => match length { - Some(length) => { sea_query_column_def.timestamp_with_time_zone_len(length); }, - None => { sea_query_column_def.timestamp_with_time_zone(); }, + Some(length) => { + sea_query_column_def.timestamp_with_time_zone_len(length); + } + None => { + sea_query_column_def.timestamp_with_time_zone(); + } }, } stmt.col(&mut sea_query_column_def); @@ -152,11 +234,12 @@ impl CreateStatementOf for EntityTrait {} #[cfg(test)] mod tests { - use crate::{CreateStatementOf, tests_cfg}; + use crate::{tests_cfg, CreateStatementOf}; #[test] fn test_create_statement_tests_cfg_cake() { - let create_statement = tests_cfg::cake::Entity::create_table_statement_of(tests_cfg::cake::Entity); + let create_statement = + tests_cfg::cake::Entity::create_table_statement_of(tests_cfg::cake::Entity); let table = format!("{:?}", create_statement.get_table_name()); let columns = format!("{:?}", create_statement.get_columns()); let relations = format!("{:?}", create_statement.get_foreign_key_create_stmts()); @@ -165,7 +248,10 @@ mod tests { assert_eq!("TableCreateStatement { table: Some(cake), columns: [ColumnDef { table: Some(cake), name: id, types: Some(Integer(None)), spec: [PrimaryKey, AutoIncrement, NotNull] }, ColumnDef { table: Some(cake), name: name, types: Some(String(None)), spec: [NotNull] }], options: [], partitions: [], indexes: [], foreign_keys: [ForeignKeyCreateStatement { foreign_key: TableForeignKey { name: None, table: Some(cake), ref_table: Some(fruit), columns: [id], ref_columns: [cake_id], on_delete: None, on_update: None } }], if_not_exists: true }", result); assert_eq!(r#"Some("cake")"#, table); assert_eq!("[ForeignKeyCreateStatement { foreign_key: TableForeignKey { name: None, table: Some(cake), ref_table: Some(fruit), columns: [id], ref_columns: [cake_id], on_delete: None, on_update: None } }]", relations); - assert_eq!(r#"[ColumnDef { table: Some(cake), name: id, types: Some(Integer(None)), spec: [PrimaryKey, AutoIncrement, NotNull] }, ColumnDef { table: Some(cake), name: name, types: Some(String(None)), spec: [NotNull] }]"#, columns); + assert_eq!( + r#"[ColumnDef { table: Some(cake), name: id, types: Some(Integer(None)), spec: [PrimaryKey, AutoIncrement, NotNull] }, ColumnDef { table: Some(cake), name: name, types: Some(String(None)), spec: [NotNull] }]"#, + columns + ); assert_eq!("[]", indexs); } } From eb6df9e8f859da9361a0090edcb271e8c0040fc3 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Tue, 31 Aug 2021 14:41:53 +0800 Subject: [PATCH 35/89] Docs --- Design.md => DESIGN.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) rename Design.md => DESIGN.md (71%) diff --git a/Design.md b/DESIGN.md similarity index 71% rename from Design.md rename to DESIGN.md index 73e88227..c14075ac 100644 --- a/Design.md +++ b/DESIGN.md @@ -20,7 +20,7 @@ After some bitterness we realized it is not possible to capture everything compi want to encounter problems at run time either. The solution is to perform checking at 'test time' to uncover problems. These checks will be removed at production so there will be no run time penalty. -## Readability +## API style ### Turbofish and inference @@ -65,4 +65,35 @@ has_many(cake::Entity, cake::Column::Id, fruit::Column::CakeId) we'd prefer having a builder and stating the params explicitly: ```rust has_many(cake::Entity).from(cake::Column::Id).to(fruit::Column::CakeId) +``` + +### Method overloading + +Consider the following two methods, which accept the same parameter but in different forms: + +```rust +fn method_with_model(m: Model) { ... } +fn method_with_active_model(a: ActiveModel) { ... } +``` + +We would define a trait + +```rust +pub trait IntoActiveModel { + fn into_active_model(self) -> ActiveModel; +} +``` + +Such that `Model` and `ActiveModel` both impl this trait. + +In this way, we can overload the two methods: + +```rust +pub fn method(a: A) +where + A: IntoActiveModel, +{ + let a: ActiveModel = a.into_active_model(); + ... +} ``` \ No newline at end of file From 6847dabb1efce16ba0540471b520c56898e4b2f2 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Tue, 31 Aug 2021 15:51:04 +0800 Subject: [PATCH 36/89] Architecture --- ARCHITECTURE.md | 39 +++++++++++++++++++++++++++++++++++++++ DESIGN.md | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 ARCHITECTURE.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 00000000..674b3163 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,39 @@ +# Architecture + +To understand the architecture of SeaORM, let's discuss what is an ORM. ORM exists to provide abstractions over common operations you would do against a database and hide the implementation details like the actual SQL queries. + +With a good ORM, you shouldn't bother to look under the API surface. Until you do. I hear you say *'abstraction leaks'*, and yes, it does. + +The approach SeaORM takes is **'layered abstraction'**, where you'd dig one layer beneath if you want to. That's why we made SeaQuery into a standalone repository. It's useful on its own, and with a public API surface and a separate namespace, it's far more difficult to create confusing internal APIs than a monolithic approach. + +The central idea in SeaORM is nearly everything is runtime configurable. At compile time, it does not know what the underlying database is. + +What benefits does database-agnostic bring? For example, you can: + +1. Make your app work on any database, depending on runtime configuration +1. Use the same models and transfer them across different databases +1. Share entities across different projects by creating a 'data structure crate', where the database is chosen by downstream 'behaviour crates' + +The API of SeaORM is not a thin shell, but consist of layers, with each layer underneath being less abstract. + +There are different stages when the API is being utilized. + +So there are two dimensions to navigate the SeaORM code base, **'stage'** and **'abstractness'**. + +First is the declaration stage. Entities and relations among them are being defined with the `EntityTrait`, `ColumnTrait` & `RelationTrait` etc. + +Second is the query building stage. + +The top most layer is `Entity`'s `find*`, `insert`, `update` & `delete` methods, where you can intuitively perform basic CRUD operations. + +One layer down, is the `Select`, `Insert`, `Update` & `Delete` structs, where they each have their own API for more advanced operations. + +One layer down, is the SeaQuery `SelectStatement`, `InsertStatement`, `UpdateStatement` & `DeleteStatement`, where they have a rich API for you to fiddle with the SQL syntax tree. + +Third is the execution stage. A separate set of structs, `Selector`, `Inserter`, `Updater` & `Deleter`, are responsible for executing the statements against a database connection. + +Finally is the resolution stage, when query results are converted into Rust structs for consumption. + +Because only the execution and resolution stages are database specific, we have the possibility to use a different driver by replacing those. + +I imagine some day, we will support a number of databases, with a matrix of different syntaxes, protocols and form-factors. \ No newline at end of file diff --git a/DESIGN.md b/DESIGN.md index c14075ac..291efc02 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,4 +1,4 @@ -# Design Goals +# Design We are heavily inspired by ActiveRecord, Eloquent and TypeORM. From dbbc8af23c865bc3107842abda656566c3082a77 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Tue, 31 Aug 2021 16:04:39 +0800 Subject: [PATCH 37/89] Readme --- README.md | 10 ++++++++-- src/lib.rs | 8 +++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f2d262e9..524aa896 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ This is an early release of SeaORM, the API is not stable yet. ``` [![Getting Started](https://img.shields.io/badge/Getting%20Started-blue)](https://www.sea-ql.org/SeaORM/docs/index) -[![Examples](https://img.shields.io/badge/Examples-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples/sqlx) +[![Examples](https://img.shields.io/badge/Examples-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples) [![Starter Kit](https://img.shields.io/badge/Starter%20Kit-green)](https://github.com/SeaQL/sea-orm/issues/37) [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv) @@ -108,7 +108,7 @@ let pear: fruit::ActiveModel = Fruit::update(pear).exec(db).await?; // update many: UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."name" LIKE '%Apple%' Fruit::update_many() - .col_expr(fruit::Column::CakeId, Expr::value(Value::Null)) + .col_expr(fruit::Column::CakeId, Expr::value(Value::Int(None))) .filter(fruit::Column::Name.contains("Apple")) .exec(db) .await?; @@ -148,6 +148,12 @@ fruit::Entity::delete_many() .await?; ``` + +## Learn More + +1. [Design](https://github.com/SeaQL/sea-orm/tree/master/DESIGN.md) +1. [Architecture](https://github.com/SeaQL/sea-orm/tree/master/ARCHITECTURE.md) + ## License Licensed under either of diff --git a/src/lib.rs b/src/lib.rs index 377bd62b..7a20d851 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ //! ``` //! //! [![Getting Started](https://img.shields.io/badge/Getting%20Started-blue)](https://www.sea-ql.org/SeaORM/docs/index) -//! [![Examples](https://img.shields.io/badge/Examples-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples/sqlx) +//! [![Examples](https://img.shields.io/badge/Examples-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples) //! [![Starter Kit](https://img.shields.io/badge/Starter%20Kit-green)](https://github.com/SeaQL/sea-orm/issues/37) //! [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv) //! @@ -180,6 +180,12 @@ //! # Ok(()) //! # } //! ``` +//! +//! ## Learn More +//! +//! 1. [Design](https://github.com/SeaQL/sea-orm/tree/master/DESIGN.md) +//! 1. [Architecture](https://github.com/SeaQL/sea-orm/tree/master/ARCHITECTURE.md) +//! //! ## License //! //! Licensed under either of From 5fbc7b146a8a853b90936a5f674d5f0101fb2448 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 31 Aug 2021 18:30:02 +0800 Subject: [PATCH 38/89] Refactoring & Testing --- Cargo.toml | 2 +- src/entity/base_entity.rs | 6 +- src/entity/column.rs | 6 +- src/entity/relation.rs | 10 +- src/entity/schema.rs | 385 ++++++++++---------------- src/query/helper.rs | 2 +- tests/common/bakery_chain/baker.rs | 2 +- tests/common/bakery_chain/bakery.rs | 2 +- tests/common/bakery_chain/cake.rs | 4 +- tests/common/bakery_chain/customer.rs | 2 +- tests/common/setup/schema.rs | 100 ++++--- 11 files changed, 220 insertions(+), 301 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ca61c819..19a79f5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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.15", git = "https://github.com/SeaQL/sea-query.git", branch = "sea-orm/create-table-stmt", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } sqlx = { version = "^0.5", optional = true } diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index d0740307..29984953 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -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) -> RelationBuilder where R: EntityTrait + Related, { - RelationBuilder::from_rel(RelationType::HasOne, R::to().rev()) + RelationBuilder::from_rel(RelationType::HasOne, R::to().rev(), true) } fn has_many(_: R) -> RelationBuilder where R: EntityTrait + Related, { - 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 diff --git a/src/entity/column.rs b/src/entity/column.rs index 611950f5..b58a181c 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -26,7 +26,7 @@ pub enum ColumnType { Timestamp, Time, Date, - Binary, + Binary(Option), Boolean, Money(Option<(u32, u32)>), Json, @@ -276,7 +276,7 @@ impl From for sea_query::ColumnType { ColumnType::Timestamp => sea_query::ColumnType::Timestamp(None), ColumnType::Time => sea_query::ColumnType::Time(None), ColumnType::Date => sea_query::ColumnType::Date, - ColumnType::Binary => sea_query::ColumnType::Binary(None), + ColumnType::Binary(s) => sea_query::ColumnType::Binary(s), ColumnType::Boolean => sea_query::ColumnType::Boolean, ColumnType::Money(s) => sea_query::ColumnType::Money(s), ColumnType::Json => sea_query::ColumnType::Json, @@ -307,7 +307,7 @@ impl From for ColumnType { sea_query::ColumnType::Timestamp(_) => Self::Timestamp, sea_query::ColumnType::Time(_) => Self::Time, sea_query::ColumnType::Date => Self::Date, - sea_query::ColumnType::Binary(_) => Self::Binary, + sea_query::ColumnType::Binary(s) => Self::Binary(s), sea_query::ColumnType::Boolean => Self::Boolean, sea_query::ColumnType::Money(s) => Self::Money(s), sea_query::ColumnType::Json => Self::Json, diff --git a/src/entity/relation.rs b/src/entity/relation.rs index 955660e2..afc8aba1 100644 --- a/src/entity/relation.rs +++ b/src/entity/relation.rs @@ -34,6 +34,7 @@ pub struct RelationDef { pub to_tbl: TableRef, pub from_col: Identity, pub to_col: Identity, + pub is_owner: bool, } pub struct RelationBuilder @@ -47,6 +48,7 @@ where to_tbl: TableRef, from_col: Option, to_col: Option, + is_owner: bool, } impl RelationDef { @@ -58,6 +60,7 @@ impl RelationDef { to_tbl: self.from_tbl, from_col: self.to_col, to_col: self.from_col, + is_owner: !self.is_owner, } } } @@ -67,7 +70,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, @@ -75,10 +78,11 @@ where to_tbl: to.table_ref(), from_col: None, to_col: None, + is_owner, } } - 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, @@ -86,6 +90,7 @@ where to_tbl: rel.to_tbl, from_col: Some(rel.from_col), to_col: Some(rel.to_col), + is_owner, } } @@ -118,6 +123,7 @@ where to_tbl: b.to_tbl, from_col: b.from_col.unwrap(), to_col: b.to_col.unwrap(), + is_owner: b.is_owner, } } } diff --git a/src/entity/schema.rs b/src/entity/schema.rs index 6c334112..e743fae0 100644 --- a/src/entity/schema.rs +++ b/src/entity/schema.rs @@ -1,257 +1,154 @@ -use crate::entity::column::ColumnTrait; -use crate::entity::primary_key::PrimaryKeyToColumn; -use crate::entity::relation::RelationTrait; -use crate::{ColumnDef as Sea_Orm_Column_Def, EntityTrait, PrimaryKeyTrait, RelationDef}; -use sea_query::{ - Alias, ColumnDef as Sea_Query_Column_Def, ColumnType as Sea_Query_Column_Type, - ForeignKeyCreateStatement, IndexCreateStatement, SeaRc, TableCreateStatement, TableRef, +use crate::{ + unpack_table_ref, ColumnTrait, EntityTrait, Identity, Iterable, PrimaryKeyToColumn, + PrimaryKeyTrait, RelationTrait, }; -pub use sea_strum::IntoEnumIterator; -pub trait CreateStatementOf { - fn create_table_statement_of(entity: E) -> TableCreateStatement - where - E: EntityTrait, - { - let mut stmt = TableCreateStatement::new(); - stmt.table(entity); - for relation in E::Relation::iter() { - let mut foreign_key_stmt = ForeignKeyCreateStatement::new(); - let relation_trait: RelationDef = relation.def(); - // foreign_key_stmt.name("Temp"); - match relation_trait.from_tbl { - TableRef::Table(tbl) => { - foreign_key_stmt.from_tbl(tbl); - } - _ => todo!(), - } - match relation_trait.to_tbl { - TableRef::Table(tbl) => { - foreign_key_stmt.to_tbl(tbl); - } - _ => todo!(), - } - match relation_trait.from_col { - crate::Identity::Unary(o1) => { - foreign_key_stmt.from_col(o1); - } - crate::Identity::Binary(o1, o2) => { - foreign_key_stmt.from_col(o1); - foreign_key_stmt.from_col(o2); - } - crate::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_trait.to_col { - crate::Identity::Unary(o1) => { - foreign_key_stmt.to_col(o1); - } - crate::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); - } - } - stmt.foreign_key(&mut foreign_key_stmt); - } - for col in E::Column::iter() { - let sea_orm_column_def: Sea_Orm_Column_Def = col.def().into(); - let mut index = IndexCreateStatement::new(); - let mut sea_query_column_def = Sea_Query_Column_Def::new(col); - for key in E::PrimaryKey::iter() { - // enum: Id, Name ... - if sea_query_column_def.get_column_name() - == Sea_Query_Column_Def::new(key.into_column()).get_column_name() - { - sea_query_column_def.primary_key(); - if E::PrimaryKey::auto_increment() { - sea_query_column_def.auto_increment(); - } - index.primary(); - } - } - if !sea_orm_column_def.null { - sea_query_column_def.not_null(); - } - if sea_orm_column_def.unique { - sea_query_column_def.unique_key(); - index.unique(); - } - if sea_orm_column_def.indexed { - index.table(entity); - index.col(col); - stmt.index(&mut index); - } - match Sea_Query_Column_Type::from(sea_orm_column_def.col_type) { - Sea_Query_Column_Type::Char(length) => match length { - Some(length) => { - sea_query_column_def.char_len(length); - } - None => { - sea_query_column_def.char(); - } - }, - Sea_Query_Column_Type::String(length) => match length { - Some(length) => { - sea_query_column_def.string_len(length); - } - None => { - sea_query_column_def.string(); - } - }, - Sea_Query_Column_Type::Text => { - sea_query_column_def.text(); - } - Sea_Query_Column_Type::TinyInteger(length) => match length { - Some(length) => { - sea_query_column_def.tiny_integer_len(length); - } - None => { - sea_query_column_def.tiny_integer(); - } - }, - // Sea_Query_Column_Type::TinyInteger => { sea_query_column_def.tiny_integer(); }, - Sea_Query_Column_Type::SmallInteger(length) => match length { - Some(length) => { - sea_query_column_def.small_integer_len(length); - } - None => { - sea_query_column_def.small_integer(); - } - }, - Sea_Query_Column_Type::Integer(length) => match length { - Some(length) => { - sea_query_column_def.integer_len(length); - } - None => { - sea_query_column_def.integer(); - } - }, - Sea_Query_Column_Type::BigInteger(length) => match length { - Some(length) => { - sea_query_column_def.big_integer_len(length); - } - None => { - sea_query_column_def.big_integer(); - } - }, - Sea_Query_Column_Type::Float(precision) => match precision { - Some(precision) => { - sea_query_column_def.float_len(precision); - } - None => { - sea_query_column_def.float(); - } - }, - Sea_Query_Column_Type::Double(precision) => match precision { - Some(precision) => { - sea_query_column_def.double_len(precision); - } - None => { - sea_query_column_def.double(); - } - }, - Sea_Query_Column_Type::Decimal(_) => { - sea_query_column_def.decimal(); - } - Sea_Query_Column_Type::DateTime(precision) => match precision { - Some(precision) => { - sea_query_column_def.date_time_len(precision); - } - None => { - sea_query_column_def.date_time(); - } - }, - Sea_Query_Column_Type::Timestamp(precision) => match precision { - Some(precision) => { - sea_query_column_def.timestamp_len(precision); - } - None => { - sea_query_column_def.timestamp(); - } - }, - Sea_Query_Column_Type::Time(precision) => match precision { - Some(precision) => { - sea_query_column_def.time_len(precision); - } - None => { - sea_query_column_def.time(); - } - }, - Sea_Query_Column_Type::Date => { - sea_query_column_def.date(); - } - Sea_Query_Column_Type::Binary(length) => match length { - Some(length) => { - sea_query_column_def.binary_len(length); - } - None => { - sea_query_column_def.binary(); - } - }, - Sea_Query_Column_Type::Boolean => { - sea_query_column_def.boolean(); - } - Sea_Query_Column_Type::Money(_) => { - sea_query_column_def.money(); - } - Sea_Query_Column_Type::Json => { - sea_query_column_def.json(); - } - Sea_Query_Column_Type::JsonBinary => { - sea_query_column_def.json_binary(); - } - Sea_Query_Column_Type::Custom(iden) => { - sea_query_column_def.custom(Alias::new(&iden.to_string())); - } - Sea_Query_Column_Type::Uuid => { - sea_query_column_def.uuid(); - } - Sea_Query_Column_Type::TimestampWithTimeZone(length) => match length { - Some(length) => { - sea_query_column_def.timestamp_with_time_zone_len(length); - } - None => { - sea_query_column_def.timestamp_with_time_zone(); - } - }, - } - stmt.col(&mut sea_query_column_def); - } - stmt.if_not_exists(); +use sea_query::{ColumnDef, ForeignKeyCreateStatement, Iden, Index, TableCreateStatement}; - stmt +pub fn entity_to_table_create_statement(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); } -} -impl CreateStatementOf for EntityTrait {} + 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); + } + } + 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::{tests_cfg, CreateStatementOf}; + use crate::{entity_to_table_create_statement, tests_cfg::*}; + use sea_query::*; #[test] - fn test_create_statement_tests_cfg_cake() { - let create_statement = - tests_cfg::cake::Entity::create_table_statement_of(tests_cfg::cake::Entity); - let table = format!("{:?}", create_statement.get_table_name()); - let columns = format!("{:?}", create_statement.get_columns()); - let relations = format!("{:?}", create_statement.get_foreign_key_create_stmts()); - let indexs = format!("{:?}", create_statement.get_indexes()); - let result = format!("{:?}", create_statement); - assert_eq!("TableCreateStatement { table: Some(cake), columns: [ColumnDef { table: Some(cake), name: id, types: Some(Integer(None)), spec: [PrimaryKey, AutoIncrement, NotNull] }, ColumnDef { table: Some(cake), name: name, types: Some(String(None)), spec: [NotNull] }], options: [], partitions: [], indexes: [], foreign_keys: [ForeignKeyCreateStatement { foreign_key: TableForeignKey { name: None, table: Some(cake), ref_table: Some(fruit), columns: [id], ref_columns: [cake_id], on_delete: None, on_update: None } }], if_not_exists: true }", result); - assert_eq!(r#"Some("cake")"#, table); - assert_eq!("[ForeignKeyCreateStatement { foreign_key: TableForeignKey { name: None, table: Some(cake), ref_table: Some(fruit), columns: [id], ref_columns: [cake_id], on_delete: None, on_update: None } }]", relations); + fn test_entity_to_table_create_statement() { assert_eq!( - r#"[ColumnDef { table: Some(cake), name: id, types: Some(Integer(None)), spec: [PrimaryKey, AutoIncrement, NotNull] }, ColumnDef { table: Some(cake), name: name, types: Some(String(None)), spec: [NotNull] }]"#, - columns + 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) ); - assert_eq!("[]", indexs); } } diff --git a/src/query/helper.rs b/src/query/helper.rs index 6ade581a..57fafe3f 100644 --- a/src/query/helper.rs +++ b/src/query/helper.rs @@ -295,7 +295,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), diff --git a/tests/common/bakery_chain/baker.rs b/tests/common/bakery_chain/baker.rs index 0c63e721..0967fcf5 100644 --- a/tests/common/bakery_chain/baker.rs +++ b/tests/common/bakery_chain/baker.rs @@ -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(), } } } diff --git a/tests/common/bakery_chain/bakery.rs b/tests/common/bakery_chain/bakery.rs index 61803329..a020cfce 100644 --- a/tests/common/bakery_chain/bakery.rs +++ b/tests/common/bakery_chain/bakery.rs @@ -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(), } } } diff --git a/tests/common/bakery_chain/cake.rs b/tests/common/bakery_chain/cake.rs index 72e649ce..29e2335a 100644 --- a/tests/common/bakery_chain/cake.rs +++ b/tests/common/bakery_chain/cake.rs @@ -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::Binary(Some(16)).def(), } } } diff --git a/tests/common/bakery_chain/customer.rs b/tests/common/bakery_chain/customer.rs index ce4319ff..7e4d9e0c 100644 --- a/tests/common/bakery_chain/customer.rs +++ b/tests/common/bakery_chain/customer.rs @@ -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(), } } } diff --git a/tests/common/setup/schema.rs b/tests/common/setup/schema.rs index 4eba40ab..4d461fd1 100644 --- a/tests/common/setup/schema.rs +++ b/tests/common/setup/schema.rs @@ -1,15 +1,29 @@ -use sea_orm::{error::*, sea_query, DbConn, ExecResult}; -use sea_query::{ColumnDef, ForeignKey, ForeignKeyAction, Index, TableCreateStatement}; +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 { +async fn create_table( + db: &DbConn, + stmt: &TableCreateStatement, + entity: E, +) -> Result +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 { - let stmt = sea_query::Table::create() + let stmt = Table::create() .table(bakery::Entity) .if_not_exists() .col( @@ -27,16 +41,17 @@ pub async fn create_bakery_table(db: &DbConn) -> Result { ) .to_owned(); - create_table(db, &stmt).await + create_table(db, &stmt, Bakery).await } pub async fn create_baker_table(db: &DbConn) -> Result { - 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,19 +64,19 @@ pub async fn create_baker_table(db: &DbConn) -> Result { .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) - .on_update(ForeignKeyAction::Cascade), + // .on_delete(ForeignKeyAction::Cascade) + // .on_update(ForeignKeyAction::Cascade), ) .to_owned(); - create_table(db, &stmt).await + create_table(db, &stmt, Baker).await } pub async fn create_customer_table(db: &DbConn) -> Result { - let stmt = sea_query::Table::create() + let stmt = Table::create() .table(customer::Entity) .if_not_exists() .col( @@ -75,11 +90,11 @@ pub async fn create_customer_table(db: &DbConn) -> Result { .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 { - let stmt = sea_query::Table::create() + let stmt = Table::create() .table(order::Entity) .if_not_exists() .col( @@ -107,27 +122,27 @@ pub async fn create_order_table(db: &DbConn) -> Result { ) .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) - .on_update(ForeignKeyAction::Cascade), + // .on_delete(ForeignKeyAction::Cascade) + // .on_update(ForeignKeyAction::Cascade), ) .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) - .on_update(ForeignKeyAction::Cascade), + // .on_delete(ForeignKeyAction::Cascade) + // .on_update(ForeignKeyAction::Cascade), ) .to_owned(); - create_table(db, &stmt).await + create_table(db, &stmt, Order).await } pub async fn create_lineitem_table(db: &DbConn) -> Result { - let stmt = sea_query::Table::create() + let stmt = Table::create() .table(lineitem::Entity) .if_not_exists() .col( @@ -159,27 +174,27 @@ pub async fn create_lineitem_table(db: &DbConn) -> Result { ) .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) - .on_update(ForeignKeyAction::Cascade), + // .on_delete(ForeignKeyAction::Cascade) + // .on_update(ForeignKeyAction::Cascade), ) .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) - .on_update(ForeignKeyAction::Cascade), + // .on_delete(ForeignKeyAction::Cascade) + // .on_update(ForeignKeyAction::Cascade), ) .to_owned(); - create_table(db, &stmt).await + create_table(db, &stmt, Lineitem).await } pub async fn create_cakes_bakers_table(db: &DbConn) -> Result { - let stmt = sea_query::Table::create() + let stmt = Table::create() .table(cakes_bakers::Entity) .if_not_exists() .col( @@ -194,32 +209,33 @@ pub async fn create_cakes_bakers_table(db: &DbConn) -> Result ) .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) - .on_update(ForeignKeyAction::Cascade), + // .on_delete(ForeignKeyAction::Cascade) + // .on_update(ForeignKeyAction::Cascade), ) .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) - .on_update(ForeignKeyAction::Cascade), + // .on_delete(ForeignKeyAction::Cascade) + // .on_update(ForeignKeyAction::Cascade), ) .to_owned(); - create_table(db, &stmt).await + create_table(db, &stmt, CakesBakers).await } pub async fn create_cake_table(db: &DbConn) -> Result { - let stmt = sea_query::Table::create() + let stmt = Table::create() .table(cake::Entity) .if_not_exists() .col( @@ -238,11 +254,11 @@ pub async fn create_cake_table(db: &DbConn) -> Result { .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) - .on_update(ForeignKeyAction::Cascade), + // .on_delete(ForeignKeyAction::Cascade) + // .on_update(ForeignKeyAction::Cascade), ) .col( ColumnDef::new(cake::Column::GlutenFree) @@ -252,5 +268,5 @@ pub async fn create_cake_table(db: &DbConn) -> Result { .col(ColumnDef::new(cake::Column::Serial).uuid().not_null()) .to_owned(); - create_table(db, &stmt).await + create_table(db, &stmt, Cake).await } From 5a0f1d0fd1887dfa6d036a00c0035ee46a5baa5c Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 31 Aug 2021 19:05:54 +0800 Subject: [PATCH 39/89] Try pretty_assertions --- Cargo.toml | 1 + src/entity/schema.rs | 1 + tests/common/setup/schema.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 19a79f5e..e303dac6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 = [] diff --git a/src/entity/schema.rs b/src/entity/schema.rs index e743fae0..930ce6aa 100644 --- a/src/entity/schema.rs +++ b/src/entity/schema.rs @@ -107,6 +107,7 @@ where #[cfg(test)] mod tests { use crate::{entity_to_table_create_statement, tests_cfg::*}; + use pretty_assertions::assert_eq; use sea_query::*; #[test] diff --git a/tests/common/setup/schema.rs b/tests/common/setup/schema.rs index 4d461fd1..321d6512 100644 --- a/tests/common/setup/schema.rs +++ b/tests/common/setup/schema.rs @@ -1,3 +1,4 @@ +use pretty_assertions::assert_eq; use sea_orm::{ entity_to_table_create_statement, error::*, sea_query, DbConn, EntityTrait, ExecResult, }; From bdf6593dfe4f8e0fbb2c33b161aac44545074a8a Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 31 Aug 2021 22:31:58 +0800 Subject: [PATCH 40/89] Hotfix --- tests/common/bakery_chain/cake.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/common/bakery_chain/cake.rs b/tests/common/bakery_chain/cake.rs index 29e2335a..3630d29a 100644 --- a/tests/common/bakery_chain/cake.rs +++ b/tests/common/bakery_chain/cake.rs @@ -56,7 +56,7 @@ impl ColumnTrait for Column { Self::Price => ColumnType::Decimal(Some((19, 4))).def(), Self::BakeryId => ColumnType::Integer.def().null(), Self::GlutenFree => ColumnType::Boolean.def(), - Self::Serial => ColumnType::Binary(Some(16)).def(), + Self::Serial => ColumnType::Uuid.def(), } } } From 1ad1767457a5e320692b9a61db7d60560695c11c Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 31 Aug 2021 23:10:39 +0800 Subject: [PATCH 41/89] cargo fmt --- tests/common/setup/schema.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/common/setup/schema.rs b/tests/common/setup/schema.rs index 321d6512..7e668f29 100644 --- a/tests/common/setup/schema.rs +++ b/tests/common/setup/schema.rs @@ -210,7 +210,7 @@ pub async fn create_cakes_bakers_table(db: &DbConn) -> Result ) .primary_key( Index::create() - .name("pk-cakes_bakers") + .name("pk-cakes_bakers") .col(cakes_bakers::Column::CakeId) .col(cakes_bakers::Column::BakerId), ) From 5073c6f4aad5b21eaafae5d63c7fbf80082cc068 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 1 Sep 2021 10:35:51 +0800 Subject: [PATCH 42/89] Relation with optional ForeignKeyAction --- src/entity/prelude.rs | 6 ++--- src/entity/relation.rs | 24 +++++++++++++++++ src/entity/schema.rs | 6 +++++ tests/common/bakery_chain/baker.rs | 2 ++ tests/common/bakery_chain/cake.rs | 2 ++ tests/common/bakery_chain/cakes_bakers.rs | 4 +++ tests/common/bakery_chain/lineitem.rs | 4 +++ tests/common/bakery_chain/order.rs | 4 +++ tests/common/setup/schema.rs | 32 +++++++++++------------ 9 files changed, 65 insertions(+), 19 deletions(-) diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs index 447117b7..c0c0f5b5 100644 --- a/src/entity/prelude.rs +++ b/src/entity/prelude.rs @@ -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, ModelTrait, - PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult, Related, RelationDef, - RelationTrait, Select, Value, + DeriveModel, DerivePrimaryKey, EntityName, EntityTrait, EnumIter, ForeignKeyAction, Iden, + IdenStatic, ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult, Related, + RelationDef, RelationTrait, Select, Value, }; #[cfg(feature = "with-json")] diff --git a/src/entity/relation.rs b/src/entity/relation.rs index afc8aba1..6e90975a 100644 --- a/src/entity/relation.rs +++ b/src/entity/relation.rs @@ -9,6 +9,8 @@ pub enum RelationType { HasMany, } +pub type ForeignKeyAction = sea_query::ForeignKeyAction; + pub trait RelationTrait: Iterable + Debug + 'static { fn def(&self) -> RelationDef; } @@ -35,6 +37,8 @@ pub struct RelationDef { pub from_col: Identity, pub to_col: Identity, pub is_owner: bool, + pub on_delete: Option, + pub on_update: Option, } pub struct RelationBuilder @@ -49,6 +53,8 @@ where from_col: Option, to_col: Option, is_owner: bool, + on_delete: Option, + on_update: Option, } impl RelationDef { @@ -61,6 +67,8 @@ impl RelationDef { 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, } } } @@ -79,6 +87,8 @@ where from_col: None, to_col: None, is_owner, + on_delete: None, + on_update: None, } } @@ -91,6 +101,8 @@ where from_col: Some(rel.from_col), to_col: Some(rel.to_col), is_owner, + on_delete: None, + on_update: None, } } @@ -109,6 +121,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 From> for RelationDef @@ -124,6 +146,8 @@ where 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, } } } diff --git a/src/entity/schema.rs b/src/entity/schema.rs index 930ce6aa..727eab06 100644 --- a/src/entity/schema.rs +++ b/src/entity/schema.rs @@ -89,6 +89,12 @@ where 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!( diff --git a/tests/common/bakery_chain/baker.rs b/tests/common/bakery_chain/baker.rs index 0967fcf5..3dff66aa 100644 --- a/tests/common/bakery_chain/baker.rs +++ b/tests/common/bakery_chain/baker.rs @@ -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(), } } diff --git a/tests/common/bakery_chain/cake.rs b/tests/common/bakery_chain/cake.rs index 3630d29a..8eaf8b9e 100644 --- a/tests/common/bakery_chain/cake.rs +++ b/tests/common/bakery_chain/cake.rs @@ -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(), } diff --git a/tests/common/bakery_chain/cakes_bakers.rs b/tests/common/bakery_chain/cakes_bakers.rs index 8106bbdf..e2067b59 100644 --- a/tests/common/bakery_chain/cakes_bakers.rs +++ b/tests/common/bakery_chain/cakes_bakers.rs @@ -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(), } } diff --git a/tests/common/bakery_chain/lineitem.rs b/tests/common/bakery_chain/lineitem.rs index 45a6037f..26ec828e 100644 --- a/tests/common/bakery_chain/lineitem.rs +++ b/tests/common/bakery_chain/lineitem.rs @@ -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(), } } diff --git a/tests/common/bakery_chain/order.rs b/tests/common/bakery_chain/order.rs index 82b02dee..6fb8d212 100644 --- a/tests/common/bakery_chain/order.rs +++ b/tests/common/bakery_chain/order.rs @@ -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(), } diff --git a/tests/common/setup/schema.rs b/tests/common/setup/schema.rs index 7e668f29..c9523572 100644 --- a/tests/common/setup/schema.rs +++ b/tests/common/setup/schema.rs @@ -68,8 +68,8 @@ pub async fn create_baker_table(db: &DbConn) -> Result { .name("fk-baker-bakery") .from(baker::Entity, baker::Column::BakeryId) .to(bakery::Entity, bakery::Column::Id) - // .on_delete(ForeignKeyAction::Cascade) - // .on_update(ForeignKeyAction::Cascade), + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), ) .to_owned(); @@ -126,16 +126,16 @@ pub async fn create_order_table(db: &DbConn) -> Result { .name("fk-order-bakery") .from(order::Entity, order::Column::BakeryId) .to(bakery::Entity, bakery::Column::Id) - // .on_delete(ForeignKeyAction::Cascade) - // .on_update(ForeignKeyAction::Cascade), + .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), + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), ) .to_owned(); @@ -178,16 +178,16 @@ pub async fn create_lineitem_table(db: &DbConn) -> Result { .name("fk-lineitem-order") .from(lineitem::Entity, lineitem::Column::OrderId) .to(order::Entity, order::Column::Id) - // .on_delete(ForeignKeyAction::Cascade) - // .on_update(ForeignKeyAction::Cascade), + .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), + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), ) .to_owned(); @@ -219,16 +219,16 @@ pub async fn create_cakes_bakers_table(db: &DbConn) -> Result .name("fk-cakes_bakers-cake") .from(cakes_bakers::Entity, cakes_bakers::Column::CakeId) .to(cake::Entity, cake::Column::Id) - // .on_delete(ForeignKeyAction::Cascade) - // .on_update(ForeignKeyAction::Cascade), + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), ) .foreign_key( ForeignKey::create() .name("fk-cakes_bakers-baker") .from(cakes_bakers::Entity, cakes_bakers::Column::BakerId) .to(baker::Entity, baker::Column::Id) - // .on_delete(ForeignKeyAction::Cascade) - // .on_update(ForeignKeyAction::Cascade), + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), ) .to_owned(); @@ -258,8 +258,8 @@ pub async fn create_cake_table(db: &DbConn) -> Result { .name("fk-cake-bakery") .from(cake::Entity, cake::Column::BakeryId) .to(bakery::Entity, bakery::Column::Id) - // .on_delete(ForeignKeyAction::Cascade) - // .on_update(ForeignKeyAction::Cascade), + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), ) .col( ColumnDef::new(cake::Column::GlutenFree) From 56a07d58c3b469727a37972be5fd4a3cac52e8fe Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 1 Sep 2021 10:37:36 +0800 Subject: [PATCH 43/89] Update sea-query dep --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e303dac6..885c8144 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", git = "https://github.com/SeaQL/sea-query.git", branch = "sea-orm/create-table-stmt", features = ["thread-safe"] } +sea-query = { version = "^0.15", git = "https://github.com/SeaQL/sea-query.git", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } sqlx = { version = "^0.5", optional = true } From ba4b938b69c8954ea22dd916faef8c4888893773 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 1 Sep 2021 13:02:57 +0800 Subject: [PATCH 44/89] Revert ColumnType::Binary changes --- src/entity/column.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/entity/column.rs b/src/entity/column.rs index b58a181c..611950f5 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -26,7 +26,7 @@ pub enum ColumnType { Timestamp, Time, Date, - Binary(Option), + Binary, Boolean, Money(Option<(u32, u32)>), Json, @@ -276,7 +276,7 @@ impl From for sea_query::ColumnType { ColumnType::Timestamp => sea_query::ColumnType::Timestamp(None), ColumnType::Time => sea_query::ColumnType::Time(None), ColumnType::Date => sea_query::ColumnType::Date, - ColumnType::Binary(s) => sea_query::ColumnType::Binary(s), + ColumnType::Binary => sea_query::ColumnType::Binary(None), ColumnType::Boolean => sea_query::ColumnType::Boolean, ColumnType::Money(s) => sea_query::ColumnType::Money(s), ColumnType::Json => sea_query::ColumnType::Json, @@ -307,7 +307,7 @@ impl From for ColumnType { sea_query::ColumnType::Timestamp(_) => Self::Timestamp, sea_query::ColumnType::Time(_) => Self::Time, sea_query::ColumnType::Date => Self::Date, - sea_query::ColumnType::Binary(s) => Self::Binary(s), + sea_query::ColumnType::Binary(_) => Self::Binary, sea_query::ColumnType::Boolean => Self::Boolean, sea_query::ColumnType::Money(s) => Self::Money(s), sea_query::ColumnType::Json => Self::Json, From 0e0ee0ede6bf37ad9d40634527fb023466334cda Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 1 Sep 2021 16:53:54 +0800 Subject: [PATCH 45/89] Add TryFromU64 & test Uuid as primary key --- src/entity/primary_key.rs | 9 ++-- src/error.rs | 2 +- src/executor/insert.rs | 16 +++---- src/executor/query.rs | 46 ++++++++++++++++++++ tests/common/bakery_chain/metadata.rs | 61 +++++++++++++++++++++++++++ tests/common/bakery_chain/mod.rs | 2 + tests/common/setup/mod.rs | 15 ++++--- tests/common/setup/schema.rs | 17 ++++++++ tests/primary_key_tests.rs | 41 ++++++++++++++++++ 9 files changed, 186 insertions(+), 23 deletions(-) create mode 100644 tests/common/bakery_chain/metadata.rs create mode 100644 tests/primary_key_tests.rs diff --git a/src/entity/primary_key.rs b/src/entity/primary_key.rs index f24c62df..9702b8a1 100644 --- a/src/entity/primary_key.rs +++ b/src/entity/primary_key.rs @@ -1,10 +1,7 @@ use super::{ColumnTrait, IdenStatic, Iterable}; -use crate::TryGetable; +use crate::{TryFromU64, TryGetable}; use sea_query::IntoValueTuple; -use std::{ - fmt::{Debug, Display}, - str::FromStr, -}; +use std::fmt::{Debug, Display}; //LINT: composite primary key cannot auto increment pub trait PrimaryKeyTrait: IdenStatic + Iterable { @@ -15,7 +12,7 @@ pub trait PrimaryKeyTrait: IdenStatic + Iterable { + PartialEq + IntoValueTuple + TryGetable - + FromStr; + + TryFromU64; fn auto_increment() -> bool; } diff --git a/src/error.rs b/src/error.rs index 8a695dac..09f80b0a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum DbErr { Conn(String), Exec(String), diff --git a/src/executor/insert.rs b/src/executor/insert.rs index e806fe62..6e657d23 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,5 +1,6 @@ use crate::{ - error::*, ActiveModelTrait, DatabaseConnection, EntityTrait, Insert, PrimaryKeyTrait, Statement, + error::*, ActiveModelTrait, DatabaseConnection, EntityTrait, Insert, PrimaryKeyTrait, + Statement, TryFromU64, }; use sea_query::InsertStatement; use std::{future::Future, marker::PhantomData}; @@ -82,20 +83,17 @@ async fn exec_insert( where A: ActiveModelTrait, { - // TODO: Postgres instead use query_one + returning clause + type ValueTypeOf = <<::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType; let last_insert_id = match db { #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => { let res = conn.query_one(statement).await?.unwrap(); res.try_get("", "last_insert_id").unwrap_or_default() } - _ => db - .execute(statement) - .await? - .last_insert_id() - .to_string() - .parse() - .unwrap_or_default(), + _ => { + let last_insert_id = db.execute(statement).await?.last_insert_id(); + ValueTypeOf::::try_from_u64(last_insert_id)? + } }; Ok(InsertResult { last_insert_id }) } diff --git a/src/executor/query.rs b/src/executor/query.rs index b7e1d12e..5420c4c9 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -405,3 +405,49 @@ impl TryGetable for Option { #[cfg(feature = "with-uuid")] try_getable_all!(uuid::Uuid); + +pub trait TryFromU64: Sized { + fn try_from_u64(n: u64) -> Result; +} + +macro_rules! try_from_u64 { + ( $type: ty ) => { + impl TryFromU64 for $type { + fn try_from_u64(n: u64) -> Result { + use std::convert::TryInto; + n.try_into().map_err(|_| { + DbErr::Exec(format!( + "fail to convert '{}' into '{}'", + n, + stringify!($type) + )) + }) + } + } + }; +} + +try_from_u64!(i8); +try_from_u64!(i16); +try_from_u64!(i32); +try_from_u64!(i64); +try_from_u64!(u8); +try_from_u64!(u16); +try_from_u64!(u32); +try_from_u64!(u64); + +macro_rules! try_from_u64_err { + ( $type: ty ) => { + impl TryFromU64 for $type { + fn try_from_u64(_: u64) -> Result { + Err(DbErr::Exec(format!( + "{} cannot be converted from u64", + stringify!($type) + ))) + } + } + }; +} + +#[cfg(feature = "with-uuid")] +try_from_u64_err!(uuid::Uuid); diff --git a/tests/common/bakery_chain/metadata.rs b/tests/common/bakery_chain/metadata.rs new file mode 100644 index 00000000..ecef26c3 --- /dev/null +++ b/tests/common/bakery_chain/metadata.rs @@ -0,0 +1,61 @@ +use sea_orm::entity::prelude::*; +use uuid::Uuid; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "metadata" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub uuid: Uuid, + pub key: String, + pub value: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Uuid, + Key, + Value, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Uuid, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = Uuid; + + fn auto_increment() -> bool { + false + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation {} + +impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Self::Uuid => ColumnType::Uuid.def(), + Self::Key => ColumnType::String(None).def(), + Self::Value => ColumnType::String(None).def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + unreachable!() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/tests/common/bakery_chain/mod.rs b/tests/common/bakery_chain/mod.rs index 89028aab..3282766d 100644 --- a/tests/common/bakery_chain/mod.rs +++ b/tests/common/bakery_chain/mod.rs @@ -4,6 +4,7 @@ pub mod cake; pub mod cakes_bakers; pub mod customer; pub mod lineitem; +pub mod metadata; pub mod order; pub use super::baker::Entity as Baker; @@ -12,4 +13,5 @@ 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::metadata::Entity as Metadata; pub use super::order::Entity as Order; diff --git a/tests/common/setup/mod.rs b/tests/common/setup/mod.rs index 7528cf6c..74e35b45 100644 --- a/tests/common/setup/mod.rs +++ b/tests/common/setup/mod.rs @@ -45,13 +45,14 @@ pub async fn setup(base_url: &str, db_name: &str) -> DatabaseConnection { Database::connect(base_url).await.unwrap() }; - 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_cake_table(&db).await.is_ok()); - assert!(schema::create_cakes_bakers_table(&db).await.is_ok()); - assert!(schema::create_lineitem_table(&db).await.is_ok()); + schema::create_bakery_table(&db).await.unwrap(); + schema::create_baker_table(&db).await.unwrap(); + schema::create_customer_table(&db).await.unwrap(); + schema::create_order_table(&db).await.unwrap(); + schema::create_cake_table(&db).await.unwrap(); + schema::create_cakes_bakers_table(&db).await.unwrap(); + schema::create_lineitem_table(&db).await.unwrap(); + schema::create_metadata_table(&db).await.unwrap(); db } diff --git a/tests/common/setup/schema.rs b/tests/common/setup/schema.rs index 4eba40ab..0e124fa2 100644 --- a/tests/common/setup/schema.rs +++ b/tests/common/setup/schema.rs @@ -254,3 +254,20 @@ pub async fn create_cake_table(db: &DbConn) -> Result { create_table(db, &stmt).await } + +pub async fn create_metadata_table(db: &DbConn) -> Result { + let stmt = sea_query::Table::create() + .table(metadata::Entity) + .if_not_exists() + .col( + ColumnDef::new(metadata::Column::Uuid) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(metadata::Column::Key).string().not_null()) + .col(ColumnDef::new(metadata::Column::Value).string().not_null()) + .to_owned(); + + create_table(db, &stmt).await +} diff --git a/tests/primary_key_tests.rs b/tests/primary_key_tests.rs new file mode 100644 index 00000000..2f74eafc --- /dev/null +++ b/tests/primary_key_tests.rs @@ -0,0 +1,41 @@ +use sea_orm::{entity::prelude::*, DatabaseConnection, Set}; +pub mod common; +pub use common::{bakery_chain::*, setup::*, TestContext}; +use uuid::Uuid; + +#[sea_orm_macros::test] +#[cfg(any( + feature = "sqlx-mysql", + feature = "sqlx-sqlite", + feature = "sqlx-postgres" +))] +async fn main() -> Result<(), DbErr> { + let ctx = TestContext::new("bakery_chain_schema_primary_key_tests").await; + + create_metadata(&ctx.db).await?; + + ctx.delete().await; + + Ok(()) +} + +async fn create_metadata(db: &DatabaseConnection) -> Result<(), DbErr> { + let metadata = metadata::ActiveModel { + uuid: Set(Uuid::new_v4()), + key: Set("markup".to_owned()), + value: Set("1.18".to_owned()), + }; + + let res = Metadata::insert(metadata.clone()).exec(db).await; + + if cfg!(feature = "sqlx-postgres") { + assert_eq!(metadata.uuid.unwrap(), res?.last_insert_id); + } else { + assert_eq!( + res.unwrap_err(), + DbErr::Exec("uuid::Uuid cannot be converted from u64".to_owned()) + ); + } + + Ok(()) +} From 13364ab63ca7f9058a04749d3abaf0e088a7c44f Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Wed, 1 Sep 2021 19:59:29 +1000 Subject: [PATCH 46/89] Use the database url in Rocket.toml to connect to mysql/postgres --- examples/rocket_example/Cargo.toml | 4 +++- examples/rocket_example/Rocket.toml | 12 ++++++++---- examples/rocket_example/src/sqlx/mod.rs | 3 ++- src/driver/rocket_db.rs | 23 +++++++++++++---------- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/examples/rocket_example/Cargo.toml b/examples/rocket_example/Cargo.toml index a8d4e736..4e645dfb 100644 --- a/examples/rocket_example/Cargo.toml +++ b/examples/rocket_example/Cargo.toml @@ -5,7 +5,9 @@ edition = "2018" publish = false [workspace] [dependencies] -rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = ["json"] } +rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = [ + "json", +] } rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", features = [] } sea-orm = { path = "../../", features = ["web-api-rocket"] } sea-query = { version = "^0.12.8" } diff --git a/examples/rocket_example/Rocket.toml b/examples/rocket_example/Rocket.toml index 0fe9f8eb..bf5098f8 100644 --- a/examples/rocket_example/Rocket.toml +++ b/examples/rocket_example/Rocket.toml @@ -1,5 +1,9 @@ -# [default.databases.sqlx] -# url = "sqlite::memory:" +[default.databases.rocket_example] -[global.databases] -blog = { url = "sqlite::memory:" } +# Mysql +# make sure to enable "sqlx-mysql" feature in Cargo.toml, i.e default = ["sqlx-mysql"] +# url = "mysql://root:@localhost/rocket_example" + +# Postgres +# make sure to enable "sqlx-postgres" feature in Cargo.toml, i.e default = ["sqlx-postgres"] +url = "postgres://root:root@localhost/rocket_example" diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index 1b43ea67..6273a49d 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -4,12 +4,13 @@ use rocket::serde::json::Json; use rocket::{Build, Rocket}; use rocket_db_pools::{sqlx, Connection, Database}; use sea_orm::entity::*; +use sea_orm::RocketDbPool; mod setup; #[derive(Database, Debug)] #[database("rocket_example")] -struct Db(sea_orm::Database); +struct Db(RocketDbPool); type Result> = std::result::Result; diff --git a/src/driver/rocket_db.rs b/src/driver/rocket_db.rs index 755a7fec..ac53fdd7 100644 --- a/src/driver/rocket_db.rs +++ b/src/driver/rocket_db.rs @@ -1,24 +1,27 @@ use async_trait::async_trait; use rocket_db_pools::{rocket::figment::Figment, Config, Error}; +#[derive(Debug)] +pub struct RocketDbPool { + db_url: String, +} + #[async_trait] -impl rocket_db_pools::Pool for crate::Database { +impl rocket_db_pools::Pool for RocketDbPool { type Error = crate::DbErr; type Connection = crate::DatabaseConnection; async fn init(figment: &Figment) -> Result { - Ok(crate::Database {}) + let config = figment.extract::().unwrap(); + let db_url = config.url; + + Ok(RocketDbPool { + db_url: db_url.to_owned(), + }) } async fn get(&self) -> Result { - #[cfg(feature = "sqlx-mysql")] - let db_url = "mysql://root:@localhost/rocket_example"; - #[cfg(feature = "sqlx-postgres")] - let db_url = "postgres://root:root@localhost/rocket_example"; - - println!("db_url: {:#?}", db_url); - - Ok(crate::Database::connect(db_url).await.unwrap()) + Ok(crate::Database::connect(&self.db_url).await.unwrap()) } } From b7d4dd45c4fd32923db1a1f82bdcdd0e6bb6871a Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Wed, 1 Sep 2021 20:09:38 +1000 Subject: [PATCH 47/89] Clean up old file, add to README --- examples/rocket_example/README.md | 5 ++++- examples/rocket_example/db/sqlx/db.sqlite | Bin 24576 -> 0 bytes examples/rocket_example/db/sqlx/db.sqlite-shm | Bin 32768 -> 0 bytes examples/rocket_example/db/sqlx/db.sqlite-wal | 0 .../20210331024424_create-posts-table.sql | 6 ------ 5 files changed, 4 insertions(+), 7 deletions(-) delete mode 100644 examples/rocket_example/db/sqlx/db.sqlite delete mode 100644 examples/rocket_example/db/sqlx/db.sqlite-shm delete mode 100644 examples/rocket_example/db/sqlx/db.sqlite-wal delete mode 100644 examples/rocket_example/db/sqlx/migrations/20210331024424_create-posts-table.sql diff --git a/examples/rocket_example/README.md b/examples/rocket_example/README.md index 122bf09d..ad8b8918 100644 --- a/examples/rocket_example/README.md +++ b/examples/rocket_example/README.md @@ -1,3 +1,6 @@ # Rocket with SeaOrm example app -`cargo run` in the `rocket_example` folder +- modify the `url` var in `Rocket.toml` to point to your chosen database +- turn on the appropriate database feature for your chosen db in `Cargo.toml` +- `cargo run` to start the server +- open browser to the address shown in `🚀 Rocket has launched from ` line diff --git a/examples/rocket_example/db/sqlx/db.sqlite b/examples/rocket_example/db/sqlx/db.sqlite deleted file mode 100644 index 362447f5c77269de04bcdc876be3d77e0012ecbe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 zcmeI4L2TPp7{~3lX`7-FG_*w%5_$+tTeYUV=jZs@(*#RL*a~UWr718?C3?1>*RV9{ zVh43XXkv`RG$c+)8wYmffVd*D0~aJF!461Vci_Mk35g*jK=7QbWv{s5(1=6%UhFi_ zpa0)`-_KwCZ0n^m_Oq}!G74&kUa`JScf)jKDOQXZzHV zTLY6Cj0e7xCl)cOjV;F}f{?%#)bwS|Bc{oWnTs9KzBG8Zz6iZ`-4!!ltI=6&hAZgB zrSihSdcEa-owxa#h(tS_snpM}Mk{D;p*r|xnw>@?K0<03)H7_y=Z?+fF<_h!;t<~z zzL)oy?c0{bZ*j|~7GuN>giDNoQo>!2TLyEyUaAwDy*{MGv)%e2v^V5P zF2!he?o>RGKNMaspk9ux#|@xr@l_d*5>veyM!X@fq-wE!ZoarwEzB=&?X=bVlgpKg zgjCzwrRP2Dc=9lY|A@M0^;U&<{0%_4)D4 z&nG5iTzcjgO)gM?01yBIKmZ5;0U!VbfB+Bx0zd!=0D(P4AU&K)_ns?EX1>>kGIzAh z51BiU{P^#$J%s^+0Rlh(2mk>f00e*l5C8&uoWRJwLubb4FQG*_FC68Nfy;8HIqIy- z`OO@~bB(8w{2j1?54VulXn7ZAW@h9sf$u3I;v|ZQql!LOMRu~tRz;yIV#y+=ik?+P zR|WKeTKHxx;7DyW&D{i#Tj6G6=b?HfgsoCazZ zXg3u}auTRHpaqH~IR%Wv@{IN5!R&#QcJR=6JU;*#yxEFYMH|I6X&pJZUmB;mia0@0xq_|L<#=`+NMv2jKw$AOHk_01yBIKmZ5;0U!VbfB+EqzY$35!&yCk8W0|c l@BigL{euDofB+Bx0zd!=00AHX1b_e#00KY&2<%}3e*uejcHaO1 diff --git a/examples/rocket_example/db/sqlx/db.sqlite-shm b/examples/rocket_example/db/sqlx/db.sqlite-shm deleted file mode 100644 index fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeIuAr62r3 Date: Wed, 1 Sep 2021 18:23:23 +0800 Subject: [PATCH 48/89] WIP: composite primary keys --- src/executor/insert.rs | 4 +- src/executor/query.rs | 83 +++++++++++++++++++++++++------------- tests/primary_key_tests.rs | 14 +++---- 3 files changed, 65 insertions(+), 36 deletions(-) diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 6e657d23..ce8f32d7 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -92,7 +92,9 @@ where } _ => { let last_insert_id = db.execute(statement).await?.last_insert_id(); - ValueTypeOf::::try_from_u64(last_insert_id)? + ValueTypeOf::::try_from_u64(last_insert_id) + .ok() + .unwrap_or_default() } }; Ok(InsertResult { last_insert_id }) diff --git a/src/executor/query.rs b/src/executor/query.rs index 5420c4c9..2d9d4db0 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -410,32 +410,6 @@ pub trait TryFromU64: Sized { fn try_from_u64(n: u64) -> Result; } -macro_rules! try_from_u64 { - ( $type: ty ) => { - impl TryFromU64 for $type { - fn try_from_u64(n: u64) -> Result { - use std::convert::TryInto; - n.try_into().map_err(|_| { - DbErr::Exec(format!( - "fail to convert '{}' into '{}'", - n, - stringify!($type) - )) - }) - } - } - }; -} - -try_from_u64!(i8); -try_from_u64!(i16); -try_from_u64!(i32); -try_from_u64!(i64); -try_from_u64!(u8); -try_from_u64!(u16); -try_from_u64!(u32); -try_from_u64!(u64); - macro_rules! try_from_u64_err { ( $type: ty ) => { impl TryFromU64 for $type { @@ -449,5 +423,60 @@ macro_rules! try_from_u64_err { }; } +macro_rules! try_from_u64_tuple { + ( $type: ty ) => { + try_from_u64_err!(($type, $type)); + try_from_u64_err!(($type, $type, $type)); + }; +} + +macro_rules! try_from_u64_numeric { + ( $type: ty ) => { + impl TryFromU64 for $type { + fn try_from_u64(n: u64) -> Result { + use std::convert::TryInto; + n.try_into().map_err(|_| { + DbErr::Exec(format!( + "fail to convert '{}' into '{}'", + n, + stringify!($type) + )) + }) + } + } + try_from_u64_tuple!($type); + }; +} + +try_from_u64_numeric!(i8); +try_from_u64_numeric!(i16); +try_from_u64_numeric!(i32); +try_from_u64_numeric!(i64); +try_from_u64_numeric!(u8); +try_from_u64_numeric!(u16); +try_from_u64_numeric!(u32); +try_from_u64_numeric!(u64); + +macro_rules! try_from_u64_string { + ( $type: ty ) => { + impl TryFromU64 for $type { + fn try_from_u64(n: u64) -> Result { + Ok(n.to_string()) + } + } + try_from_u64_tuple!($type); + }; +} + +try_from_u64_string!(String); + +macro_rules! try_from_u64_dummy { + ( $type: ty ) => { + try_from_u64_err!($type); + try_from_u64_err!(($type, $type)); + try_from_u64_err!(($type, $type, $type)); + }; +} + #[cfg(feature = "with-uuid")] -try_from_u64_err!(uuid::Uuid); +try_from_u64_dummy!(uuid::Uuid); diff --git a/tests/primary_key_tests.rs b/tests/primary_key_tests.rs index 2f74eafc..15fdfe43 100644 --- a/tests/primary_key_tests.rs +++ b/tests/primary_key_tests.rs @@ -26,16 +26,14 @@ async fn create_metadata(db: &DatabaseConnection) -> Result<(), DbErr> { value: Set("1.18".to_owned()), }; - let res = Metadata::insert(metadata.clone()).exec(db).await; + let res = Metadata::insert(metadata.clone()).exec(db).await?; - if cfg!(feature = "sqlx-postgres") { - assert_eq!(metadata.uuid.unwrap(), res?.last_insert_id); + let expected_uuid = if cfg!(feature = "sqlx-postgres") { + metadata.uuid.unwrap() } else { - assert_eq!( - res.unwrap_err(), - DbErr::Exec("uuid::Uuid cannot be converted from u64".to_owned()) - ); - } + Uuid::default() + }; + assert_eq!(res.last_insert_id, expected_uuid); Ok(()) } From d14322ad752472b4f2f2d7132922272492fdeeaa Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 1 Sep 2021 19:05:59 +0800 Subject: [PATCH 49/89] WIP (workflow will fail) --- .../async-std/src/example_cake_filling.rs | 2 +- sea-orm-codegen/tests/entity/cake_filling.rs | 2 +- src/entity/active_model.rs | 4 +- src/entity/primary_key.rs | 7 +- src/executor/query.rs | 72 +++++++++++++++++-- src/tests_cfg/cake_filling.rs | 2 +- src/tests_cfg/cake_filling_price.rs | 2 +- tests/common/bakery_chain/cakes_bakers.rs | 2 +- 8 files changed, 77 insertions(+), 16 deletions(-) diff --git a/examples/async-std/src/example_cake_filling.rs b/examples/async-std/src/example_cake_filling.rs index c24e7296..4fa188bc 100644 --- a/examples/async-std/src/example_cake_filling.rs +++ b/examples/async-std/src/example_cake_filling.rs @@ -28,7 +28,7 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { - type ValueType = i32; + type ValueType = (i32, i32); fn auto_increment() -> bool { false diff --git a/sea-orm-codegen/tests/entity/cake_filling.rs b/sea-orm-codegen/tests/entity/cake_filling.rs index 227740c1..d100fa8c 100644 --- a/sea-orm-codegen/tests/entity/cake_filling.rs +++ b/sea-orm-codegen/tests/entity/cake_filling.rs @@ -30,7 +30,7 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { - type ValueType = i32; + type ValueType = (i32, i32); fn auto_increment() -> bool { false diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index 612e285d..f823411c 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -225,16 +225,14 @@ where if ::auto_increment() && res.last_insert_id != ::ValueType::default() { - let last_insert_id = res.last_insert_id.to_string(); let find = E::find_by_id(res.last_insert_id).one(db); let found = find.await; let model: Option = found?; match model { Some(model) => Ok(model.into_active_model()), None => Err(DbErr::Exec(format!( - "Failed to find inserted item: {} {}", + "Failed to find inserted item: {}", E::default().to_string(), - last_insert_id ))), } } else { diff --git a/src/entity/primary_key.rs b/src/entity/primary_key.rs index 9702b8a1..b9c381d4 100644 --- a/src/entity/primary_key.rs +++ b/src/entity/primary_key.rs @@ -1,17 +1,16 @@ use super::{ColumnTrait, IdenStatic, Iterable}; -use crate::{TryFromU64, TryGetable}; +use crate::{TryFromU64, TryGetableMany}; use sea_query::IntoValueTuple; -use std::fmt::{Debug, Display}; +use std::fmt::Debug; //LINT: composite primary key cannot auto increment pub trait PrimaryKeyTrait: IdenStatic + Iterable { type ValueType: Sized + Default + Debug - + Display + PartialEq + IntoValueTuple - + TryGetable + + TryGetableMany + TryFromU64; fn auto_increment() -> bool; diff --git a/src/executor/query.rs b/src/executor/query.rs index 2d9d4db0..88d6a157 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -17,10 +17,8 @@ pub(crate) enum QueryResultRow { Mock(crate::MockRow), } -pub trait TryGetable { - fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result - where - Self: Sized; +pub trait TryGetable: Sized { + fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result; } // QueryResult // @@ -406,6 +404,72 @@ impl TryGetable for Option { #[cfg(feature = "with-uuid")] try_getable_all!(uuid::Uuid); +// TryGetableMany // + +pub trait TryGetableMany: Sized { + fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result; +} + +impl TryGetableMany for T +where + T: TryGetable, +{ + fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { + let expect_len = 1; + if cols.len() < expect_len { + return Err(DbErr::Query(format!( + "Expect {} column names supplied but got slice of length {}", + expect_len, + cols.len() + ))); + } + T::try_get(res, pre, &cols[0]) + } +} + +impl TryGetableMany for (T, T) +where + T: TryGetable, +{ + fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { + let expect_len = 2; + if cols.len() < expect_len { + return Err(DbErr::Query(format!( + "Expect {} column names supplied but got slice of length {}", + expect_len, + cols.len() + ))); + } + Ok(( + T::try_get(res, pre, &cols[0])?, + T::try_get(res, pre, &cols[1])?, + )) + } +} + +impl TryGetableMany for (T, T, T) +where + T: TryGetable, +{ + fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { + let expect_len = 3; + if cols.len() < expect_len { + return Err(DbErr::Query(format!( + "Expect {} column names supplied but got slice of length {}", + expect_len, + cols.len() + ))); + } + Ok(( + T::try_get(res, pre, &cols[0])?, + T::try_get(res, pre, &cols[1])?, + T::try_get(res, pre, &cols[2])?, + )) + } +} + +// TryFromU64 // + pub trait TryFromU64: Sized { fn try_from_u64(n: u64) -> Result; } diff --git a/src/tests_cfg/cake_filling.rs b/src/tests_cfg/cake_filling.rs index 4336d2fa..ed6d0af0 100644 --- a/src/tests_cfg/cake_filling.rs +++ b/src/tests_cfg/cake_filling.rs @@ -29,7 +29,7 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { - type ValueType = i32; + type ValueType = (i32, i32); fn auto_increment() -> bool { false diff --git a/src/tests_cfg/cake_filling_price.rs b/src/tests_cfg/cake_filling_price.rs index bd2bc8eb..e820ae0f 100644 --- a/src/tests_cfg/cake_filling_price.rs +++ b/src/tests_cfg/cake_filling_price.rs @@ -35,7 +35,7 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { - type ValueType = i32; + type ValueType = (i32, i32); fn auto_increment() -> bool { false diff --git a/tests/common/bakery_chain/cakes_bakers.rs b/tests/common/bakery_chain/cakes_bakers.rs index 853cf6da..98a25682 100644 --- a/tests/common/bakery_chain/cakes_bakers.rs +++ b/tests/common/bakery_chain/cakes_bakers.rs @@ -28,7 +28,7 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { - type ValueType = i32; + type ValueType = (i32, i32); fn auto_increment() -> bool { false From 23f2a3d7119eb61efdcb70d27a2e8ea2045761aa Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 1 Sep 2021 22:04:43 +0800 Subject: [PATCH 50/89] Hotfix - git merge conflict --- src/executor/query.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/executor/query.rs b/src/executor/query.rs index 8546a5e7..7be17b4b 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -18,7 +18,7 @@ pub(crate) enum QueryResultRow { } pub trait TryGetable: Sized { - fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result; + fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result; } pub enum TryGetError { From d664985ea9fd1a80dd49c1ee798b807ca959eff8 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 1 Sep 2021 23:24:43 +0800 Subject: [PATCH 51/89] WIP --- sea-orm-codegen/src/entity/base_entity.rs | 25 +++++++--- src/executor/insert.rs | 2 +- src/executor/query.rs | 56 ++++++++++------------- 3 files changed, 45 insertions(+), 38 deletions(-) diff --git a/sea-orm-codegen/src/entity/base_entity.rs b/sea-orm-codegen/src/entity/base_entity.rs index 2cc6dbb4..7b28f9e4 100644 --- a/sea-orm-codegen/src/entity/base_entity.rs +++ b/sea-orm-codegen/src/entity/base_entity.rs @@ -118,12 +118,25 @@ impl Entity { } pub fn get_primary_key_rs_type(&self) -> TokenStream { - if let Some(primary_key) = self.primary_keys.first() { - self.columns - .iter() - .find(|col| col.name.eq(&primary_key.name)) - .unwrap() - .get_rs_type() + let types = self + .primary_keys + .iter() + .map(|primary_key| { + self.columns + .iter() + .find(|col| col.name.eq(&primary_key.name)) + .unwrap() + .get_rs_type() + .to_string() + }) + .collect::>(); + if !types.is_empty() { + let value_type = if types.len() > 1 { + vec!["(".to_owned(), types.join(", "), ")".to_owned()] + } else { + types + }; + value_type.join("").parse().unwrap() } else { TokenStream::new() } diff --git a/src/executor/insert.rs b/src/executor/insert.rs index ce8f32d7..a0577e4b 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -88,7 +88,7 @@ where #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => { let res = conn.query_one(statement).await?.unwrap(); - res.try_get("", "last_insert_id").unwrap_or_default() + res.try_get_many("", "last_insert_id").unwrap_or_default() } _ => { let last_insert_id = db.execute(statement).await?.last_insert_id(); diff --git a/src/executor/query.rs b/src/executor/query.rs index 7be17b4b..407e963c 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -262,9 +262,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")] @@ -282,22 +285,15 @@ try_getable_all!(uuid::Uuid); // TryGetableMany // pub trait TryGetableMany: Sized { - fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result; + fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result; } impl TryGetableMany for T where T: TryGetable, { - fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { - let expect_len = 1; - if cols.len() < expect_len { - return Err(DbErr::Query(format!( - "Expect {} column names supplied but got slice of length {}", - expect_len, - cols.len() - ))); - } + fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { + try_get_many_with_slice_len_of(1, cols)?; T::try_get(res, pre, &cols[0]) } } @@ -306,15 +302,8 @@ impl TryGetableMany for (T, T) where T: TryGetable, { - fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { - let expect_len = 2; - if cols.len() < expect_len { - return Err(DbErr::Query(format!( - "Expect {} column names supplied but got slice of length {}", - expect_len, - cols.len() - ))); - } + fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { + try_get_many_with_slice_len_of(2, cols)?; Ok(( T::try_get(res, pre, &cols[0])?, T::try_get(res, pre, &cols[1])?, @@ -326,15 +315,8 @@ impl TryGetableMany for (T, T, T) where T: TryGetable, { - fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { - let expect_len = 3; - if cols.len() < expect_len { - return Err(DbErr::Query(format!( - "Expect {} column names supplied but got slice of length {}", - expect_len, - cols.len() - ))); - } + fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { + try_get_many_with_slice_len_of(3, cols)?; Ok(( T::try_get(res, pre, &cols[0])?, T::try_get(res, pre, &cols[1])?, @@ -343,6 +325,18 @@ where } } +fn try_get_many_with_slice_len_of(len: usize, cols: &[String]) -> Result<(), TryGetError> { + if cols.len() < len { + Err(TryGetError::DbErr(DbErr::Query(format!( + "Expect {} column names supplied but got slice of length {}", + len, + cols.len() + )))) + } else { + Ok(()) + } +} + // TryFromU64 // pub trait TryFromU64: Sized { From e72d8a9e049e4656ca4918021aee1ccfa2cc7fab Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 2 Sep 2021 11:26:40 +0800 Subject: [PATCH 52/89] Use TryGetableMany --- src/executor/insert.rs | 18 +++++++++++------- src/executor/query.rs | 7 +++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/executor/insert.rs b/src/executor/insert.rs index a0577e4b..90065675 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -38,13 +38,12 @@ where let mut query = self.query; #[cfg(feature = "sqlx-postgres")] if let DatabaseConnection::SqlxPostgresPoolConnection(_) = db { - use crate::Iterable; - use sea_query::{Alias, Expr, Query}; - for key in ::PrimaryKey::iter() { + use crate::{sea_query::Query, Iterable}; + if ::PrimaryKey::iter().count() > 0 { query.returning( Query::select() - .expr_as(Expr::col(key), Alias::new("last_insert_id")) - .to_owned(), + .columns(::PrimaryKey::iter()) + .take(), ); } } @@ -83,12 +82,17 @@ async fn exec_insert( where A: ActiveModelTrait, { - type ValueTypeOf = <<::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType; + type PrimaryKey = <::Entity as EntityTrait>::PrimaryKey; + type ValueTypeOf = as PrimaryKeyTrait>::ValueType; let last_insert_id = match db { #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => { + use crate::{sea_query::Iden, Iterable}; + let cols = PrimaryKey::::iter() + .map(|col| col.to_string()) + .collect::>(); let res = conn.query_one(statement).await?.unwrap(); - res.try_get_many("", "last_insert_id").unwrap_or_default() + res.try_get_many("", cols.as_ref()).unwrap_or_default() } _ => { let last_insert_id = db.execute(statement).await?.last_insert_id(); diff --git a/src/executor/query.rs b/src/executor/query.rs index 407e963c..1c2ce4a1 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -44,6 +44,13 @@ impl QueryResult { { Ok(T::try_get(self, pre, col)?) } + + pub fn try_get_many(&self, pre: &str, cols: &[String]) -> Result + where + T: TryGetableMany, + { + Ok(T::try_get_many(self, pre, cols)?) + } } impl fmt::Debug for QueryResultRow { From 8e2b7e561e6f1f209f366286bf25a122939ccd20 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 2 Sep 2021 11:47:57 +0800 Subject: [PATCH 53/89] Testing composite primary keys --- tests/crud/create_cake.rs | 10 +++++++++- tests/crud/create_lineitem.rs | 10 +++++++++- tests/crud/create_order.rs | 10 +++++++++- tests/primary_key_tests.rs | 14 ++++++++------ tests/sequential_op_tests.rs | 20 ++++++++++++++++++-- 5 files changed, 53 insertions(+), 11 deletions(-) diff --git a/tests/crud/create_cake.rs b/tests/crud/create_cake.rs index fb4566eb..eea74350 100644 --- a/tests/crud/create_cake.rs +++ b/tests/crud/create_cake.rs @@ -53,10 +53,18 @@ pub async fn test_create_cake(db: &DbConn) { baker_id: Set(baker_insert_res.last_insert_id as i32), ..Default::default() }; - let _cake_baker_res = CakesBakers::insert(cake_baker) + let cake_baker_res = CakesBakers::insert(cake_baker.clone()) .exec(db) .await .expect("could not insert cake_baker"); + assert_eq!( + cake_baker_res.last_insert_id, + if cfg!(feature = "sqlx-postgres") { + (cake_baker.cake_id.unwrap(), cake_baker.baker_id.unwrap()) + } else { + Default::default() + } + ); assert!(cake.is_some()); let cake_model = cake.unwrap(); diff --git a/tests/crud/create_lineitem.rs b/tests/crud/create_lineitem.rs index 1030651a..9acba961 100644 --- a/tests/crud/create_lineitem.rs +++ b/tests/crud/create_lineitem.rs @@ -52,10 +52,18 @@ pub async fn test_create_lineitem(db: &DbConn) { baker_id: Set(baker_insert_res.last_insert_id as i32), ..Default::default() }; - let _cake_baker_res = CakesBakers::insert(cake_baker) + let cake_baker_res = CakesBakers::insert(cake_baker.clone()) .exec(db) .await .expect("could not insert cake_baker"); + assert_eq!( + cake_baker_res.last_insert_id, + if cfg!(feature = "sqlx-postgres") { + (cake_baker.cake_id.unwrap(), cake_baker.baker_id.unwrap()) + } else { + Default::default() + } + ); // Customer let customer_kate = customer::ActiveModel { diff --git a/tests/crud/create_order.rs b/tests/crud/create_order.rs index e3b12b52..8d10cfcd 100644 --- a/tests/crud/create_order.rs +++ b/tests/crud/create_order.rs @@ -52,10 +52,18 @@ pub async fn test_create_order(db: &DbConn) { baker_id: Set(baker_insert_res.last_insert_id as i32), ..Default::default() }; - let _cake_baker_res = CakesBakers::insert(cake_baker) + let cake_baker_res = CakesBakers::insert(cake_baker.clone()) .exec(db) .await .expect("could not insert cake_baker"); + assert_eq!( + cake_baker_res.last_insert_id, + if cfg!(feature = "sqlx-postgres") { + (cake_baker.cake_id.unwrap(), cake_baker.baker_id.unwrap()) + } else { + Default::default() + } + ); // Customer let customer_kate = customer::ActiveModel { diff --git a/tests/primary_key_tests.rs b/tests/primary_key_tests.rs index 15fdfe43..74dd46e5 100644 --- a/tests/primary_key_tests.rs +++ b/tests/primary_key_tests.rs @@ -28,12 +28,14 @@ async fn create_metadata(db: &DatabaseConnection) -> Result<(), DbErr> { let res = Metadata::insert(metadata.clone()).exec(db).await?; - let expected_uuid = if cfg!(feature = "sqlx-postgres") { - metadata.uuid.unwrap() - } else { - Uuid::default() - }; - assert_eq!(res.last_insert_id, expected_uuid); + assert_eq!( + res.last_insert_id, + if cfg!(feature = "sqlx-postgres") { + metadata.uuid.unwrap() + } else { + Default::default() + } + ); Ok(()) } diff --git a/tests/sequential_op_tests.rs b/tests/sequential_op_tests.rs index 300812cf..6c490762 100644 --- a/tests/sequential_op_tests.rs +++ b/tests/sequential_op_tests.rs @@ -78,10 +78,18 @@ async fn init_setup(db: &DatabaseConnection) { ..Default::default() }; - let _cake_baker_res = CakesBakers::insert(cake_baker) + let cake_baker_res = CakesBakers::insert(cake_baker.clone()) .exec(db) .await .expect("could not insert cake_baker"); + assert_eq!( + cake_baker_res.last_insert_id, + if cfg!(feature = "sqlx-postgres") { + (cake_baker.cake_id.unwrap(), cake_baker.baker_id.unwrap()) + } else { + Default::default() + } + ); let customer_kate = customer::ActiveModel { name: Set("Kate".to_owned()), @@ -211,10 +219,18 @@ async fn create_cake(db: &DatabaseConnection, baker: baker::Model) -> Option Date: Thu, 2 Sep 2021 16:30:57 +0800 Subject: [PATCH 54/89] Update tests --- src/executor/select.rs | 2 +- src/query/helper.rs | 29 +++++++++++++++++- tests/relational_tests.rs | 63 +++++++++++++++++++++++++++++++-------- 3 files changed, 79 insertions(+), 15 deletions(-) diff --git a/src/executor/select.rs b/src/executor/select.rs index 6196ec73..836ea495 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -128,7 +128,7 @@ where E: EntityTrait, F: EntityTrait, { - fn into_model(self) -> Selector> + pub fn into_model(self) -> Selector> where M: FromQueryResult, N: FromQueryResult, diff --git a/src/query/helper.rs b/src/query/helper.rs index 6ade581a..4ca44905 100644 --- a/src/query/helper.rs +++ b/src/query/helper.rs @@ -3,7 +3,7 @@ use crate::{ RelationDef, }; use sea_query::{ - Alias, Expr, IntoCondition, SeaRc, SelectExpr, SelectStatement, SimpleExpr, TableRef, + Alias, Expr, Iden, IntoCondition, SeaRc, SelectExpr, SelectStatement, SimpleExpr, TableRef, }; pub use sea_query::{Condition, ConditionalStatement, DynIden, JoinType, Order, OrderedStatement}; @@ -66,6 +66,33 @@ pub trait QuerySelect: Sized { self } + /// Add a select column with prefixed alias + /// ``` + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; + /// + /// assert_eq!( + /// cake::Entity::find() + /// .select_only() + /// .column_as_prefixed(cake::Column::Id.count(), "A_", cake::Column::Id) + /// .build(DbBackend::Postgres) + /// .to_string(), + /// r#"SELECT COUNT("cake"."id") AS "A_id" FROM "cake""# + /// ); + /// ``` + fn column_as_prefixed(mut self, col: C, prefix: &str, alias: I) -> Self + where + C: IntoSimpleExpr, + I: Iden, + { + self.query().expr(SelectExpr { + expr: col.into_simple_expr(), + alias: Some(SeaRc::new(Alias::new( + vec![prefix, alias.to_string().as_str()].join("").as_str(), + ))), + }); + self + } + /// Add a group by column /// ``` /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; diff --git a/tests/relational_tests.rs b/tests/relational_tests.rs index 1ddee902..f67d4e88 100644 --- a/tests/relational_tests.rs +++ b/tests/relational_tests.rs @@ -652,23 +652,60 @@ pub async fn linked() -> Result<(), DbErr> { .save(&ctx.db) .await?; - /* - SELECT `baker`.`id` AS `A_id`, `baker`.`name` AS `A_name`, - `baker`.`contact_details` AS `A_contact_details`, - `baker`.`bakery_id` AS `A_bakery_id`, `customer`.`id` AS `B_id`, - `customer`.`name` AS `B_name`, `customer`.`notes` AS `B_notes` - FROM `baker` - LEFT JOIN `cakes_bakers` ON `baker`.`id` = `cakes_bakers`.`baker_id` - LEFT JOIN `cake` ON `cakes_bakers`.`cake_id` = `cake`.`id` - LEFT JOIN `lineitem` ON `cake`.`id` = `lineitem`.`cake_id` - LEFT JOIN `order` ON `lineitem`.`order_id` = `order`.`id` - LEFT JOIN `customer` ON `order`.`customer_id` = `customer`.`id` - */ + #[derive(Debug, FromQueryResult, PartialEq)] + struct BakerLite { + name: String, + } + + #[derive(Debug, FromQueryResult, PartialEq)] + struct CustomerLite { + name: String, + } + let baked_for_customers = Baker::find() .find_also_linked(baker::BakedForCustomer) + .select_only() + .column_as_prefixed(baker::Column::Name, "A_", baker::Column::Name) + .column_as_prefixed(customer::Column::Name, "B_", customer::Column::Name) + .group_by(baker::Column::Id) + .group_by(customer::Column::Id) + .group_by(baker::Column::Name) + .group_by(customer::Column::Name) + .order_by_asc(baker::Column::Id) + .order_by_asc(customer::Column::Id) + .into_model::() .all(&ctx.db) .await?; - println!("{:#?}", baked_for_customers); + + assert_eq!( + baked_for_customers, + vec![ + ( + BakerLite { + name: "Baker Bob".to_owned(), + }, + Some(CustomerLite { + name: "Kara".to_owned(), + }) + ), + ( + BakerLite { + name: "Baker Bobby".to_owned(), + }, + Some(CustomerLite { + name: "Kate".to_owned(), + }) + ), + ( + BakerLite { + name: "Baker Bobby".to_owned(), + }, + Some(CustomerLite { + name: "Kara".to_owned(), + }) + ), + ] + ); ctx.delete().await; From cd43ff7143b50b339e0b01df5b74b1ddce77aec0 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 2 Sep 2021 17:43:29 +0800 Subject: [PATCH 55/89] Remove turbofish --- tests/relational_tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/relational_tests.rs b/tests/relational_tests.rs index f67d4e88..2d129d2d 100644 --- a/tests/relational_tests.rs +++ b/tests/relational_tests.rs @@ -8,7 +8,7 @@ pub mod common; pub use common::{bakery_chain::*, setup::*, TestContext}; // Run the test locally: -// DATABASE_URL="mysql://root:@localhost" cargo test --features sqlx-mysql,runtime-async-std --test relational_tests +// DATABASE_URL="mysql://root:@localhost" cargo test --features sqlx-mysql,runtime-async-std-native-tls --test relational_tests #[sea_orm_macros::test] #[cfg(any( feature = "sqlx-mysql", @@ -662,7 +662,7 @@ pub async fn linked() -> Result<(), DbErr> { name: String, } - let baked_for_customers = Baker::find() + let baked_for_customers: Vec<(BakerLite, Option)> = Baker::find() .find_also_linked(baker::BakedForCustomer) .select_only() .column_as_prefixed(baker::Column::Name, "A_", baker::Column::Name) @@ -673,7 +673,7 @@ pub async fn linked() -> Result<(), DbErr> { .group_by(customer::Column::Name) .order_by_asc(baker::Column::Id) .order_by_asc(customer::Column::Id) - .into_model::() + .into_model() .all(&ctx.db) .await?; From a5ea54df6122a02b4030229d5c3f7bce502397ec Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Thu, 2 Sep 2021 20:20:02 +1000 Subject: [PATCH 56/89] Renders HTML pages --- examples/rocket_example/Cargo.toml | 3 + examples/rocket_example/Rocket.toml | 4 +- examples/rocket_example/src/sqlx/mod.rs | 57 ++- examples/rocket_example/src/sqlx/post.rs | 4 +- .../rocket_example/static/css/normalize.css | 427 ++++++++++++++++++ .../rocket_example/static/css/skeleton.css | 421 +++++++++++++++++ examples/rocket_example/static/css/style.css | 58 +++ .../rocket_example/static/images/favicon.png | Bin 0 -> 1155 bytes .../rocket_example/templates/base.html.tera | 81 ++++ .../templates/error/404.html.tera | 11 + .../rocket_example/templates/index.html.tera | 30 ++ .../rocket_example/templates/new.html.tera | 30 ++ 12 files changed, 1108 insertions(+), 18 deletions(-) create mode 100644 examples/rocket_example/static/css/normalize.css create mode 100644 examples/rocket_example/static/css/skeleton.css create mode 100644 examples/rocket_example/static/css/style.css create mode 100644 examples/rocket_example/static/images/favicon.png create mode 100644 examples/rocket_example/templates/base.html.tera create mode 100644 examples/rocket_example/templates/error/404.html.tera create mode 100644 examples/rocket_example/templates/index.html.tera create mode 100644 examples/rocket_example/templates/new.html.tera diff --git a/examples/rocket_example/Cargo.toml b/examples/rocket_example/Cargo.toml index 4e645dfb..be96f986 100644 --- a/examples/rocket_example/Cargo.toml +++ b/examples/rocket_example/Cargo.toml @@ -9,6 +9,9 @@ rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = [ "json", ] } rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", features = [] } +rocket_dyn_templates = { git = "https://github.com/SergioBenitez/Rocket.git", features = [ + "tera", +] } sea-orm = { path = "../../", features = ["web-api-rocket"] } sea-query = { version = "^0.12.8" } diff --git a/examples/rocket_example/Rocket.toml b/examples/rocket_example/Rocket.toml index bf5098f8..b7fcc12a 100644 --- a/examples/rocket_example/Rocket.toml +++ b/examples/rocket_example/Rocket.toml @@ -1,5 +1,7 @@ -[default.databases.rocket_example] +[default] +template_dir = "templates/" +[default.databases.rocket_example] # Mysql # make sure to enable "sqlx-mysql" feature in Cargo.toml, i.e default = ["sqlx-mysql"] # url = "mysql://root:@localhost/rocket_example" diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index 6273a49d..9174a2e9 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -1,8 +1,12 @@ use rocket::fairing::{self, AdHoc}; -use rocket::response::status::Created; +use rocket::form::{Context, Form}; +use rocket::fs::{relative, FileServer}; +use rocket::response::{Flash, Redirect}; use rocket::serde::json::Json; -use rocket::{Build, Rocket}; +use rocket::{Build, Request, Rocket}; use rocket_db_pools::{sqlx, Connection, Database}; +use rocket_dyn_templates::{context, Template}; + use sea_orm::entity::*; use sea_orm::RocketDbPool; @@ -17,11 +21,15 @@ type Result> = std::result::Result, - post: Json, -) -> Result>> { +#[get("/new")] +fn new() -> Template { + Template::render("new", &Context::default()) +} + +#[post("/", data = "")] +async fn create(conn: Connection, post_form: Form) -> Flash { + let post = post_form.into_inner(); + let _post = post::ActiveModel { title: Set(post.title.to_owned()), text: Set(post.text.to_owned()), @@ -31,20 +39,24 @@ async fn create( .await .expect("could not insert post"); - Ok(Created::new("/").body(post)) + Flash::success(Redirect::to("/"), "Post successfully added.") } #[get("/")] -async fn list(conn: Connection) -> Result>> { - let ids = Post::find() +async fn list(conn: Connection) -> Template { + let posts = Post::find() .all(&conn) .await .expect("could not retrieve posts") .into_iter() - .map(|record| record.id.unwrap()) .collect::>(); - Ok(Json(ids)) + Template::render( + "index", + context! { + posts: posts, + }, + ) } #[get("/")] @@ -61,16 +73,16 @@ async fn read(conn: Connection, id: i64) -> Option> { } #[delete("/")] -async fn delete(conn: Connection, id: i32) -> Result> { +async fn delete(conn: Connection, id: i32) -> Flash { let post: post::ActiveModel = Post::find_by_id(id) .one(&conn) .await .unwrap() .unwrap() .into(); - let result = post.delete(&conn).await.unwrap(); + let _result = post.delete(&conn).await.unwrap(); - Ok((result.rows_affected == 1).then(|| ())) + Flash::success(Redirect::to("/"), "Post successfully deleted.") } #[delete("/")] @@ -79,6 +91,16 @@ async fn destroy(conn: Connection) -> Result<()> { Ok(()) } +#[catch(404)] +pub fn not_found(req: &Request<'_>) -> Template { + Template::render( + "error/404", + context! { + uri: req.uri() + }, + ) +} + async fn run_migrations(rocket: Rocket) -> fairing::Result { #[cfg(feature = "sqlx-mysql")] let db_url = "mysql://root:@localhost/rocket_example"; @@ -96,6 +118,9 @@ pub fn stage() -> AdHoc { rocket .attach(Db::init()) .attach(AdHoc::try_on_ignite("SQLx Migrations", run_migrations)) - .mount("/sqlx", routes![create, delete, destroy, list, read,]) + .mount("/", FileServer::from(relative!("/static"))) + .mount("/", routes![new, create, delete, destroy, list, read,]) + .register("/", catchers![not_found]) + .attach(Template::fairing()) }) } diff --git a/examples/rocket_example/src/sqlx/post.rs b/examples/rocket_example/src/sqlx/post.rs index bfba82aa..da7a6443 100644 --- a/examples/rocket_example/src/sqlx/post.rs +++ b/examples/rocket_example/src/sqlx/post.rs @@ -11,7 +11,9 @@ impl EntityName for Entity { } } -#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Deserialize, Serialize)] +#[derive( + Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Deserialize, Serialize, FromForm, +)] #[serde(crate = "rocket::serde")] pub struct Model { #[serde(skip_deserializing, skip_serializing_if = "Option::is_none")] diff --git a/examples/rocket_example/static/css/normalize.css b/examples/rocket_example/static/css/normalize.css new file mode 100644 index 00000000..458eea1e --- /dev/null +++ b/examples/rocket_example/static/css/normalize.css @@ -0,0 +1,427 @@ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} diff --git a/examples/rocket_example/static/css/skeleton.css b/examples/rocket_example/static/css/skeleton.css new file mode 100644 index 00000000..cdc432a4 --- /dev/null +++ b/examples/rocket_example/static/css/skeleton.css @@ -0,0 +1,421 @@ +/* +* Skeleton V2.0.4 +* Copyright 2014, Dave Gamache +* www.getskeleton.com +* Free to use under the MIT license. +* https://opensource.org/licenses/mit-license.php +* 12/29/2014 +*/ + + +/* Table of contents +–––––––––––––––––––––––––––––––––––––––––––––––––– +- Grid +- Base Styles +- Typography +- Links +- Buttons +- Forms +- Lists +- Code +- Tables +- Spacing +- Utilities +- Clearing +- Media Queries +*/ + + +/* Grid +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.container { + position: relative; + width: 100%; + max-width: 960px; + margin: 0 auto; + padding: 0 20px; + box-sizing: border-box; } +.column, +.columns { + width: 100%; + float: left; + box-sizing: border-box; } + +/* For devices larger than 400px */ +@media (min-width: 400px) { + .container { + width: 85%; + padding: 0; } +} + +/* For devices larger than 550px */ +@media (min-width: 550px) { + .container { + width: 80%; } + .column, + .columns { + margin-left: 4%; } + .column:first-child, + .columns:first-child { + margin-left: 0; } + + .one.column, + .one.columns { width: 4.66666666667%; } + .two.columns { width: 13.3333333333%; } + .three.columns { width: 22%; } + .four.columns { width: 30.6666666667%; } + .five.columns { width: 39.3333333333%; } + .six.columns { width: 48%; } + .seven.columns { width: 56.6666666667%; } + .eight.columns { width: 65.3333333333%; } + .nine.columns { width: 74.0%; } + .ten.columns { width: 82.6666666667%; } + .eleven.columns { width: 91.3333333333%; } + .twelve.columns { width: 100%; margin-left: 0; } + + .one-third.column { width: 30.6666666667%; } + .two-thirds.column { width: 65.3333333333%; } + + .one-half.column { width: 48%; } + + /* Offsets */ + .offset-by-one.column, + .offset-by-one.columns { margin-left: 8.66666666667%; } + .offset-by-two.column, + .offset-by-two.columns { margin-left: 17.3333333333%; } + .offset-by-three.column, + .offset-by-three.columns { margin-left: 26%; } + .offset-by-four.column, + .offset-by-four.columns { margin-left: 34.6666666667%; } + .offset-by-five.column, + .offset-by-five.columns { margin-left: 43.3333333333%; } + .offset-by-six.column, + .offset-by-six.columns { margin-left: 52%; } + .offset-by-seven.column, + .offset-by-seven.columns { margin-left: 60.6666666667%; } + .offset-by-eight.column, + .offset-by-eight.columns { margin-left: 69.3333333333%; } + .offset-by-nine.column, + .offset-by-nine.columns { margin-left: 78.0%; } + .offset-by-ten.column, + .offset-by-ten.columns { margin-left: 86.6666666667%; } + .offset-by-eleven.column, + .offset-by-eleven.columns { margin-left: 95.3333333333%; } + + .offset-by-one-third.column, + .offset-by-one-third.columns { margin-left: 34.6666666667%; } + .offset-by-two-thirds.column, + .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } + + .offset-by-one-half.column, + .offset-by-one-half.columns { margin-left: 52%; } + +} + + +/* Base Styles +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +/* NOTE +html is set to 62.5% so that all the REM measurements throughout Skeleton +are based on 10px sizing. So basically 1.5rem = 15px :) */ +html { + font-size: 62.5%; } +body { + font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ + line-height: 1.6; + font-weight: 400; + font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #222; } + + +/* Typography +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 2rem; + font-weight: 300; } +h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} +h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } +h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } +h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } +h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } +h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } + +/* Larger than phablet */ +@media (min-width: 550px) { + h1 { font-size: 5.0rem; } + h2 { font-size: 4.2rem; } + h3 { font-size: 3.6rem; } + h4 { font-size: 3.0rem; } + h5 { font-size: 2.4rem; } + h6 { font-size: 1.5rem; } +} + +p { + margin-top: 0; } + + +/* Links +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +a { + color: #1EAEDB; } +a:hover { + color: #0FA0CE; } + + +/* Buttons +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.button, +button, +input[type="submit"], +input[type="reset"], +input[type="button"] { + display: inline-block; + height: 38px; + padding: 0 30px; + color: #555; + text-align: center; + font-size: 11px; + font-weight: 600; + line-height: 38px; + letter-spacing: .1rem; + text-transform: uppercase; + text-decoration: none; + white-space: nowrap; + background-color: transparent; + border-radius: 4px; + border: 1px solid #bbb; + cursor: pointer; + box-sizing: border-box; } +.button:hover, +button:hover, +input[type="submit"]:hover, +input[type="reset"]:hover, +input[type="button"]:hover, +.button:focus, +button:focus, +input[type="submit"]:focus, +input[type="reset"]:focus, +input[type="button"]:focus { + color: #333; + border-color: #888; + outline: 0; } +.button.button-primary, +button.button-primary, +button.primary, +input[type="submit"].button-primary, +input[type="reset"].button-primary, +input[type="button"].button-primary { + color: #FFF; + background-color: #33C3F0; + border-color: #33C3F0; } +.button.button-primary:hover, +button.button-primary:hover, +button.primary:hover, +input[type="submit"].button-primary:hover, +input[type="reset"].button-primary:hover, +input[type="button"].button-primary:hover, +.button.button-primary:focus, +button.button-primary:focus, +button.primary:focus, +input[type="submit"].button-primary:focus, +input[type="reset"].button-primary:focus, +input[type="button"].button-primary:focus { + color: #FFF; + background-color: #1EAEDB; + border-color: #1EAEDB; } + + +/* Forms +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +input[type="email"], +input[type="number"], +input[type="search"], +input[type="text"], +input[type="tel"], +input[type="url"], +input[type="password"], +textarea, +select { + height: 38px; + padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ + background-color: #fff; + border: 1px solid #D1D1D1; + border-radius: 4px; + box-shadow: none; + box-sizing: border-box; } +/* Removes awkward default styles on some inputs for iOS */ +input[type="email"], +input[type="number"], +input[type="search"], +input[type="text"], +input[type="tel"], +input[type="url"], +input[type="password"], +textarea { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; } +textarea { + min-height: 65px; + padding-top: 6px; + padding-bottom: 6px; } +input[type="email"]:focus, +input[type="number"]:focus, +input[type="search"]:focus, +input[type="text"]:focus, +input[type="tel"]:focus, +input[type="url"]:focus, +input[type="password"]:focus, +textarea:focus, +select:focus { + border: 1px solid #33C3F0; + outline: 0; } +label, +legend { + display: block; + margin-bottom: .5rem; + font-weight: 600; } +fieldset { + padding: 0; + border-width: 0; } +input[type="checkbox"], +input[type="radio"] { + display: inline; } +label > .label-body { + display: inline-block; + margin-left: .5rem; + font-weight: normal; } + + +/* Lists +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +ul { + list-style: circle inside; } +ol { + list-style: decimal inside; } +ol, ul { + padding-left: 0; + margin-top: 0; } +ul ul, +ul ol, +ol ol, +ol ul { + margin: 1.5rem 0 1.5rem 3rem; + font-size: 90%; } +li { + margin-bottom: 1rem; } + + +/* Code +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +code { + padding: .2rem .5rem; + margin: 0 .2rem; + font-size: 90%; + white-space: nowrap; + background: #F1F1F1; + border: 1px solid #E1E1E1; + border-radius: 4px; } +pre > code { + display: block; + padding: 1rem 1.5rem; + white-space: pre; } + + +/* Tables +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +th, +td { + padding: 12px 15px; + text-align: left; + border-bottom: 1px solid #E1E1E1; } +th:first-child, +td:first-child { + padding-left: 0; } +th:last-child, +td:last-child { + padding-right: 0; } + + +/* Spacing +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +button, +.button { + margin-bottom: 1rem; } +input, +textarea, +select, +fieldset { + margin-bottom: 1.5rem; } +pre, +blockquote, +dl, +figure, +table, +p, +ul, +ol, +form { + margin-bottom: 2.5rem; } + + +/* Utilities +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.u-full-width { + width: 100%; + box-sizing: border-box; } +.u-max-full-width { + max-width: 100%; + box-sizing: border-box; } +.u-pull-right { + float: right; } +.u-pull-left { + float: left; } + + +/* Misc +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +hr { + margin-top: 3rem; + margin-bottom: 3.5rem; + border-width: 0; + border-top: 1px solid #E1E1E1; } + + +/* Clearing +–––––––––––––––––––––––––––––––––––––––––––––––––– */ + +/* Self Clearing Goodness */ +.container:after, +.row:after, +.u-cf { + content: ""; + display: table; + clear: both; } + + +/* Media Queries +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +/* +Note: The best way to structure the use of media queries is to create the queries +near the relevant code. For example, if you wanted to change the styles for buttons +on small devices, paste the mobile query code up in the buttons section and style it +there. +*/ + + +/* Larger than mobile */ +@media (min-width: 400px) {} + +/* Larger than phablet (also point when grid becomes active) */ +@media (min-width: 550px) {} + +/* Larger than tablet */ +@media (min-width: 750px) {} + +/* Larger than desktop */ +@media (min-width: 1000px) {} + +/* Larger than Desktop HD */ +@media (min-width: 1200px) {} diff --git a/examples/rocket_example/static/css/style.css b/examples/rocket_example/static/css/style.css new file mode 100644 index 00000000..cdf155ec --- /dev/null +++ b/examples/rocket_example/static/css/style.css @@ -0,0 +1,58 @@ +.field-error { + border: 1px solid #ff0000 !important; +} + +.field-error-msg { + color: #ff0000; + display: block; + margin: -10px 0 10px 0; +} + +.field-success { + border: 1px solid #5AB953 !important; +} + +.field-success-msg { + color: #5AB953; + display: block; + margin: -10px 0 10px 0; +} + +span.completed { + text-decoration: line-through; +} + +form.inline { + display: inline; +} + +form.link, +button.link { + display: inline; + color: #1EAEDB; + border: none; + outline: none; + background: none; + cursor: pointer; + padding: 0; + margin: 0 0 0 0; + height: inherit; + text-decoration: underline; + font-size: inherit; + text-transform: none; + font-weight: normal; + line-height: inherit; + letter-spacing: inherit; +} + +form.link:hover, button.link:hover { + color: #0FA0CE; +} + +button.small { + height: 20px; + padding: 0 10px; + font-size: 10px; + line-height: 20px; + margin: 0 2.5px; +} diff --git a/examples/rocket_example/static/images/favicon.png b/examples/rocket_example/static/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..02b7390443ae2877c186898f52da23f062adbdcc GIT binary patch literal 1155 zcmV-}1bq96P)(=Qx>xolP{L(>2x~5PFgqllLG?-XURWh zvn7|E20`C&yWRV!SI1Uhr?1K%v2UB0m}sFhe_PG5av(Pl2<+kH9bD;aRz}QZjK*Wi zzU=Ss53_$_q-W@WteT*|u)Sk6X{KXzp9O21Er;<5zg9bJJVN(-=Zy8l4qegdI#S(evShjLl#Pk=r9jtgPegJHb!|>nu2DZUh(FA*z*X!Ly#;=Z< zcY*h@wRV}ZUw{XTs%x<*&yAr6*O7QQIqlYkGx)Ptvae%$?Bex{(}9JyQ+)L}e`z0B>Y7?qR+gjq`2_(| z*YR1|dc+Lo=!ck$dL#l;$vC2h!$%RW3u(Hst`lh9wh`s^nV%ujFKnUV($0F52BLCWB8{a&>BRO_*@>CN}62 z(UjU)Dh1DMef+rt@phC|?U_NQ(5sA6dU2zmz~B@z{?iJlE7 z#AcOX=vSv4Lj8d7>Y@B!eoVBgSnmWY)={>fquK*F zBSk|8RK;;Ao#{j?9gxf#WYg+-BV)QTNo=ijU>!sLJNa>)EQ2-Nf((u4A6fZ5e+Gh* Ve)_7M0d)WX002ovPDHLkV1i9aF}45z literal 0 HcmV?d00001 diff --git a/examples/rocket_example/templates/base.html.tera b/examples/rocket_example/templates/base.html.tera new file mode 100644 index 00000000..0b83d9ed --- /dev/null +++ b/examples/rocket_example/templates/base.html.tera @@ -0,0 +1,81 @@ + + + + + Rocket Todo Example + + + + + + + + + + + +
+

+ + {# +
+

Rocket Todo

+
+
+ + {% if msg %} + + {{ msg.1 }} + + {% endif %} +
+
+ +
+
+
+ +
+
+
    + {% for task in tasks %} {% if task.completed %} +
  • + {{ task.description }} +
    + + +
    +
    + + +
    +
  • + {% else %} +
  • + +
  • + {% endif %} {% endfor %} +
+
+
+ #} {% block content %}{% endblock content %} +
+ + diff --git a/examples/rocket_example/templates/error/404.html.tera b/examples/rocket_example/templates/error/404.html.tera new file mode 100644 index 00000000..afda653d --- /dev/null +++ b/examples/rocket_example/templates/error/404.html.tera @@ -0,0 +1,11 @@ + + + + + 404 - tera + + +

404: Hey! There's nothing here.

+ The page at {{ uri }} does not exist! + + diff --git a/examples/rocket_example/templates/index.html.tera b/examples/rocket_example/templates/index.html.tera new file mode 100644 index 00000000..2d74535c --- /dev/null +++ b/examples/rocket_example/templates/index.html.tera @@ -0,0 +1,30 @@ +{% extends "base" %} {% block content %} +

Posts

+ + + + + + + + + + + {% for post in posts %} + + + + + + + {% endfor %} + +
IDTitleText
{{ post.id }}{{ post.title }}{{ post.text }} + +
+ +

Add Post

+{% endblock content %} diff --git a/examples/rocket_example/templates/new.html.tera b/examples/rocket_example/templates/new.html.tera new file mode 100644 index 00000000..d00379c6 --- /dev/null +++ b/examples/rocket_example/templates/new.html.tera @@ -0,0 +1,30 @@ +{% extends "base" %} {% block content %} +
+

New Post

+
+
+ + +
+
+ +
+
+
+{% endblock content %} From b825df1331f7fb9769d137e217ec89a8726aa68e Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Thu, 2 Sep 2021 21:33:28 +1000 Subject: [PATCH 57/89] Get db_url from Db in run_migrations --- examples/rocket_example/src/sqlx/mod.rs | 10 +++------- src/driver/rocket_db.rs | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs index 9174a2e9..4121aba7 100644 --- a/examples/rocket_example/src/sqlx/mod.rs +++ b/examples/rocket_example/src/sqlx/mod.rs @@ -3,6 +3,7 @@ use rocket::form::{Context, Form}; use rocket::fs::{relative, FileServer}; use rocket::response::{Flash, Redirect}; use rocket::serde::json::Json; +use rocket::Config; use rocket::{Build, Request, Rocket}; use rocket_db_pools::{sqlx, Connection, Database}; use rocket_dyn_templates::{context, Template}; @@ -102,13 +103,8 @@ pub fn not_found(req: &Request<'_>) -> Template { } async fn run_migrations(rocket: Rocket) -> fairing::Result { - #[cfg(feature = "sqlx-mysql")] - let db_url = "mysql://root:@localhost/rocket_example"; - #[cfg(feature = "sqlx-postgres")] - let db_url = "postgres://root:root@localhost/rocket_example"; - - let conn = sea_orm::Database::connect(db_url).await.unwrap(); - + let db_url = Db::fetch(&rocket).unwrap().db_url.clone(); + let conn = sea_orm::Database::connect(&db_url).await.unwrap(); let _create_post_table = setup::create_post_table(&conn).await; Ok(rocket) } diff --git a/src/driver/rocket_db.rs b/src/driver/rocket_db.rs index ac53fdd7..9ce494da 100644 --- a/src/driver/rocket_db.rs +++ b/src/driver/rocket_db.rs @@ -3,7 +3,7 @@ use rocket_db_pools::{rocket::figment::Figment, Config, Error}; #[derive(Debug)] pub struct RocketDbPool { - db_url: String, + pub db_url: String, } #[async_trait] From 5f47421b386432133555f1b42a99b6d2e57e25c4 Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Thu, 2 Sep 2021 21:47:55 +1000 Subject: [PATCH 58/89] Remove sqlx dir and clean up --- examples/rocket_example/src/main.rs | 118 ++++++++++++++++- .../rocket_example/src/{sqlx => }/post.rs | 0 .../rocket_example/src/{sqlx => }/setup.rs | 6 +- examples/rocket_example/src/sqlx/mod.rs | 122 ------------------ src/driver/rocket_db.rs | 2 +- 5 files changed, 118 insertions(+), 130 deletions(-) rename examples/rocket_example/src/{sqlx => }/post.rs (100%) rename examples/rocket_example/src/{sqlx => }/setup.rs (95%) delete mode 100644 examples/rocket_example/src/sqlx/mod.rs diff --git a/examples/rocket_example/src/main.rs b/examples/rocket_example/src/main.rs index 774f58a6..c688ca99 100644 --- a/examples/rocket_example/src/main.rs +++ b/examples/rocket_example/src/main.rs @@ -1,9 +1,123 @@ #[macro_use] extern crate rocket; -mod sqlx; +use rocket::fairing::{self, AdHoc}; +use rocket::form::{Context, Form}; +use rocket::fs::{relative, FileServer}; +use rocket::response::{Flash, Redirect}; +use rocket::serde::json::Json; +use rocket::{Build, Request, Rocket}; +use rocket_db_pools::{sqlx, Connection, Database}; +use rocket_dyn_templates::{context, Template}; + +use sea_orm::entity::*; +use sea_orm::RocketDbPool; + +mod setup; + +#[derive(Database, Debug)] +#[database("rocket_example")] +struct Db(RocketDbPool); + +type Result> = std::result::Result; + +mod post; +pub use post::Entity as Post; + +#[get("/new")] +fn new() -> Template { + Template::render("new", &Context::default()) +} + +#[post("/", data = "")] +async fn create(conn: Connection, post_form: Form) -> Flash { + let post = post_form.into_inner(); + + let _post = post::ActiveModel { + title: Set(post.title.to_owned()), + text: Set(post.text.to_owned()), + ..Default::default() + } + .save(&conn) + .await + .expect("could not insert post"); + + Flash::success(Redirect::to("/"), "Post successfully added.") +} + +#[get("/")] +async fn list(conn: Connection) -> Template { + let posts = Post::find() + .all(&conn) + .await + .expect("could not retrieve posts") + .into_iter() + .collect::>(); + + Template::render( + "index", + context! { + posts: posts, + }, + ) +} + +#[get("/")] +async fn read(conn: Connection, id: i64) -> Option> { + let post: Option = Post::find_by_id(id) + .one(&conn) + .await + .expect("could not find post"); + + match post { + None => None, + Some(post) => Some(Json(post)), + } +} + +#[delete("/")] +async fn delete(conn: Connection, id: i32) -> Flash { + let post: post::ActiveModel = Post::find_by_id(id) + .one(&conn) + .await + .unwrap() + .unwrap() + .into(); + let _result = post.delete(&conn).await.unwrap(); + + Flash::success(Redirect::to("/"), "Post successfully deleted.") +} + +#[delete("/")] +async fn destroy(conn: Connection) -> Result<()> { + let _result = Post::delete_many().exec(&conn).await.unwrap(); + Ok(()) +} + +#[catch(404)] +pub fn not_found(req: &Request<'_>) -> Template { + Template::render( + "error/404", + context! { + uri: req.uri() + }, + ) +} + +async fn run_migrations(rocket: Rocket) -> fairing::Result { + let db_url = Db::fetch(&rocket).unwrap().db_url.clone(); + let conn = sea_orm::Database::connect(&db_url).await.unwrap(); + let _create_post_table = setup::create_post_table(&conn).await; + Ok(rocket) +} #[launch] fn rocket() -> _ { - rocket::build().attach(sqlx::stage()) + rocket::build() + .attach(Db::init()) + .attach(AdHoc::try_on_ignite("Migrations", run_migrations)) + .mount("/", FileServer::from(relative!("/static"))) + .mount("/", routes![new, create, delete, destroy, list, read,]) + .register("/", catchers![not_found]) + .attach(Template::fairing()) } diff --git a/examples/rocket_example/src/sqlx/post.rs b/examples/rocket_example/src/post.rs similarity index 100% rename from examples/rocket_example/src/sqlx/post.rs rename to examples/rocket_example/src/post.rs diff --git a/examples/rocket_example/src/sqlx/setup.rs b/examples/rocket_example/src/setup.rs similarity index 95% rename from examples/rocket_example/src/sqlx/setup.rs rename to examples/rocket_example/src/setup.rs index 2421d84f..92c3ca8c 100644 --- a/examples/rocket_example/src/sqlx/setup.rs +++ b/examples/rocket_example/src/setup.rs @@ -1,9 +1,5 @@ -use sea_orm::{error::*, sea_query, DbConn, ExecResult}; - use sea_orm::sea_query::{ColumnDef, TableCreateStatement}; - -// mod post; -pub use super::post::*; +use sea_orm::{error::*, sea_query, DbConn, ExecResult}; async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result { let builder = db.get_database_backend(); diff --git a/examples/rocket_example/src/sqlx/mod.rs b/examples/rocket_example/src/sqlx/mod.rs deleted file mode 100644 index 4121aba7..00000000 --- a/examples/rocket_example/src/sqlx/mod.rs +++ /dev/null @@ -1,122 +0,0 @@ -use rocket::fairing::{self, AdHoc}; -use rocket::form::{Context, Form}; -use rocket::fs::{relative, FileServer}; -use rocket::response::{Flash, Redirect}; -use rocket::serde::json::Json; -use rocket::Config; -use rocket::{Build, Request, Rocket}; -use rocket_db_pools::{sqlx, Connection, Database}; -use rocket_dyn_templates::{context, Template}; - -use sea_orm::entity::*; -use sea_orm::RocketDbPool; - -mod setup; - -#[derive(Database, Debug)] -#[database("rocket_example")] -struct Db(RocketDbPool); - -type Result> = std::result::Result; - -mod post; -pub use post::Entity as Post; - -#[get("/new")] -fn new() -> Template { - Template::render("new", &Context::default()) -} - -#[post("/", data = "")] -async fn create(conn: Connection, post_form: Form) -> Flash { - let post = post_form.into_inner(); - - let _post = post::ActiveModel { - title: Set(post.title.to_owned()), - text: Set(post.text.to_owned()), - ..Default::default() - } - .save(&conn) - .await - .expect("could not insert post"); - - Flash::success(Redirect::to("/"), "Post successfully added.") -} - -#[get("/")] -async fn list(conn: Connection) -> Template { - let posts = Post::find() - .all(&conn) - .await - .expect("could not retrieve posts") - .into_iter() - .collect::>(); - - Template::render( - "index", - context! { - posts: posts, - }, - ) -} - -#[get("/")] -async fn read(conn: Connection, id: i64) -> Option> { - let post: Option = Post::find_by_id(id) - .one(&conn) - .await - .expect("could not find post"); - - match post { - None => None, - Some(post) => Some(Json(post)), - } -} - -#[delete("/")] -async fn delete(conn: Connection, id: i32) -> Flash { - let post: post::ActiveModel = Post::find_by_id(id) - .one(&conn) - .await - .unwrap() - .unwrap() - .into(); - let _result = post.delete(&conn).await.unwrap(); - - Flash::success(Redirect::to("/"), "Post successfully deleted.") -} - -#[delete("/")] -async fn destroy(conn: Connection) -> Result<()> { - let _result = Post::delete_many().exec(&conn).await.unwrap(); - Ok(()) -} - -#[catch(404)] -pub fn not_found(req: &Request<'_>) -> Template { - Template::render( - "error/404", - context! { - uri: req.uri() - }, - ) -} - -async fn run_migrations(rocket: Rocket) -> fairing::Result { - let db_url = Db::fetch(&rocket).unwrap().db_url.clone(); - let conn = sea_orm::Database::connect(&db_url).await.unwrap(); - let _create_post_table = setup::create_post_table(&conn).await; - Ok(rocket) -} - -pub fn stage() -> AdHoc { - AdHoc::on_ignite("SQLx Stage", |rocket| async { - rocket - .attach(Db::init()) - .attach(AdHoc::try_on_ignite("SQLx Migrations", run_migrations)) - .mount("/", FileServer::from(relative!("/static"))) - .mount("/", routes![new, create, delete, destroy, list, read,]) - .register("/", catchers![not_found]) - .attach(Template::fairing()) - }) -} diff --git a/src/driver/rocket_db.rs b/src/driver/rocket_db.rs index 9ce494da..5489e051 100644 --- a/src/driver/rocket_db.rs +++ b/src/driver/rocket_db.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use rocket_db_pools::{rocket::figment::Figment, Config, Error}; +use rocket_db_pools::{rocket::figment::Figment, Config}; #[derive(Debug)] pub struct RocketDbPool { From 39b5cb7f333906ee9c83d7adf62f1d60266b197f Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Thu, 2 Sep 2021 22:30:23 +1000 Subject: [PATCH 59/89] Render flash messages --- examples/rocket_example/src/main.rs | 5 ++++- examples/rocket_example/static/css/style.css | 17 +++++++++-------- .../rocket_example/templates/index.html.tera | 5 +++++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/examples/rocket_example/src/main.rs b/examples/rocket_example/src/main.rs index c688ca99..7048e4d4 100644 --- a/examples/rocket_example/src/main.rs +++ b/examples/rocket_example/src/main.rs @@ -4,6 +4,7 @@ extern crate rocket; use rocket::fairing::{self, AdHoc}; use rocket::form::{Context, Form}; use rocket::fs::{relative, FileServer}; +use rocket::request::FlashMessage; use rocket::response::{Flash, Redirect}; use rocket::serde::json::Json; use rocket::{Build, Request, Rocket}; @@ -46,18 +47,20 @@ async fn create(conn: Connection, post_form: Form) -> Flash) -> Template { +async fn list(conn: Connection, flash: Option>) -> Template { let posts = Post::find() .all(&conn) .await .expect("could not retrieve posts") .into_iter() .collect::>(); + let flash = flash.map(FlashMessage::into_inner); Template::render( "index", context! { posts: posts, + flash: flash, }, ) } diff --git a/examples/rocket_example/static/css/style.css b/examples/rocket_example/static/css/style.css index cdf155ec..1b6a1720 100644 --- a/examples/rocket_example/static/css/style.css +++ b/examples/rocket_example/static/css/style.css @@ -2,24 +2,24 @@ border: 1px solid #ff0000 !important; } -.field-error-msg { +.field-error-flash { color: #ff0000; display: block; margin: -10px 0 10px 0; } .field-success { - border: 1px solid #5AB953 !important; + border: 1px solid #5ab953 !important; } -.field-success-msg { - color: #5AB953; +.field-success-flash { + color: #5ab953; display: block; margin: -10px 0 10px 0; } span.completed { - text-decoration: line-through; + text-decoration: line-through; } form.inline { @@ -29,7 +29,7 @@ form.inline { form.link, button.link { display: inline; - color: #1EAEDB; + color: #1eaedb; border: none; outline: none; background: none; @@ -45,8 +45,9 @@ button.link { letter-spacing: inherit; } -form.link:hover, button.link:hover { - color: #0FA0CE; +form.link:hover, +button.link:hover { + color: #0fa0ce; } button.small { diff --git a/examples/rocket_example/templates/index.html.tera b/examples/rocket_example/templates/index.html.tera index 2d74535c..077f7ff1 100644 --- a/examples/rocket_example/templates/index.html.tera +++ b/examples/rocket_example/templates/index.html.tera @@ -1,5 +1,10 @@ {% extends "base" %} {% block content %}

Posts

+{% if flash %} + + {{ flash.1 }} + +{% endif %} From 541b94f15d6b968733485a7e3ffde1c33d26dafc Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Sep 2021 11:23:29 +0800 Subject: [PATCH 60/89] Update query helper column_as --- src/entity/identity.rs | 34 ++++++++++++++++++++++++++++++++- src/executor/select.rs | 8 ++++---- src/query/combine.rs | 33 ++++++++++++++++++++++++++------ src/query/helper.rs | 40 ++++++--------------------------------- src/query/mod.rs | 2 +- tests/relational_tests.rs | 5 +++-- 6 files changed, 74 insertions(+), 48 deletions(-) diff --git a/src/entity/identity.rs b/src/entity/identity.rs index d1cc3170..d8623b7d 100644 --- a/src/entity/identity.rs +++ b/src/entity/identity.rs @@ -1,5 +1,6 @@ use crate::{ColumnTrait, EntityTrait, IdenStatic}; -use sea_query::{DynIden, IntoIden}; +use sea_query::{Alias, DynIden, Iden, IntoIden, SeaRc}; +use std::fmt; #[derive(Debug, Clone)] pub enum Identity { @@ -8,6 +9,25 @@ pub enum Identity { Ternary(DynIden, DynIden, DynIden), } +impl Iden for Identity { + fn unquoted(&self, s: &mut dyn fmt::Write) { + match self { + Identity::Unary(iden) => { + write!(s, "{}", iden.to_string()).unwrap(); + } + Identity::Binary(iden1, iden2) => { + write!(s, "{}", iden1.to_string()).unwrap(); + write!(s, "{}", iden2.to_string()).unwrap(); + } + Identity::Ternary(iden1, iden2, iden3) => { + write!(s, "{}", iden1.to_string()).unwrap(); + write!(s, "{}", iden2.to_string()).unwrap(); + write!(s, "{}", iden3.to_string()).unwrap(); + } + } + } +} + pub trait IntoIdentity { fn into_identity(self) -> Identity; } @@ -19,6 +39,18 @@ where fn identity_of(self) -> Identity; } +impl IntoIdentity for String { + fn into_identity(self) -> Identity { + self.as_str().into_identity() + } +} + +impl IntoIdentity for &str { + fn into_identity(self) -> Identity { + Identity::Unary(SeaRc::new(Alias::new(self))) + } +} + impl IntoIdentity for T where T: IdenStatic, diff --git a/src/executor/select.rs b/src/executor/select.rs index 836ea495..fc3a970b 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -1,6 +1,6 @@ use crate::{ - error::*, query::combine, DatabaseConnection, EntityTrait, FromQueryResult, Iterable, - JsonValue, ModelTrait, Paginator, PrimaryKeyToColumn, QueryResult, Select, SelectTwo, + error::*, DatabaseConnection, EntityTrait, FromQueryResult, IdenStatic, Iterable, JsonValue, + ModelTrait, Paginator, PrimaryKeyToColumn, QueryResult, Select, SelectA, SelectB, SelectTwo, SelectTwoMany, Statement, }; use sea_query::SelectStatement; @@ -66,8 +66,8 @@ where fn from_raw_query_result(res: QueryResult) -> Result { Ok(( - M::from_query_result(&res, combine::SELECT_A)?, - N::from_query_result_optional(&res, combine::SELECT_B)?, + M::from_query_result(&res, SelectA.as_str())?, + N::from_query_result_optional(&res, SelectB.as_str())?, )) } } diff --git a/src/query/combine.rs b/src/query/combine.rs index 8cce0510..0c0f151f 100644 --- a/src/query/combine.rs +++ b/src/query/combine.rs @@ -1,10 +1,31 @@ -use crate::{EntityTrait, IntoSimpleExpr, Iterable, QueryTrait, Select, SelectTwo, SelectTwoMany}; +use crate::{ + EntityTrait, IdenStatic, IntoSimpleExpr, Iterable, QueryTrait, Select, SelectTwo, SelectTwoMany, +}; use core::marker::PhantomData; pub use sea_query::JoinType; use sea_query::{Alias, ColumnRef, Iden, Order, SeaRc, SelectExpr, SelectStatement, SimpleExpr}; -pub const SELECT_A: &str = "A_"; -pub const SELECT_B: &str = "B_"; +macro_rules! select_def { + ( $ident: ident, $str: expr ) => { + #[derive(Debug, Clone, Copy)] + pub struct $ident; + + impl Iden for $ident { + fn unquoted(&self, s: &mut dyn std::fmt::Write) { + write!(s, "{}", self.as_str()).unwrap(); + } + } + + impl IdenStatic for $ident { + fn as_str(&self) -> &str { + $str + } + } + }; +} + +select_def!(SelectA, "A_"); +select_def!(SelectB, "B_"); impl Select where @@ -37,7 +58,7 @@ where where F: EntityTrait, { - self = self.apply_alias(SELECT_A); + self = self.apply_alias(SelectA.as_str()); SelectTwo::new(self.into_query()) } @@ -45,7 +66,7 @@ where where F: EntityTrait, { - self = self.apply_alias(SELECT_A); + self = self.apply_alias(SelectA.as_str()); SelectTwoMany::new(self.into_query()) } } @@ -102,7 +123,7 @@ where S: QueryTrait, { for col in ::iter() { - let alias = format!("{}{}", SELECT_B, col.to_string().as_str()); + let alias = format!("{}{}", SelectB.as_str(), col.as_str()); selector.query().expr(SelectExpr { expr: col.into_simple_expr(), alias: Some(SeaRc::new(Alias::new(&alias))), diff --git a/src/query/helper.rs b/src/query/helper.rs index 4ca44905..88a5c55c 100644 --- a/src/query/helper.rs +++ b/src/query/helper.rs @@ -1,11 +1,9 @@ use crate::{ - ColumnTrait, EntityTrait, Identity, IntoSimpleExpr, Iterable, ModelTrait, PrimaryKeyToColumn, - RelationDef, -}; -use sea_query::{ - Alias, Expr, Iden, IntoCondition, SeaRc, SelectExpr, SelectStatement, SimpleExpr, TableRef, + ColumnTrait, EntityTrait, Identity, IntoIdentity, IntoSimpleExpr, Iterable, ModelTrait, + PrimaryKeyToColumn, RelationDef, }; pub use sea_query::{Condition, ConditionalStatement, DynIden, JoinType, Order, OrderedStatement}; +use sea_query::{Expr, IntoCondition, SeaRc, SelectExpr, SelectStatement, SimpleExpr, TableRef}; // LINT: when the column does not appear in tables selected from // LINT: when there is a group by clause, but some columns don't have aggregate functions @@ -55,40 +53,14 @@ pub trait QuerySelect: Sized { /// r#"SELECT COUNT("cake"."id") AS "count" FROM "cake""# /// ); /// ``` - fn column_as(mut self, col: C, alias: &str) -> Self + fn column_as(mut self, col: C, alias: I) -> Self where C: IntoSimpleExpr, + I: IntoIdentity, { self.query().expr(SelectExpr { expr: col.into_simple_expr(), - alias: Some(SeaRc::new(Alias::new(alias))), - }); - self - } - - /// Add a select column with prefixed alias - /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; - /// - /// assert_eq!( - /// cake::Entity::find() - /// .select_only() - /// .column_as_prefixed(cake::Column::Id.count(), "A_", cake::Column::Id) - /// .build(DbBackend::Postgres) - /// .to_string(), - /// r#"SELECT COUNT("cake"."id") AS "A_id" FROM "cake""# - /// ); - /// ``` - fn column_as_prefixed(mut self, col: C, prefix: &str, alias: I) -> Self - where - C: IntoSimpleExpr, - I: Iden, - { - self.query().expr(SelectExpr { - expr: col.into_simple_expr(), - alias: Some(SeaRc::new(Alias::new( - vec![prefix, alias.to_string().as_str()].join("").as_str(), - ))), + alias: Some(SeaRc::new(alias.into_identity())), }); self } diff --git a/src/query/mod.rs b/src/query/mod.rs index 899882ba..54cc12dd 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -9,7 +9,7 @@ mod select; mod traits; mod update; -// pub use combine::*; +pub use combine::{SelectA, SelectB}; pub use delete::*; pub use helper::*; pub use insert::*; diff --git a/tests/relational_tests.rs b/tests/relational_tests.rs index 2d129d2d..198d4b24 100644 --- a/tests/relational_tests.rs +++ b/tests/relational_tests.rs @@ -484,6 +484,7 @@ pub async fn having() { ))] pub async fn linked() -> Result<(), DbErr> { use common::bakery_chain::Order; + use sea_orm::{SelectA, SelectB}; let ctx = TestContext::new("test_linked").await; @@ -665,8 +666,8 @@ pub async fn linked() -> Result<(), DbErr> { let baked_for_customers: Vec<(BakerLite, Option)> = Baker::find() .find_also_linked(baker::BakedForCustomer) .select_only() - .column_as_prefixed(baker::Column::Name, "A_", baker::Column::Name) - .column_as_prefixed(customer::Column::Name, "B_", customer::Column::Name) + .column_as(baker::Column::Name, (SelectA, baker::Column::Name)) + .column_as(customer::Column::Name, (SelectB, customer::Column::Name)) .group_by(baker::Column::Id) .group_by(customer::Column::Id) .group_by(baker::Column::Name) From 4e111aea64040ebf8a536d5c9ae92185b9bc1a02 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Sep 2021 11:54:24 +0800 Subject: [PATCH 61/89] Use sea-query 0.16 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 885c8144..9b9d291d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", git = "https://github.com/SeaQL/sea-query.git", 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 } From c8e223287590095f98e2b28ffd6b82d191cafcd1 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 3 Sep 2021 14:17:07 +0800 Subject: [PATCH 62/89] Reduce panic --- src/executor/query.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/executor/query.rs b/src/executor/query.rs index 1c2ce4a1..a817a9ce 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -59,9 +59,9 @@ impl fmt::Debug for QueryResultRow { #[cfg(feature = "sqlx-mysql")] Self::SqlxMySql(row) => write!(f, "{:?}", row), #[cfg(feature = "sqlx-postgres")] - Self::SqlxPostgres(_) => panic!("QueryResultRow::SqlxPostgres cannot be inspected"), + Self::SqlxPostgres(_) => write!("QueryResultRow::SqlxPostgres cannot be inspected"), #[cfg(feature = "sqlx-sqlite")] - Self::SqlxSqlite(_) => panic!("QueryResultRow::SqlxSqlite cannot be inspected"), + Self::SqlxSqlite(_) => write!("QueryResultRow::SqlxSqlite cannot be inspected"), #[cfg(feature = "mock")] Self::Mock(row) => write!(f, "{:?}", row), } From a781cc66107033b964a72c7a271b1d42ec4c75ae Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 3 Sep 2021 14:21:51 +0800 Subject: [PATCH 63/89] Reduce panic --- src/database/connection.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/database/connection.rs b/src/database/connection.rs index 3d4215f5..6a39b240 100644 --- a/src/database/connection.rs +++ b/src/database/connection.rs @@ -75,7 +75,7 @@ impl DatabaseConnection { DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.execute(stmt).await, #[cfg(feature = "mock")] DatabaseConnection::MockDatabaseConnection(conn) => conn.execute(stmt).await, - DatabaseConnection::Disconnected => panic!("Disconnected"), + DatabaseConnection::Disconnected => Err(DbErr::Conn("Disconnected".to_owned())), } } @@ -89,7 +89,7 @@ impl DatabaseConnection { DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.query_one(stmt).await, #[cfg(feature = "mock")] DatabaseConnection::MockDatabaseConnection(conn) => conn.query_one(stmt).await, - DatabaseConnection::Disconnected => panic!("Disconnected"), + DatabaseConnection::Disconnected => Err(DbErr::Conn("Disconnected".to_owned())), } } @@ -103,7 +103,7 @@ impl DatabaseConnection { DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.query_all(stmt).await, #[cfg(feature = "mock")] DatabaseConnection::MockDatabaseConnection(conn) => conn.query_all(stmt).await, - DatabaseConnection::Disconnected => panic!("Disconnected"), + DatabaseConnection::Disconnected => Err(DbErr::Conn("Disconnected".to_owned())), } } From 7f38621c3c82ad2c02ed19d843692b6d08b248d6 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 2 Sep 2021 18:34:44 +0800 Subject: [PATCH 64/89] Set clippy deny --- src/driver/mock.rs | 5 ++++- src/driver/sqlx_mysql.rs | 2 ++ src/driver/sqlx_postgres.rs | 2 ++ src/driver/sqlx_sqlite.rs | 2 ++ src/entity/relation.rs | 2 ++ src/executor/query.rs | 1 + src/executor/select.rs | 1 + src/lib.rs | 7 +++++++ 8 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/driver/mock.rs b/src/driver/mock.rs index 0a1629bf..823ddb32 100644 --- a/src/driver/mock.rs +++ b/src/driver/mock.rs @@ -2,19 +2,22 @@ use crate::{ debug_print, error::*, DatabaseConnection, DbBackend, ExecResult, MockDatabase, QueryResult, Statement, Transaction, }; +use std::fmt::Debug; use std::sync::{ atomic::{AtomicUsize, Ordering}, Mutex, }; +#[derive(Debug)] pub struct MockDatabaseConnector; +#[derive(Debug)] pub struct MockDatabaseConnection { counter: AtomicUsize, mocker: Mutex>, } -pub trait MockDatabaseTrait: Send { +pub trait MockDatabaseTrait: Send + Debug { fn execute(&mut self, counter: usize, stmt: Statement) -> Result; fn query(&mut self, counter: usize, stmt: Statement) -> Result, DbErr>; diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs index 48da88a0..e91df037 100644 --- a/src/driver/sqlx_mysql.rs +++ b/src/driver/sqlx_mysql.rs @@ -10,8 +10,10 @@ use crate::{debug_print, error::*, executor::*, DatabaseConnection, Statement}; use super::sqlx_common::*; +#[derive(Debug)] pub struct SqlxMySqlConnector; +#[derive(Debug)] pub struct SqlxMySqlPoolConnection { pool: MySqlPool, } diff --git a/src/driver/sqlx_postgres.rs b/src/driver/sqlx_postgres.rs index b3d71f9a..086dc995 100644 --- a/src/driver/sqlx_postgres.rs +++ b/src/driver/sqlx_postgres.rs @@ -10,8 +10,10 @@ use crate::{debug_print, error::*, executor::*, DatabaseConnection, Statement}; use super::sqlx_common::*; +#[derive(Debug)] pub struct SqlxPostgresConnector; +#[derive(Debug)] pub struct SqlxPostgresPoolConnection { pool: PgPool, } diff --git a/src/driver/sqlx_sqlite.rs b/src/driver/sqlx_sqlite.rs index ac275b72..5fa4bdcd 100644 --- a/src/driver/sqlx_sqlite.rs +++ b/src/driver/sqlx_sqlite.rs @@ -10,8 +10,10 @@ use crate::{debug_print, error::*, executor::*, DatabaseConnection, Statement}; use super::sqlx_common::*; +#[derive(Debug)] pub struct SqlxSqliteConnector; +#[derive(Debug)] pub struct SqlxSqlitePoolConnection { pool: SqlitePool, } diff --git a/src/entity/relation.rs b/src/entity/relation.rs index 4a35b8ba..7b2d7b4d 100644 --- a/src/entity/relation.rs +++ b/src/entity/relation.rs @@ -46,6 +46,7 @@ pub trait Linked { } } +#[derive(Debug)] pub struct RelationDef { pub rel_type: RelationType, pub from_tbl: TableRef, @@ -57,6 +58,7 @@ pub struct RelationDef { pub on_update: Option, } +#[derive(Debug)] pub struct RelationBuilder where E: EntityTrait, diff --git a/src/executor/query.rs b/src/executor/query.rs index a817a9ce..b853771a 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -21,6 +21,7 @@ pub trait TryGetable: Sized { fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result; } +#[derive(Debug)] pub enum TryGetError { DbErr(DbErr), Null, diff --git a/src/executor/select.rs b/src/executor/select.rs index fc3a970b..0a5163de 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -30,6 +30,7 @@ pub trait SelectorTrait { fn from_raw_query_result(res: QueryResult) -> Result; } +#[derive(Debug)] pub struct SelectModel where M: FromQueryResult, diff --git a/src/lib.rs b/src/lib.rs index 441df8bd..ee5bb8cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,10 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![deny( + missing_debug_implementations, + clippy::print_stderr, + clippy::print_stdout +)] + //!
//! //! From 9d5ce08ff891497cd9ef591ca692b47ac2c57f68 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 2 Sep 2021 18:35:10 +0800 Subject: [PATCH 65/89] Logging --- Cargo.toml | 6 ++++-- sea-orm-macros/src/lib.rs | 4 ++++ src/util.rs | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3db574bb..387d27d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ chrono = { version = "^0", optional = true } 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-orm-macros = { version = "^0.1.1", path = "sea-orm-macros", optional = true } sea-query = { version = "^0.16", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } @@ -40,6 +40,7 @@ sqlx-core = { version = "^0.5", optional = true } sqlx-macros = { version = "^0.5", optional = true } serde_json = { version = "^1", optional = true } uuid = { version = "0.8", features = ["serde", "v4"], optional = true } +log = { version = "^0.4", optional = true } [dev-dependencies] smol = { version = "^1.2" } @@ -49,11 +50,12 @@ tokio = { version = "^1.6", features = ["full"] } actix-rt = { version = "2.2.0" } maplit = { version = "^1" } rust_decimal_macros = { version = "^1" } +env_logger = { version = "^0.9" } sea-orm = { path = ".", features = ["debug-print"] } pretty_assertions = { version = "^0.7" } [features] -debug-print = [] +debug-print = ["log"] default = [ "macros", "mock", diff --git a/sea-orm-macros/src/lib.rs b/sea-orm-macros/src/lib.rs index a2217aa8..a9abf8b2 100644 --- a/sea-orm-macros/src/lib.rs +++ b/sea-orm-macros/src/lib.rs @@ -99,6 +99,10 @@ pub fn test(_: TokenStream, input: TokenStream) -> TokenStream { #[test] #(#attrs)* fn #name() #ret { + let _ = ::env_logger::builder() + .filter_level(::log::LevelFilter::Debug) + .is_test(true) + .try_init(); crate::block_on!(async { #body }) } ) diff --git a/src/util.rs b/src/util.rs index f6ddfd54..20cc8eb3 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,7 +1,7 @@ #[macro_export] #[cfg(feature = "debug-print")] macro_rules! debug_print { - ($( $args:expr ),*) => { println!( $( $args ),* ); } + ($( $args:expr ),*) => { log::debug!( $( $args ),* ); } } #[macro_export] From d212b62ce5b064397cc9df1778088ed02fe66eca Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 3 Sep 2021 14:34:20 +0800 Subject: [PATCH 66/89] Fixup --- src/executor/query.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/executor/query.rs b/src/executor/query.rs index a817a9ce..f16a420c 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -59,9 +59,9 @@ impl fmt::Debug for QueryResultRow { #[cfg(feature = "sqlx-mysql")] Self::SqlxMySql(row) => write!(f, "{:?}", row), #[cfg(feature = "sqlx-postgres")] - Self::SqlxPostgres(_) => write!("QueryResultRow::SqlxPostgres cannot be inspected"), + Self::SqlxPostgres(_) => write!(f, "QueryResultRow::SqlxPostgres cannot be inspected"), #[cfg(feature = "sqlx-sqlite")] - Self::SqlxSqlite(_) => write!("QueryResultRow::SqlxSqlite cannot be inspected"), + Self::SqlxSqlite(_) => write!(f, "QueryResultRow::SqlxSqlite cannot be inspected"), #[cfg(feature = "mock")] Self::Mock(row) => write!(f, "{:?}", row), } From b5d85486dd8b2347ca94ffc142c6d70177dbfb02 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Sep 2021 14:35:20 +0800 Subject: [PATCH 67/89] Fixup --- src/entity/schema.rs | 8 ++++---- src/tests_cfg/cake.rs | 1 + tests/common/setup/schema.rs | 2 +- tests/relational_tests.rs | 24 ++++++++++++------------ 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/entity/schema.rs b/src/entity/schema.rs index 727eab06..06c688a8 100644 --- a/src/entity/schema.rs +++ b/src/entity/schema.rs @@ -21,10 +21,10 @@ where 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 column.to_string() == primary_key.into_column().to_string() { + if E::PrimaryKey::auto_increment() { + column_def.auto_increment(); + } if E::PrimaryKey::iter().count() == 1 { column_def.primary_key(); } diff --git a/src/tests_cfg/cake.rs b/src/tests_cfg/cake.rs index 49ada3aa..0eeb0738 100644 --- a/src/tests_cfg/cake.rs +++ b/src/tests_cfg/cake.rs @@ -75,6 +75,7 @@ impl Related for Entity { } } +#[derive(Debug)] pub struct CakeToFilling; impl Linked for CakeToFilling { diff --git a/tests/common/setup/schema.rs b/tests/common/setup/schema.rs index 2dc5f239..32e7d8a5 100644 --- a/tests/common/setup/schema.rs +++ b/tests/common/setup/schema.rs @@ -286,5 +286,5 @@ pub async fn create_metadata_table(db: &DbConn) -> Result { .col(ColumnDef::new(metadata::Column::Value).string().not_null()) .to_owned(); - create_table(db, &stmt).await + create_table(db, &stmt, Metadata).await } diff --git a/tests/relational_tests.rs b/tests/relational_tests.rs index 198d4b24..c950ddc6 100644 --- a/tests/relational_tests.rs +++ b/tests/relational_tests.rs @@ -494,7 +494,7 @@ pub async fn linked() -> Result<(), DbErr> { profit_margin: Set(10.4), ..Default::default() }; - let seaside_bakery_res: InsertResult = Bakery::insert(seaside_bakery).exec(&ctx.db).await?; + let seaside_bakery_res = Bakery::insert(seaside_bakery).exec(&ctx.db).await?; // Bob's Baker, Cake & Cake Baker let baker_bob = baker::ActiveModel { @@ -507,7 +507,7 @@ pub async fn linked() -> Result<(), DbErr> { bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), ..Default::default() }; - let baker_bob_res: InsertResult = Baker::insert(baker_bob).exec(&ctx.db).await?; + let baker_bob_res = Baker::insert(baker_bob).exec(&ctx.db).await?; let mud_cake = cake::ActiveModel { name: Set("Mud Cake".to_owned()), price: Set(dec!(10.25)), @@ -516,7 +516,7 @@ pub async fn linked() -> Result<(), DbErr> { bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), ..Default::default() }; - let mud_cake_res: InsertResult = Cake::insert(mud_cake).exec(&ctx.db).await?; + let mud_cake_res = Cake::insert(mud_cake).exec(&ctx.db).await?; let bob_cakes_bakers = cakes_bakers::ActiveModel { cake_id: Set(mud_cake_res.last_insert_id as i32), baker_id: Set(baker_bob_res.last_insert_id as i32), @@ -533,7 +533,7 @@ pub async fn linked() -> Result<(), DbErr> { bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), ..Default::default() }; - let baker_bobby_res: InsertResult = Baker::insert(baker_bobby).exec(&ctx.db).await?; + let baker_bobby_res = Baker::insert(baker_bobby).exec(&ctx.db).await?; let cheese_cake = cake::ActiveModel { name: Set("Cheese Cake".to_owned()), price: Set(dec!(20.5)), @@ -542,7 +542,7 @@ pub async fn linked() -> Result<(), DbErr> { bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), ..Default::default() }; - let cheese_cake_res: InsertResult = Cake::insert(cheese_cake).exec(&ctx.db).await?; + let cheese_cake_res = Cake::insert(cheese_cake).exec(&ctx.db).await?; let bobby_cakes_bakers = cakes_bakers::ActiveModel { cake_id: Set(cheese_cake_res.last_insert_id as i32), baker_id: Set(baker_bobby_res.last_insert_id as i32), @@ -559,7 +559,7 @@ pub async fn linked() -> Result<(), DbErr> { bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), ..Default::default() }; - let chocolate_cake_res: InsertResult = Cake::insert(chocolate_cake).exec(&ctx.db).await?; + let chocolate_cake_res = Cake::insert(chocolate_cake).exec(&ctx.db).await?; let bobby_cakes_bakers = cakes_bakers::ActiveModel { cake_id: Set(chocolate_cake_res.last_insert_id as i32), baker_id: Set(baker_bobby_res.last_insert_id as i32), @@ -575,7 +575,7 @@ pub async fn linked() -> Result<(), DbErr> { notes: Set(Some("Loves cheese cake".to_owned())), ..Default::default() }; - let customer_kate_res: InsertResult = Customer::insert(customer_kate).exec(&ctx.db).await?; + let customer_kate_res = Customer::insert(customer_kate).exec(&ctx.db).await?; let kate_order_1 = order::ActiveModel { bakery_id: Set(seaside_bakery_res.last_insert_id as i32), customer_id: Set(customer_kate_res.last_insert_id as i32), @@ -583,7 +583,7 @@ pub async fn linked() -> Result<(), DbErr> { placed_at: Set(Utc::now().naive_utc()), ..Default::default() }; - let kate_order_1_res: InsertResult = Order::insert(kate_order_1).exec(&ctx.db).await?; + let kate_order_1_res = Order::insert(kate_order_1).exec(&ctx.db).await?; lineitem::ActiveModel { cake_id: Set(cheese_cake_res.last_insert_id as i32), order_id: Set(kate_order_1_res.last_insert_id as i32), @@ -600,7 +600,7 @@ pub async fn linked() -> Result<(), DbErr> { placed_at: Set(Utc::now().naive_utc()), ..Default::default() }; - let kate_order_2_res: InsertResult = Order::insert(kate_order_2).exec(&ctx.db).await?; + let kate_order_2_res = Order::insert(kate_order_2).exec(&ctx.db).await?; lineitem::ActiveModel { cake_id: Set(chocolate_cake_res.last_insert_id as i32), order_id: Set(kate_order_2_res.last_insert_id as i32), @@ -617,7 +617,7 @@ pub async fn linked() -> Result<(), DbErr> { notes: Set(Some("Loves all cakes".to_owned())), ..Default::default() }; - let customer_kara_res: InsertResult = Customer::insert(customer_kara).exec(&ctx.db).await?; + let customer_kara_res = Customer::insert(customer_kara).exec(&ctx.db).await?; let kara_order_1 = order::ActiveModel { bakery_id: Set(seaside_bakery_res.last_insert_id as i32), customer_id: Set(customer_kara_res.last_insert_id as i32), @@ -625,7 +625,7 @@ pub async fn linked() -> Result<(), DbErr> { placed_at: Set(Utc::now().naive_utc()), ..Default::default() }; - let kara_order_1_res: InsertResult = Order::insert(kara_order_1).exec(&ctx.db).await?; + let kara_order_1_res = Order::insert(kara_order_1).exec(&ctx.db).await?; lineitem::ActiveModel { cake_id: Set(mud_cake_res.last_insert_id as i32), order_id: Set(kara_order_1_res.last_insert_id as i32), @@ -642,7 +642,7 @@ pub async fn linked() -> Result<(), DbErr> { placed_at: Set(Utc::now().naive_utc()), ..Default::default() }; - let kara_order_2_res: InsertResult = Order::insert(kara_order_2).exec(&ctx.db).await?; + let kara_order_2_res = Order::insert(kara_order_2).exec(&ctx.db).await?; lineitem::ActiveModel { cake_id: Set(cheese_cake_res.last_insert_id as i32), order_id: Set(kara_order_2_res.last_insert_id as i32), From 4d9789fef48236780702423c4bb10b82a0183429 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 2 Sep 2021 15:05:01 +0800 Subject: [PATCH 68/89] cargo fmt doc comments --- rustfmt.toml | 1 + 1 file changed, 1 insertion(+) create mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..521706a0 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +format_code_in_doc_comments=true \ No newline at end of file From 5060890888fc38e4693eaa9a329cda1b7d2c69bd Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Sep 2021 14:56:58 +0800 Subject: [PATCH 69/89] cargo +nightly fmt --- src/entity/base_entity.rs | 26 +++++++++----- src/entity/column.rs | 4 +-- src/executor/select.rs | 76 ++++++++++++++++++++++----------------- src/lib.rs | 6 ++-- src/query/insert.rs | 42 +++++++++++----------- src/query/update.rs | 2 +- 6 files changed, 86 insertions(+), 70 deletions(-) diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index 29984953..67f817a7 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -137,13 +137,18 @@ pub trait EntityTrait: EntityName { /// assert_eq!( /// db.into_transaction_log(), /// vec![ - /// Transaction::from_sql_and_values( - /// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#, vec![1u64.into()] - /// ), - /// Transaction::from_sql_and_values( - /// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, vec![] - /// ), - /// ]); + /// Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#, + /// vec![1u64.into()] + /// ), + /// Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, + /// vec![] + /// ), + /// ] + /// ); /// ``` fn find() -> Select { Select::new() @@ -186,8 +191,11 @@ pub trait EntityTrait: EntityName { /// assert_eq!( /// db.into_transaction_log(), /// vec![Transaction::from_sql_and_values( - /// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "cake"."id" = $1"#, vec![11i32.into()] - /// )]); + /// DbBackend::Postgres, + /// r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "cake"."id" = $1"#, + /// vec![11i32.into()] + /// )] + /// ); /// ``` /// Find by composite key /// ``` diff --git a/src/entity/column.rs b/src/entity/column.rs index 611950f5..baaf190c 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -103,7 +103,7 @@ pub trait ColumnTrait: IdenStatic + Iterable + FromStr { /// /// assert_eq!( /// cake::Entity::find() - /// .filter(cake::Column::Id.between(2,3)) + /// .filter(cake::Column::Id.between(2, 3)) /// .build(DbBackend::MySql) /// .to_string(), /// "SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` BETWEEN 2 AND 3" @@ -121,7 +121,7 @@ pub trait ColumnTrait: IdenStatic + Iterable + FromStr { /// /// assert_eq!( /// cake::Entity::find() - /// .filter(cake::Column::Id.not_between(2,3)) + /// .filter(cake::Column::Id.not_between(2, 3)) /// .build(DbBackend::MySql) /// .to_string(), /// "SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` NOT BETWEEN 2 AND 3" diff --git a/src/executor/select.rs b/src/executor/select.rs index 0a5163de..ef30bb3d 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -290,14 +290,15 @@ where /// /// # let _: Result<(), DbErr> = smol::block_on(async { /// # - /// let res: Vec = cake::Entity::find().from_raw_sql( - /// Statement::from_sql_and_values( - /// DbBackend::Postgres, r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#, vec![] - /// ) - /// ) - /// .into_model::() - /// .all(&db) - /// .await?; + /// let res: Vec = cake::Entity::find() + /// .from_raw_sql(Statement::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#, + /// vec![], + /// )) + /// .into_model::() + /// .all(&db) + /// .await?; /// /// assert_eq!( /// res, @@ -318,11 +319,12 @@ where /// /// assert_eq!( /// db.into_transaction_log(), - /// vec![ - /// Transaction::from_sql_and_values( - /// DbBackend::Postgres, r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#, vec![] - /// ), - /// ]); + /// vec![Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#, + /// vec![] + /// ),] + /// ); /// ``` pub fn into_model(self) -> SelectorRaw> where @@ -407,22 +409,26 @@ where /// /// # let _: Result<(), DbErr> = smol::block_on(async { /// # - /// let _: Option = cake::Entity::find().from_raw_sql( - /// Statement::from_sql_and_values( - /// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = $1"#, vec![1.into()] - /// ) - /// ).one(&db).await?; + /// let _: Option = cake::Entity::find() + /// .from_raw_sql(Statement::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = $1"#, + /// vec![1.into()], + /// )) + /// .one(&db) + /// .await?; /// # /// # Ok(()) /// # }); /// /// assert_eq!( /// db.into_transaction_log(), - /// vec![ - /// Transaction::from_sql_and_values( - /// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = $1"#, vec![1.into()] - /// ), - /// ]); + /// vec![Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = $1"#, + /// vec![1.into()] + /// ),] + /// ); /// ``` pub async fn one(self, db: &DatabaseConnection) -> Result, DbErr> { let row = db.query_one(self.stmt).await?; @@ -442,22 +448,26 @@ where /// /// # let _: Result<(), DbErr> = smol::block_on(async { /// # - /// let _: Vec = cake::Entity::find().from_raw_sql( - /// Statement::from_sql_and_values( - /// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, vec![] - /// ) - /// ).all(&db).await?; + /// let _: Vec = cake::Entity::find() + /// .from_raw_sql(Statement::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, + /// vec![], + /// )) + /// .all(&db) + /// .await?; /// # /// # Ok(()) /// # }); /// /// assert_eq!( /// db.into_transaction_log(), - /// vec![ - /// Transaction::from_sql_and_values( - /// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, vec![] - /// ), - /// ]); + /// vec![Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, + /// vec![] + /// ),] + /// ); /// ``` pub async fn all(self, db: &DatabaseConnection) -> Result, DbErr> { let rows = db.query_all(self.stmt).await?; diff --git a/src/lib.rs b/src/lib.rs index ee5bb8cb..24022090 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,10 +77,8 @@ //! let fruits: Vec = cheese.find_related(Fruit).all(db).await?; //! //! // find related models (eager) -//! let cake_with_fruits: Vec<(cake::Model, Vec)> = Cake::find() -//! .find_with_related(Fruit) -//! .all(db) -//! .await?; +//! let cake_with_fruits: Vec<(cake::Model, Vec)> = +//! Cake::find().find_with_related(Fruit).all(db).await?; //! //! # Ok(()) //! # } diff --git a/src/query/insert.rs b/src/query/insert.rs index abd52b4b..a65071e1 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -43,11 +43,11 @@ where /// /// assert_eq!( /// Insert::one(cake::Model { - /// id: 1, - /// name: "Apple Pie".to_owned(), - /// }) - /// .build(DbBackend::Postgres) - /// .to_string(), + /// id: 1, + /// name: "Apple Pie".to_owned(), + /// }) + /// .build(DbBackend::Postgres) + /// .to_string(), /// r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie')"#, /// ); /// ``` @@ -57,11 +57,11 @@ where /// /// assert_eq!( /// Insert::one(cake::ActiveModel { - /// id: Unset(None), - /// name: Set("Apple Pie".to_owned()), - /// }) - /// .build(DbBackend::Postgres) - /// .to_string(), + /// id: Unset(None), + /// name: Set("Apple Pie".to_owned()), + /// }) + /// .build(DbBackend::Postgres) + /// .to_string(), /// r#"INSERT INTO "cake" ("name") VALUES ('Apple Pie')"#, /// ); /// ``` @@ -79,17 +79,17 @@ where /// /// assert_eq!( /// Insert::many(vec![ - /// cake::Model { - /// id: 1, - /// name: "Apple Pie".to_owned(), - /// }, - /// cake::Model { - /// id: 2, - /// name: "Orange Scone".to_owned(), - /// } - /// ]) - /// .build(DbBackend::Postgres) - /// .to_string(), + /// cake::Model { + /// id: 1, + /// name: "Apple Pie".to_owned(), + /// }, + /// cake::Model { + /// id: 2, + /// name: "Orange Scone".to_owned(), + /// } + /// ]) + /// .build(DbBackend::Postgres) + /// .to_string(), /// r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie'), (2, 'Orange Scone')"#, /// ); /// ``` diff --git a/src/query/update.rs b/src/query/update.rs index 21fd39cb..8cd66c37 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -59,7 +59,7 @@ impl Update { /// Update many ActiveModel /// /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::fruit, sea_query::Expr, DbBackend}; + /// use sea_orm::{entity::*, query::*, sea_query::Expr, tests_cfg::fruit, DbBackend}; /// /// assert_eq!( /// Update::many(fruit::Entity) From ca56f2790941480753b413ed4c86da4d509515fc Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Sep 2021 16:20:57 +0800 Subject: [PATCH 70/89] cargo clippy --- .github/workflows/rust.yml | 18 ++++++++++++++++++ tests/basic.rs | 3 +-- tests/common/mod.rs | 2 +- tests/common/setup/schema.rs | 3 +-- tests/crud/create_cake.rs | 3 +-- tests/crud/create_lineitem.rs | 1 - tests/crud/create_order.rs | 1 - tests/crud/mod.rs | 16 +++++++++++----- tests/crud/updates.rs | 6 +++--- tests/crud_tests.rs | 32 ++++++++++++++++---------------- tests/primary_key_tests.rs | 5 +++-- tests/query_tests.rs | 6 +++--- tests/relational_tests.rs | 12 ++++++------ tests/sequential_op_tests.rs | 12 ++++++------ 14 files changed, 70 insertions(+), 50 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 426cb003..f2fc7579 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -11,6 +11,24 @@ env: jobs: + clippy: + name: Clippy + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + components: clippy + override: true + + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-targets + compile-sqlite: name: Compile SQLite runs-on: ubuntu-20.04 diff --git a/tests/basic.rs b/tests/basic.rs index 78e3f25c..e62f1ad6 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -1,7 +1,6 @@ pub mod common; -#[allow(unused_imports)] -use sea_orm::{entity::*, error::*, sea_query, tests_cfg::*, Database, DbConn}; +pub use sea_orm::{entity::*, error::*, sea_query, tests_cfg::*, Database, DbConn}; // DATABASE_URL="sqlite::memory:" cargo test --features sqlx-sqlit,runtime-async-std --test basic #[sea_orm_macros::test] diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 54630999..2392ff72 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -19,7 +19,7 @@ impl TestContext { let db: DatabaseConnection = setup::setup(&base_url, test_name).await; Self { - base_url: base_url, + base_url, db_name: test_name.to_string(), db, } diff --git a/tests/common/setup/schema.rs b/tests/common/setup/schema.rs index 32e7d8a5..e3844dd2 100644 --- a/tests/common/setup/schema.rs +++ b/tests/common/setup/schema.rs @@ -1,11 +1,10 @@ +pub use super::super::bakery_chain::*; 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, diff --git a/tests/crud/create_cake.rs b/tests/crud/create_cake.rs index eea74350..4fa914a5 100644 --- a/tests/crud/create_cake.rs +++ b/tests/crud/create_cake.rs @@ -51,7 +51,6 @@ pub async fn test_create_cake(db: &DbConn) { 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 = CakesBakers::insert(cake_baker.clone()) .exec(db) @@ -70,7 +69,7 @@ pub async fn test_create_cake(db: &DbConn) { 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!(!cake_model.gluten_free); assert_eq!( cake_model .find_related(Bakery) diff --git a/tests/crud/create_lineitem.rs b/tests/crud/create_lineitem.rs index 9acba961..da82cc82 100644 --- a/tests/crud/create_lineitem.rs +++ b/tests/crud/create_lineitem.rs @@ -50,7 +50,6 @@ pub async fn test_create_lineitem(db: &DbConn) { 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 = CakesBakers::insert(cake_baker.clone()) .exec(db) diff --git a/tests/crud/create_order.rs b/tests/crud/create_order.rs index 8d10cfcd..ba8ff09b 100644 --- a/tests/crud/create_order.rs +++ b/tests/crud/create_order.rs @@ -50,7 +50,6 @@ pub async fn test_create_order(db: &DbConn) { 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 = CakesBakers::insert(cake_baker.clone()) .exec(db) diff --git a/tests/crud/mod.rs b/tests/crud/mod.rs index 7e024055..916878c8 100644 --- a/tests/crud/mod.rs +++ b/tests/crud/mod.rs @@ -1,7 +1,3 @@ -use sea_orm::{entity::*, DbConn}; - -pub use super::common::bakery_chain::*; - pub mod create_baker; pub mod create_cake; pub mod create_lineitem; @@ -9,6 +5,16 @@ pub mod create_order; pub mod deletes; pub mod updates; +pub use create_baker::*; +pub use create_cake::*; +pub use create_lineitem::*; +pub use create_order::*; +pub use deletes::*; +pub use updates::*; + +pub use super::common::bakery_chain::*; +use sea_orm::{entity::*, DbConn}; + pub async fn test_create_bakery(db: &DbConn) { let seaside_bakery = bakery::ActiveModel { name: Set("SeaSide Bakery".to_owned()), @@ -28,7 +34,7 @@ pub async fn test_create_bakery(db: &DbConn) { assert!(bakery.is_some()); let bakery_model = bakery.unwrap(); assert_eq!(bakery_model.name, "SeaSide Bakery"); - assert_eq!(bakery_model.profit_margin, 10.4); + assert!((bakery_model.profit_margin - 10.4).abs() < f64::EPSILON); } pub async fn test_create_customer(db: &DbConn) { diff --git a/tests/crud/updates.rs b/tests/crud/updates.rs index 172c9c21..83b9a5d3 100644 --- a/tests/crud/updates.rs +++ b/tests/crud/updates.rs @@ -36,7 +36,7 @@ pub async fn test_update_cake(db: &DbConn) { 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!(!cake_model.gluten_free); let mut cake_am: cake::ActiveModel = cake_model.into(); cake_am.name = Set("Extra chocolate mud cake".to_owned()); @@ -75,7 +75,7 @@ pub async fn test_update_bakery(db: &DbConn) { assert!(bakery.is_some()); let bakery_model = bakery.unwrap(); assert_eq!(bakery_model.name, "SeaSide Bakery"); - assert_eq!(bakery_model.profit_margin, 10.4); + assert!((bakery_model.profit_margin - 10.40).abs() < f64::EPSILON); let mut bakery_am: bakery::ActiveModel = bakery_model.into(); bakery_am.name = Set("SeaBreeze Bakery".to_owned()); @@ -92,7 +92,7 @@ pub async fn test_update_bakery(db: &DbConn) { .expect("could not find bakery"); let bakery_model = bakery.unwrap(); assert_eq!(bakery_model.name, "SeaBreeze Bakery"); - assert_eq!(bakery_model.profit_margin, 12.00); + assert!((bakery_model.profit_margin - 12.00).abs() < f64::EPSILON); } pub async fn test_update_deleted_customer(db: &DbConn) { diff --git a/tests/crud_tests.rs b/tests/crud_tests.rs index 3c26ddfd..7edb3500 100644 --- a/tests/crud_tests.rs +++ b/tests/crud_tests.rs @@ -1,10 +1,10 @@ -use sea_orm::DatabaseConnection; - pub mod common; -pub use common::{bakery_chain::*, setup::*, TestContext}; - mod crud; +pub use common::{bakery_chain::*, setup::*, TestContext}; +pub use crud::*; +use sea_orm::DatabaseConnection; + // Run the test locally: // DATABASE_URL="mysql://root:root@localhost" cargo test --features sqlx-mysql,runtime-async-std --test crud_tests // DATABASE_URL="postgres://root:root@localhost" cargo test --features sqlx-postgres,runtime-async-std --test crud_tests @@ -20,18 +20,18 @@ async fn main() { ctx.delete().await; } -async fn create_entities(db: &DatabaseConnection) { - crud::test_create_bakery(db).await; - crud::create_baker::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; +pub async fn create_entities(db: &DatabaseConnection) { + test_create_bakery(db).await; + test_create_baker(db).await; + test_create_customer(db).await; + test_create_cake(db).await; + test_create_lineitem(db).await; + test_create_order(db).await; - crud::updates::test_update_cake(db).await; - crud::updates::test_update_bakery(db).await; - crud::updates::test_update_deleted_customer(db).await; + test_update_cake(db).await; + test_update_bakery(db).await; + test_update_deleted_customer(db).await; - crud::deletes::test_delete_cake(db).await; - crud::deletes::test_delete_bakery(db).await; + test_delete_cake(db).await; + test_delete_bakery(db).await; } diff --git a/tests/primary_key_tests.rs b/tests/primary_key_tests.rs index 74dd46e5..ea8255e9 100644 --- a/tests/primary_key_tests.rs +++ b/tests/primary_key_tests.rs @@ -1,6 +1,7 @@ -use sea_orm::{entity::prelude::*, DatabaseConnection, Set}; pub mod common; + pub use common::{bakery_chain::*, setup::*, TestContext}; +use sea_orm::{entity::prelude::*, DatabaseConnection, Set}; use uuid::Uuid; #[sea_orm_macros::test] @@ -19,7 +20,7 @@ async fn main() -> Result<(), DbErr> { Ok(()) } -async fn create_metadata(db: &DatabaseConnection) -> Result<(), DbErr> { +pub async fn create_metadata(db: &DatabaseConnection) -> Result<(), DbErr> { let metadata = metadata::ActiveModel { uuid: Set(Uuid::new_v4()), key: Set("markup".to_owned()), diff --git a/tests/query_tests.rs b/tests/query_tests.rs index 4e905686..2b5a2295 100644 --- a/tests/query_tests.rs +++ b/tests/query_tests.rs @@ -1,8 +1,8 @@ -use sea_orm::entity::*; -use sea_orm::QueryFilter; - pub mod common; + pub use common::{bakery_chain::*, setup::*, TestContext}; +pub use sea_orm::entity::*; +pub use sea_orm::QueryFilter; // Run the test locally: // DATABASE_URL="mysql://root:@localhost" cargo test --features sqlx-mysql,runtime-async-std --test query_tests diff --git a/tests/relational_tests.rs b/tests/relational_tests.rs index c950ddc6..caef9c3c 100644 --- a/tests/relational_tests.rs +++ b/tests/relational_tests.rs @@ -1,11 +1,11 @@ -use chrono::offset::Utc; -use rust_decimal::prelude::*; -use rust_decimal_macros::dec; -use sea_orm::{entity::*, query::*, DbErr, FromQueryResult}; -use uuid::Uuid; - pub mod common; + +pub use chrono::offset::Utc; pub use common::{bakery_chain::*, setup::*, TestContext}; +pub use rust_decimal::prelude::*; +pub use rust_decimal_macros::dec; +pub use sea_orm::{entity::*, query::*, DbErr, FromQueryResult}; +pub use uuid::Uuid; // Run the test locally: // DATABASE_URL="mysql://root:@localhost" cargo test --features sqlx-mysql,runtime-async-std-native-tls --test relational_tests diff --git a/tests/sequential_op_tests.rs b/tests/sequential_op_tests.rs index 6c490762..28333d84 100644 --- a/tests/sequential_op_tests.rs +++ b/tests/sequential_op_tests.rs @@ -1,11 +1,11 @@ -use chrono::offset::Utc; -use rust_decimal::prelude::*; -use rust_decimal_macros::dec; -use sea_orm::{entity::*, query::*, DatabaseConnection, FromQueryResult}; -use uuid::Uuid; - pub mod common; + +pub use chrono::offset::Utc; pub use common::{bakery_chain::*, setup::*, TestContext}; +pub use rust_decimal::prelude::*; +pub use rust_decimal_macros::dec; +pub use sea_orm::{entity::*, query::*, DatabaseConnection, FromQueryResult}; +pub use uuid::Uuid; // Run the test locally: // DATABASE_URL="mysql://root:@localhost" cargo test --features sqlx-mysql,runtime-async-std --test sequential_op_tests From e852a09498e7a7ad8ad24af5688f1f77d148d05f Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Sep 2021 16:51:22 +0800 Subject: [PATCH 71/89] cargo clippy --- .github/workflows/rust.yml | 2 +- sea-orm-codegen/src/entity/column.rs | 27 +++++------------------ sea-orm-codegen/src/entity/transformer.rs | 14 ++++++------ sea-orm-codegen/src/entity/writer.rs | 2 +- 4 files changed, 15 insertions(+), 30 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f2fc7579..b7f014e1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -27,7 +27,7 @@ jobs: - uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - args: --all-targets + args: --all-targets --all compile-sqlite: name: Compile SQLite diff --git a/sea-orm-codegen/src/entity/column.rs b/sea-orm-codegen/src/entity/column.rs index a2c3b4f5..0b9eb8de 100644 --- a/sea-orm-codegen/src/entity/column.rs +++ b/sea-orm-codegen/src/entity/column.rs @@ -119,33 +119,18 @@ impl From<&ColumnDef> for Column { Some(ty) => ty.clone(), None => panic!("ColumnType should not be empty"), }; - let auto_increments: Vec = col_def + let auto_increment = 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 = col_def + .any(|spec| matches!(spec, ColumnSpec::AutoIncrement)); + let not_null = col_def .get_column_spec() .iter() - .filter_map(|spec| match spec { - ColumnSpec::NotNull => Some(true), - _ => None, - }) - .collect(); - let not_null = !not_nulls.is_empty(); - let uniques: Vec = col_def + .any(|spec| matches!(spec, ColumnSpec::NotNull)); + let unique = col_def .get_column_spec() .iter() - .filter_map(|spec| match spec { - ColumnSpec::UniqueKey => Some(true), - _ => None, - }) - .collect(); - let unique = !uniques.is_empty(); + .any(|spec| matches!(spec, ColumnSpec::UniqueKey)); Self { name, col_type, diff --git a/sea-orm-codegen/src/entity/transformer.rs b/sea-orm-codegen/src/entity/transformer.rs index 75dc25c7..840eade0 100644 --- a/sea-orm-codegen/src/entity/transformer.rs +++ b/sea-orm-codegen/src/entity/transformer.rs @@ -34,11 +34,6 @@ impl EntityTransformer { .iter() .map(|col_def| col_def.into()) .collect(); - let unique_columns: Vec = columns - .iter() - .filter(|col| col.unique) - .map(|col| col.name.clone()) - .collect(); let relations = table_create .get_foreign_key_create_stmts() .iter() @@ -85,8 +80,13 @@ impl EntityTransformer { false => { let ref_table = rel.ref_table; let mut unique = true; - for col in rel.columns.iter() { - if !unique_columns.contains(col) { + for column in rel.columns.iter() { + if !entity + .columns + .iter() + .filter(|col| col.unique) + .any(|col| col.name.as_str() == column) + { unique = false; break; } diff --git a/sea-orm-codegen/src/entity/writer.rs b/sea-orm-codegen/src/entity/writer.rs index 7accac2d..19e9af1c 100644 --- a/sea-orm-codegen/src/entity/writer.rs +++ b/sea-orm-codegen/src/entity/writer.rs @@ -308,7 +308,7 @@ mod tests { use sea_query::ColumnType; use std::io::{self, BufRead, BufReader}; - const ENTITY_FILES: [&'static str; 5] = [ + const ENTITY_FILES: [&str; 5] = [ include_str!("../../tests/entity/cake.rs"), include_str!("../../tests/entity/cake_filling.rs"), include_str!("../../tests/entity/filling.rs"), From 6b17cc50bc57d57354564192757c96f6b25d49d3 Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Fri, 3 Sep 2021 22:00:28 +1000 Subject: [PATCH 72/89] Can edit a post --- examples/rocket_example/src/main.rs | 40 +++++++++++++++--- examples/rocket_example/static/css/style.css | 17 ++++++++ .../rocket_example/templates/edit.html.tera | 42 +++++++++++++++++++ .../rocket_example/templates/index.html.tera | 9 +--- 4 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 examples/rocket_example/templates/edit.html.tera diff --git a/examples/rocket_example/src/main.rs b/examples/rocket_example/src/main.rs index 7048e4d4..9a55deac 100644 --- a/examples/rocket_example/src/main.rs +++ b/examples/rocket_example/src/main.rs @@ -46,6 +46,29 @@ async fn create(conn: Connection, post_form: Form) -> Flash", data = "")] +async fn update(conn: Connection, id: i64, post_form: Form) -> Flash { + let post: post::ActiveModel = Post::find_by_id(id) + .one(&conn) + .await + .unwrap() + .unwrap() + .into(); + + let post_data = post_form.into_inner(); + + let _edited_post = post::ActiveModel { + id: post.id, + title: Set(post_data.title.to_owned()), + text: Set(post_data.text.to_owned()), + } + .save(&conn) + .await + .expect("could not edit post"); + + Flash::success(Redirect::to("/"), "Post successfully edited.") +} + #[get("/")] async fn list(conn: Connection, flash: Option>) -> Template { let posts = Post::find() @@ -66,16 +89,18 @@ async fn list(conn: Connection, flash: Option>) -> Template } #[get("/")] -async fn read(conn: Connection, id: i64) -> Option> { +async fn edit(conn: Connection, id: i64) -> Template { let post: Option = Post::find_by_id(id) .one(&conn) .await .expect("could not find post"); - match post { - None => None, - Some(post) => Some(Json(post)), - } + Template::render( + "edit", + context! { + post: post, + }, + ) } #[delete("/")] @@ -120,7 +145,10 @@ fn rocket() -> _ { .attach(Db::init()) .attach(AdHoc::try_on_ignite("Migrations", run_migrations)) .mount("/", FileServer::from(relative!("/static"))) - .mount("/", routes![new, create, delete, destroy, list, read,]) + .mount( + "/", + routes![new, create, delete, destroy, list, edit, update], + ) .register("/", catchers![not_found]) .attach(Template::fairing()) } diff --git a/examples/rocket_example/static/css/style.css b/examples/rocket_example/static/css/style.css index 1b6a1720..62c2f904 100644 --- a/examples/rocket_example/static/css/style.css +++ b/examples/rocket_example/static/css/style.css @@ -57,3 +57,20 @@ button.small { line-height: 20px; margin: 0 2.5px; } + +.post { +} + +.post:hover { + background-color: #bce2ee; +} + +.post td { + padding: 5px; + width: 150px; +} + +#delete-button { + color: red; + border-color: red; +} diff --git a/examples/rocket_example/templates/edit.html.tera b/examples/rocket_example/templates/edit.html.tera new file mode 100644 index 00000000..a59b3093 --- /dev/null +++ b/examples/rocket_example/templates/edit.html.tera @@ -0,0 +1,42 @@ +{% extends "base" %} {% block content %} +
+

Edit Post

+
+
+
+
+ + +
+
+ +
+ +
+
+
+
+ + +
+ +
+
+
+{% endblock content %} diff --git a/examples/rocket_example/templates/index.html.tera b/examples/rocket_example/templates/index.html.tera index 077f7ff1..6ccbff4b 100644 --- a/examples/rocket_example/templates/index.html.tera +++ b/examples/rocket_example/templates/index.html.tera @@ -12,20 +12,13 @@
- {% for post in posts %} - + - {% endfor %} From 3f41f92690bf962e1dd1f7e19c1b2abf28a30a35 Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Fri, 3 Sep 2021 22:20:46 +1000 Subject: [PATCH 73/89] Order posts by id --- examples/rocket_example/src/main.rs | 3 ++- examples/rocket_example/static/css/style.css | 3 --- src/executor/query.rs | 14 +++++++++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/examples/rocket_example/src/main.rs b/examples/rocket_example/src/main.rs index 9a55deac..8dd92fca 100644 --- a/examples/rocket_example/src/main.rs +++ b/examples/rocket_example/src/main.rs @@ -6,12 +6,12 @@ use rocket::form::{Context, Form}; use rocket::fs::{relative, FileServer}; use rocket::request::FlashMessage; use rocket::response::{Flash, Redirect}; -use rocket::serde::json::Json; use rocket::{Build, Request, Rocket}; use rocket_db_pools::{sqlx, Connection, Database}; use rocket_dyn_templates::{context, Template}; use sea_orm::entity::*; +use sea_orm::QueryOrder; use sea_orm::RocketDbPool; mod setup; @@ -72,6 +72,7 @@ async fn update(conn: Connection, id: i64, post_form: Form) -> #[get("/")] async fn list(conn: Connection, flash: Option>) -> Template { let posts = Post::find() + .order_by_asc(post::Column::Id) .all(&conn) .await .expect("could not retrieve posts") diff --git a/examples/rocket_example/static/css/style.css b/examples/rocket_example/static/css/style.css index 62c2f904..ac2720d3 100644 --- a/examples/rocket_example/static/css/style.css +++ b/examples/rocket_example/static/css/style.css @@ -58,9 +58,6 @@ button.small { margin: 0 2.5px; } -.post { -} - .post:hover { background-color: #bce2ee; } diff --git a/src/executor/query.rs b/src/executor/query.rs index 08728081..d880063d 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -103,6 +103,7 @@ macro_rules! try_getable_all { .and_then(|opt| opt.ok_or(TryGetError::Null)) } #[cfg(feature = "mock")] + #[allow(unused_variables)] QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| { debug_print!("{:#?}", e.to_string()); TryGetError::Null @@ -138,6 +139,7 @@ macro_rules! try_getable_unsigned { .and_then(|opt| opt.ok_or(TryGetError::Null)) } #[cfg(feature = "mock")] + #[allow(unused_variables)] QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| { debug_print!("{:#?}", e.to_string()); TryGetError::Null @@ -170,6 +172,7 @@ macro_rules! try_getable_mysql { panic!("{} unsupported by sqlx-sqlite", stringify!($type)) } #[cfg(feature = "mock")] + #[allow(unused_variables)] QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| { debug_print!("{:#?}", e.to_string()); TryGetError::Null @@ -202,6 +205,7 @@ macro_rules! try_getable_postgres { panic!("{} unsupported by sqlx-sqlite", stringify!($type)) } #[cfg(feature = "mock")] + #[allow(unused_variables)] QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| { debug_print!("{:#?}", e.to_string()); TryGetError::Null @@ -264,12 +268,16 @@ 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")] + #[allow(unused_variables)] QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| { debug_print!("{:#?}", e.to_string()); TryGetError::Null From 5b6e3b04b94d40d96df0ab83e199ae1e0dce68c1 Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Fri, 3 Sep 2021 22:28:30 +1000 Subject: [PATCH 74/89] Cancel edit button --- examples/rocket_example/templates/edit.html.tera | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/rocket_example/templates/edit.html.tera b/examples/rocket_example/templates/edit.html.tera index a59b3093..47882e65 100644 --- a/examples/rocket_example/templates/edit.html.tera +++ b/examples/rocket_example/templates/edit.html.tera @@ -25,7 +25,15 @@ />
- +
+ + + +
+
+
+ +
From 4cb5bf962bbcc64fa1c1b9dadd1a86abc4a28c36 Mon Sep 17 00:00:00 2001 From: Sam Samai Date: Fri, 3 Sep 2021 22:41:02 +1000 Subject: [PATCH 75/89] Add ValueType --- examples/rocket_example/src/post.rs | 2 ++ examples/rocket_example/templates/index.html.tera | 7 ++++++- examples/rocket_example/templates/new.html.tera | 14 +++++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/examples/rocket_example/src/post.rs b/examples/rocket_example/src/post.rs index da7a6443..0bce0e7a 100644 --- a/examples/rocket_example/src/post.rs +++ b/examples/rocket_example/src/post.rs @@ -35,6 +35,8 @@ pub enum PrimaryKey { } impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { true } diff --git a/examples/rocket_example/templates/index.html.tera b/examples/rocket_example/templates/index.html.tera index 6ccbff4b..621f18b8 100644 --- a/examples/rocket_example/templates/index.html.tera +++ b/examples/rocket_example/templates/index.html.tera @@ -24,5 +24,10 @@
ID Title Text
{{ post.id }} {{ post.title }} {{ post.text }} - -
-

Add Post

+ + {% endblock content %} diff --git a/examples/rocket_example/templates/new.html.tera b/examples/rocket_example/templates/new.html.tera index d00379c6..57daffe2 100644 --- a/examples/rocket_example/templates/new.html.tera +++ b/examples/rocket_example/templates/new.html.tera @@ -2,7 +2,7 @@

New Post

-
+
-
- +
+
+ + + +
+
+
+ +
From 9665355d226b9bd7446282b42719a6d383e05dd4 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 3 Sep 2021 21:54:38 +0800 Subject: [PATCH 76/89] Screenshot --- examples/rocket_example/README.md | 2 ++ examples/rocket_example/Screenshot.png | Bin 0 -> 26165 bytes 2 files changed, 2 insertions(+) create mode 100644 examples/rocket_example/Screenshot.png diff --git a/examples/rocket_example/README.md b/examples/rocket_example/README.md index ad8b8918..360abb68 100644 --- a/examples/rocket_example/README.md +++ b/examples/rocket_example/README.md @@ -1,3 +1,5 @@ +![screenshot](Screenshot.png) + # Rocket with SeaOrm example app - modify the `url` var in `Rocket.toml` to point to your chosen database diff --git a/examples/rocket_example/Screenshot.png b/examples/rocket_example/Screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..237c4f75e8a9f76e2999647638feb986394b9837 GIT binary patch literal 26165 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 Tfd|D-;}X^u=gdiF7jFFrk*L~q literal 0 HcmV?d00001 From e6b5d3ebdce27e512cdc3bc9f590ede7c439a3c3 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 3 Sep 2021 21:58:11 +0800 Subject: [PATCH 77/89] Edit --- examples/rocket_example/README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/rocket_example/README.md b/examples/rocket_example/README.md index 360abb68..35a20a07 100644 --- a/examples/rocket_example/README.md +++ b/examples/rocket_example/README.md @@ -1,8 +1,11 @@ ![screenshot](Screenshot.png) -# Rocket with SeaOrm example app +# Rocket with SeaORM example app -- modify the `url` var in `Rocket.toml` to point to your chosen database -- turn on the appropriate database feature for your chosen db in `Cargo.toml` -- `cargo run` to start the server -- open browser to the address shown in `🚀 Rocket has launched from ` line +1. Modify the `url` var in `Rocket.toml` to point to your chosen database + +1. Turn on the appropriate database feature for your chosen db in `Cargo.toml` (the `default = ["sqlx-mysql"]`) + +1. `cargo run` to start the server + +1. Open in browser after seeing the `🚀 Rocket has launched from http://localhost:8000` line From 9be7440e5841fb45ea67c9010aa7d69bb54322db Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 3 Sep 2021 22:05:48 +0800 Subject: [PATCH 78/89] Readme --- README.md | 10 ++++------ examples/rocket_example/README.md | 2 +- src/lib.rs | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 82696f29..7249b784 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ This is an early release of SeaORM, the API is not stable yet. ``` [![Getting Started](https://img.shields.io/badge/Getting%20Started-blue)](https://www.sea-ql.org/SeaORM/docs/index) -[![Examples](https://img.shields.io/badge/Examples-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples) -[![Starter Kit](https://img.shields.io/badge/Starter%20Kit-green)](https://github.com/SeaQL/sea-orm/issues/37) +[![Examples](https://img.shields.io/badge/Examples-green)](https://github.com/SeaQL/sea-orm/tree/master/examples) +[![Rocket Example](https://img.shields.io/badge/Rocket%20Example-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv) ## Features @@ -68,10 +68,8 @@ let cheese: cake::Model = cheese.unwrap(); let fruits: Vec = cheese.find_related(Fruit).all(db).await?; // find related models (eager) -let cake_with_fruits: Vec<(cake::Model, Vec)> = Cake::find() - .find_with_related(Fruit) - .all(db) - .await?; +let cake_with_fruits: Vec<(cake::Model, Vec)> = + Cake::find().find_with_related(Fruit).all(db).await?; ``` ### Insert diff --git a/examples/rocket_example/README.md b/examples/rocket_example/README.md index 35a20a07..596a763a 100644 --- a/examples/rocket_example/README.md +++ b/examples/rocket_example/README.md @@ -4,7 +4,7 @@ 1. Modify the `url` var in `Rocket.toml` to point to your chosen database -1. Turn on the appropriate database feature for your chosen db in `Cargo.toml` (the `default = ["sqlx-mysql"]`) +1. Turn on the appropriate database feature for your chosen db in `Cargo.toml` (the `default = ["sqlx-postgres"]` line) 1. `cargo run` to start the server diff --git a/src/lib.rs b/src/lib.rs index 24022090..ed475e6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,8 +32,8 @@ //! ``` //! //! [![Getting Started](https://img.shields.io/badge/Getting%20Started-blue)](https://www.sea-ql.org/SeaORM/docs/index) -//! [![Examples](https://img.shields.io/badge/Examples-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples) -//! [![Starter Kit](https://img.shields.io/badge/Starter%20Kit-green)](https://github.com/SeaQL/sea-orm/issues/37) +//! [![Examples](https://img.shields.io/badge/Examples-green)](https://github.com/SeaQL/sea-orm/tree/master/examples) +//! [![Rocket Example](https://img.shields.io/badge/Rocket%20Example-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) //! [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv) //! //! ## Features From 5c23cf77391d2408b62b900d397d7df0b248e70c Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 3 Sep 2021 22:27:16 +0800 Subject: [PATCH 79/89] Cargo --- Cargo.toml | 10 ++++------ examples/rocket_example/Cargo.toml | 21 +++++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 16b4a378..a096135d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,24 +23,22 @@ path = "src/lib.rs" [dependencies] async-stream = { version = "^0.3" } +async-trait = { version = "0.1", optional = true } chrono = { version = "^0", optional = true } futures = { version = "^0.3" } futures-util = { version = "^0.3" } +log = { version = "^0.4", optional = true } +rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", optional = true } rust_decimal = { version = "^1", optional = true } sea-orm-macros = { version = "^0.1.1", path = "sea-orm-macros", optional = true } sea-query = { version = "^0.16", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } +serde_json = { version = "^1", optional = true } sqlx = { version = "^0.5", optional = true } sqlx-core = { version = "^0.5", optional = true } sqlx-macros = { version = "^0.5", optional = true } -serde_json = { version = "^1", optional = true } uuid = { version = "0.8", features = ["serde", "v4"], optional = true } -rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", features = [ - "sqlx_mysql", -], optional = true } -async-trait = { version = "0.1", optional = true } -log = { version = "^0.4", optional = true } [dev-dependencies] smol = { version = "^1.2" } diff --git a/examples/rocket_example/Cargo.toml b/examples/rocket_example/Cargo.toml index be96f986..644dd412 100644 --- a/examples/rocket_example/Cargo.toml +++ b/examples/rocket_example/Cargo.toml @@ -1,31 +1,32 @@ [package] -name = "rocket-example" +name = "sea-orm-rocket-example" version = "0.1.0" +authors = ["Sam Samai "] edition = "2018" publish = false + [workspace] + [dependencies] +async-stream = { version = "^0.3" } +futures = { version = "^0.3" } +futures-util = { version = "^0.3" } rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = [ "json", ] } -rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", features = [] } +rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git" } rocket_dyn_templates = { git = "https://github.com/SergioBenitez/Rocket.git", features = [ "tera", ] } sea-orm = { path = "../../", features = ["web-api-rocket"] } -sea-query = { version = "^0.12.8" } - serde_json = { version = "^1" } -futures = { version = "^0.3" } -async-stream = { version = "^0.3" } -futures-util = { version = "^0.3" } [dependencies.sqlx] -version = "0.5.1" +version = "^0.5" default-features = false features = ["macros", "offline", "migrate"] [features] default = ["sqlx-postgres"] -sqlx-postgres = ["sea-orm/sqlx-postgres"] -sqlx-mysql = ["sea-orm/sqlx-mysql"] +sqlx-mysql = ["sea-orm/sqlx-mysql", "rocket_db_pools/sqlx_mysql"] +sqlx-postgres = ["sea-orm/sqlx-postgres", "rocket_db_pools/sqlx_postgres"] From 60fdc787663e6bce31b824659978616380774a6a Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 3 Sep 2021 23:11:31 +0800 Subject: [PATCH 80/89] Move RocketDbPool --- Cargo.toml | 3 --- examples/rocket_example/Cargo.toml | 1 + examples/rocket_example/src/main.rs | 4 +++- .../rocket_db.rs => examples/rocket_example/src/pool.rs | 6 +++--- src/driver/mod.rs | 5 ----- 5 files changed, 7 insertions(+), 12 deletions(-) rename src/driver/rocket_db.rs => examples/rocket_example/src/pool.rs (78%) diff --git a/Cargo.toml b/Cargo.toml index a096135d..a5d534c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,12 +23,10 @@ path = "src/lib.rs" [dependencies] async-stream = { version = "^0.3" } -async-trait = { version = "0.1", optional = true } chrono = { version = "^0", optional = true } futures = { version = "^0.3" } futures-util = { version = "^0.3" } log = { version = "^0.4", optional = true } -rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git", optional = true } rust_decimal = { version = "^1", optional = true } sea-orm-macros = { version = "^0.1.1", path = "sea-orm-macros", optional = true } sea-query = { version = "^0.16", features = ["thread-safe"] } @@ -92,4 +90,3 @@ runtime-actix-rustls = ["sqlx/runtime-actix-rustls", "runtime-actix"] runtime-tokio = [] runtime-tokio-native-tls = ["sqlx/runtime-tokio-native-tls", "runtime-tokio"] runtime-tokio-rustls = ["sqlx/runtime-tokio-rustls", "runtime-tokio"] -web-api-rocket = ["rocket_db_pools", "async-trait"] diff --git a/examples/rocket_example/Cargo.toml b/examples/rocket_example/Cargo.toml index 644dd412..944f8da5 100644 --- a/examples/rocket_example/Cargo.toml +++ b/examples/rocket_example/Cargo.toml @@ -9,6 +9,7 @@ publish = false [dependencies] async-stream = { version = "^0.3" } +async-trait = { version = "0.1" } futures = { version = "^0.3" } futures-util = { version = "^0.3" } rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = [ diff --git a/examples/rocket_example/src/main.rs b/examples/rocket_example/src/main.rs index 8dd92fca..b5b60b4b 100644 --- a/examples/rocket_example/src/main.rs +++ b/examples/rocket_example/src/main.rs @@ -12,7 +12,9 @@ use rocket_dyn_templates::{context, Template}; use sea_orm::entity::*; use sea_orm::QueryOrder; -use sea_orm::RocketDbPool; + +mod pool; +use pool::RocketDbPool; mod setup; diff --git a/src/driver/rocket_db.rs b/examples/rocket_example/src/pool.rs similarity index 78% rename from src/driver/rocket_db.rs rename to examples/rocket_example/src/pool.rs index 5489e051..094746ba 100644 --- a/src/driver/rocket_db.rs +++ b/examples/rocket_example/src/pool.rs @@ -8,9 +8,9 @@ pub struct RocketDbPool { #[async_trait] impl rocket_db_pools::Pool for RocketDbPool { - type Error = crate::DbErr; + type Error = sea_orm::DbErr; - type Connection = crate::DatabaseConnection; + type Connection = sea_orm::DatabaseConnection; async fn init(figment: &Figment) -> Result { let config = figment.extract::().unwrap(); @@ -22,6 +22,6 @@ impl rocket_db_pools::Pool for RocketDbPool { } async fn get(&self) -> Result { - Ok(crate::Database::connect(&self.db_url).await.unwrap()) + Ok(sea_orm::Database::connect(&self.db_url).await.unwrap()) } } diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 66ea7c79..6f6cfb64 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -19,8 +19,3 @@ pub use sqlx_mysql::*; pub use sqlx_postgres::*; #[cfg(feature = "sqlx-sqlite")] pub use sqlx_sqlite::*; - -#[cfg(feature = "web-api-rocket")] -mod rocket_db; -#[cfg(feature = "web-api-rocket")] -pub use rocket_db::*; From 9a1b771e41bc6bfd28dbd1de5a8e52f7203816c7 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 3 Sep 2021 22:54:47 +0800 Subject: [PATCH 81/89] 0.2.0 --- CHANGELOG.md | 14 ++++++++++++++ Cargo.toml | 2 +- examples/rocket_example/Cargo.toml | 3 ++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9035c40..88555996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## 0.2.0 - 2021-09-03 + +- [[#37]] Rocket example +- [[#114]] `log` crate and `env-logger` +- [[#103]] `InsertResult` to return the primary key's type +- [[#89]] Represent several relations between same types by `Linked` +- [[#59]] Transforming an Entity into `TableCreateStatement` + +[#37]: https://github.com/SeaQL/sea-orm/issues/37 +[#114]: https://github.com/SeaQL/sea-orm/issues/114 +[#103]: https://github.com/SeaQL/sea-orm/issues/103 +[#89]: https://github.com/SeaQL/sea-orm/issues/89 +[#59]: https://github.com/SeaQL/sea-orm/issues/59 + ## 0.1.3 - 2021-08-30 - [[#108]] Remove impl TryGetable for Option diff --git a/Cargo.toml b/Cargo.toml index a5d534c1..a74b3658 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "sea-orm-macros", "sea-orm-codegen"] [package] name = "sea-orm" -version = "0.1.3" +version = "0.2.0" authors = ["Chris Tsang "] edition = "2018" description = "🐚 An async & dynamic ORM for Rust" diff --git a/examples/rocket_example/Cargo.toml b/examples/rocket_example/Cargo.toml index 944f8da5..1b4303c5 100644 --- a/examples/rocket_example/Cargo.toml +++ b/examples/rocket_example/Cargo.toml @@ -19,7 +19,8 @@ rocket_db_pools = { git = "https://github.com/SergioBenitez/Rocket.git" } rocket_dyn_templates = { git = "https://github.com/SergioBenitez/Rocket.git", features = [ "tera", ] } -sea-orm = { path = "../../", features = ["web-api-rocket"] } +# remove `path = ""` in your own project +sea-orm = { path = "../../", version = "^0.2" } serde_json = { version = "^1" } [dependencies.sqlx] From e9e864c382b747174d6322d83b53a58f433c44af Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 3 Sep 2021 23:35:17 +0800 Subject: [PATCH 82/89] Readme --- README.md | 1 + src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 7249b784..dceb8f59 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ fruit::Entity::delete_many() 1. [Design](https://github.com/SeaQL/sea-orm/tree/master/DESIGN.md) 1. [Architecture](https://github.com/SeaQL/sea-orm/tree/master/ARCHITECTURE.md) +1. [Compare with Diesel](https://www.sea-ql.org/SeaORM/docs/internal-design/diesel) ## License diff --git a/src/lib.rs b/src/lib.rs index ed475e6b..2a41d159 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,6 +190,7 @@ //! //! 1. [Design](https://github.com/SeaQL/sea-orm/tree/master/DESIGN.md) //! 1. [Architecture](https://github.com/SeaQL/sea-orm/tree/master/ARCHITECTURE.md) +//! 1. [Compare with Diesel](https://www.sea-ql.org/SeaORM/docs/internal-design/diesel) //! //! ## License //! From 9483a5af8b14e3710ac980b2a38dd71023e87cf7 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sat, 4 Sep 2021 01:46:35 +0800 Subject: [PATCH 83/89] sea-orm-codegen 0.2.0 --- sea-orm-codegen/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sea-orm-codegen/Cargo.toml b/sea-orm-codegen/Cargo.toml index 9080bd2b..56cd72ae 100644 --- a/sea-orm-codegen/Cargo.toml +++ b/sea-orm-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sea-orm-codegen" -version = "0.1.3" +version = "0.2.0" authors = ["Billy Chan "] edition = "2018" description = "Code Generator for SeaORM" From cb6c84fc15c3a5866a253827e4d220a86824d3c4 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sat, 4 Sep 2021 01:48:09 +0800 Subject: [PATCH 84/89] sea-orm-cli 0.2.0 --- sea-orm-cli/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sea-orm-cli/Cargo.toml b/sea-orm-cli/Cargo.toml index 7c9ec510..46340f7b 100644 --- a/sea-orm-cli/Cargo.toml +++ b/sea-orm-cli/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "sea-orm-cli" -version = "0.1.3" +version = "0.2.0" authors = [ "Billy Chan " ] edition = "2018" description = "Command line utility for SeaORM" @@ -22,7 +22,7 @@ clap = { version = "^2.33.3" } dotenv = { version = "^0.15" } async-std = { version = "^1.9", features = [ "attributes" ] } sea-orm = { version = "^0.1.2", features = [ "sqlx-all" ] } -sea-orm-codegen = { version = "^0.1.3" } +sea-orm-codegen = { version = "^0.2.0" } sea-schema = { version = "^0.2.7", default-features = false, features = [ "sqlx-mysql", "sqlx-postgres", From a9d9b499d11c2381d7f3c162f66ba2cbeb2d09b1 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sat, 4 Sep 2021 02:05:31 +0800 Subject: [PATCH 85/89] sea-orm-macros 0.2.0 --- Cargo.toml | 2 +- sea-orm-macros/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a74b3658..02b9bf36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ futures = { version = "^0.3" } futures-util = { version = "^0.3" } log = { version = "^0.4", optional = true } rust_decimal = { version = "^1", optional = true } -sea-orm-macros = { version = "^0.1.1", path = "sea-orm-macros", optional = true } +sea-orm-macros = { version = "^0.2", path = "sea-orm-macros", optional = true } sea-query = { version = "^0.16", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } diff --git a/sea-orm-macros/Cargo.toml b/sea-orm-macros/Cargo.toml index 312adeaa..93782047 100644 --- a/sea-orm-macros/Cargo.toml +++ b/sea-orm-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sea-orm-macros" -version = "0.1.1" +version = "0.2.0" authors = [ "Billy Chan " ] edition = "2018" description = "Derive macros for SeaORM" From 44c2e6d1c00cbddac3ba0ce3dc7d27e23541a598 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sat, 4 Sep 2021 19:54:57 +0800 Subject: [PATCH 86/89] 0.2.1 --- Cargo.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 02b9bf36..d1148546 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "sea-orm-macros", "sea-orm-codegen"] [package] name = "sea-orm" -version = "0.2.0" +version = "0.2.1" authors = ["Chris Tsang "] edition = "2018" description = "🐚 An async & dynamic ORM for Rust" @@ -34,8 +34,6 @@ sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1", optional = true } sqlx = { version = "^0.5", optional = true } -sqlx-core = { version = "^0.5", optional = true } -sqlx-macros = { version = "^0.5", optional = true } uuid = { version = "0.8", features = ["serde", "v4"], optional = true } [dev-dependencies] From d1d5915364bc5aea2031cf838dbb3044fa0a9414 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sat, 4 Sep 2021 22:22:17 +0800 Subject: [PATCH 87/89] Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88555996..4bea7112 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## 0.2.1 - 2021-09-04 + +- Update dependencies + ## 0.2.0 - 2021-09-03 - [[#37]] Rocket example From de1f5d397df26659ef11ee9cc97251964503c748 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sat, 4 Sep 2021 22:24:01 +0800 Subject: [PATCH 88/89] Readme --- README.md | 4 ---- src/lib.rs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/README.md b/README.md index dceb8f59..9696b6d3 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,6 @@ SeaORM is a relational ORM to help you build light weight and concurrent web services in Rust. -```markdown -This is an early release of SeaORM, the API is not stable yet. -``` - [![Getting Started](https://img.shields.io/badge/Getting%20Started-blue)](https://www.sea-ql.org/SeaORM/docs/index) [![Examples](https://img.shields.io/badge/Examples-green)](https://github.com/SeaQL/sea-orm/tree/master/examples) [![Rocket Example](https://img.shields.io/badge/Rocket%20Example-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) diff --git a/src/lib.rs b/src/lib.rs index 2a41d159..6e63d2ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,10 +27,6 @@ //! //! SeaORM is a relational ORM to help you build light weight and concurrent web services in Rust. //! -//! ```markdown -//! This is an early release of SeaORM, the API is not stable yet. -//! ``` -//! //! [![Getting Started](https://img.shields.io/badge/Getting%20Started-blue)](https://www.sea-ql.org/SeaORM/docs/index) //! [![Examples](https://img.shields.io/badge/Examples-green)](https://github.com/SeaQL/sea-orm/tree/master/examples) //! [![Rocket Example](https://img.shields.io/badge/Rocket%20Example-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) From 8d8415817de060d529dfd958cc77ce0417b89eac Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 6 Sep 2021 17:09:27 +0800 Subject: [PATCH 89/89] Add `nullable` to ColumnDef --- src/entity/column.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/entity/column.rs b/src/entity/column.rs index baaf190c..6e871f4d 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -248,7 +248,11 @@ impl ColumnDef { self } - pub fn null(mut self) -> Self { + pub fn null(self) -> Self { + self.nullable() + } + + pub fn nullable(mut self) -> Self { self.null = true; self }