From 57201788ef219e67b44b5b2d7574bc5658b3efee Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 20 Sep 2021 10:33:29 +0800 Subject: [PATCH 01/14] Codegen `TimestampWithTimeZone` fixup --- sea-orm-codegen/src/entity/column.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sea-orm-codegen/src/entity/column.rs b/sea-orm-codegen/src/entity/column.rs index 2bab8766..df286fc7 100644 --- a/sea-orm-codegen/src/entity/column.rs +++ b/sea-orm-codegen/src/entity/column.rs @@ -43,7 +43,6 @@ impl Column { ColumnType::Uuid => "Uuid", ColumnType::Binary(_) => "Vec", ColumnType::Boolean => "bool", - _ => unimplemented!(), } .parse() .unwrap(); @@ -91,6 +90,7 @@ impl Column { }, ColumnType::DateTime(_) => quote! { ColumnType::DateTime.def() }, ColumnType::Timestamp(_) => quote! { ColumnType::Timestamp.def() }, + ColumnType::TimestampWithTimeZone(_) => quote! { ColumnType::TimestampWithTimeZone.def() }, ColumnType::Time(_) => quote! { ColumnType::Time.def() }, ColumnType::Date => quote! { ColumnType::Date.def() }, ColumnType::Binary(_) => quote! { ColumnType::Binary.def() }, @@ -106,7 +106,6 @@ impl Column { let s = s.to_string(); quote! { ColumnType::Custom(#s.to_owned()).def() } } - _ => unimplemented!(), }; if !self.not_null { col_def.extend(quote! { From cb60c4afa3955a3723be93947f8ed823d9ddc522 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 20 Sep 2021 11:13:46 +0800 Subject: [PATCH 02/14] Keep match catchall --- sea-orm-codegen/src/entity/column.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sea-orm-codegen/src/entity/column.rs b/sea-orm-codegen/src/entity/column.rs index df286fc7..1f6815a0 100644 --- a/sea-orm-codegen/src/entity/column.rs +++ b/sea-orm-codegen/src/entity/column.rs @@ -43,6 +43,7 @@ impl Column { ColumnType::Uuid => "Uuid", ColumnType::Binary(_) => "Vec", ColumnType::Boolean => "bool", + _ => unimplemented!(), } .parse() .unwrap(); @@ -106,6 +107,7 @@ impl Column { let s = s.to_string(); quote! { ColumnType::Custom(#s.to_owned()).def() } } + _ => unimplemented!(), }; if !self.not_null { col_def.extend(quote! { From c9047d49e718395ca87426e3906bd3bbab99aa7e Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 20 Sep 2021 14:38:19 +0800 Subject: [PATCH 03/14] Update README & lib.rs --- README.md | 107 ++++++++++++++++++++++++ sea-orm-codegen/src/entity/column.rs | 4 +- src/entity/column.rs | 26 +++++- src/lib.rs | 120 +++++++++++++++++++++++++++ 4 files changed, 255 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1bd05a58..ae9e7bfc 100644 --- a/README.md +++ b/README.md @@ -31,18 +31,125 @@ SeaORM is a relational ORM to help you build light weight and concurrent web ser Relying on [SQLx](https://github.com/launchbadge/sqlx), SeaORM is a new library with async support from day 1. +```rust +// execute multiple queries in parallel +let cakes_and_fruits: (Vec, Vec) = + futures::try_join!(Cake::find().all(&db), Fruit::find().all(&db))?; + +``` + 2. Dynamic Built upon [SeaQuery](https://github.com/SeaQL/sea-query), SeaORM allows you to build complex queries without 'fighting the ORM'. +```rust +// build subquery with ease +let cakes_with_filling: Vec = cake::Entity::find() + .filter( + Condition::any().add( + cake::Column::Id.in_subquery( + Query::select() + .column(cake_filling::Column::CakeId) + .from(cake_filling::Entity) + .to_owned(), + ), + ), + ) + .all(&db) + .await?; + +``` + +[more on SeaQuery](https://docs.rs/sea-query/*/sea_query/) + 3. Testable Use mock connections to write unit tests for your logic. +```rust +// Setup mock connection +let db = MockDatabase::new(DbBackend::Postgres) + .append_query_results(vec![ + vec![ + cake::Model { + id: 1, + name: "New York Cheese".to_owned(), + }, + ], + ]) + .into_connection(); + +// Perform your application logic +assert_eq!( + cake::Entity::find().one(&db).await?, + Some(cake::Model { + id: 1, + name: "New York Cheese".to_owned(), + }) +); + +// Compare it against the expected transaction log +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()] + ), + ] +); +``` + +[more on testing](/docs/write-test/mock) + 4. Service oriented Quickly build services that join, filter, sort and paginate data in APIs. +```rust +#[get("/?&")] +async fn list( + conn: Connection, + posts_per_page: Option, + page: Option, + flash: Option>, +) -> Template { + // Set page number and items per page + let page = page.unwrap_or(0); + let posts_per_page = posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE); + + // Setup paginator + let paginator = Post::find() + .order_by_asc(post::Column::Id) + .paginate(&conn, posts_per_page); + let num_pages = paginator.num_pages().await.ok().unwrap(); + + // Fetch paginated posts + let posts = paginator + .fetch_page(page) + .await + .expect("could not retrieve posts"); + + let flash = flash.map(FlashMessage::into_inner); + + Template::render( + "index", + context! { + posts: posts, + flash: flash, + page: page, + posts_per_page: posts_per_page, + num_pages: num_pages, + }, + ) +} +``` + +[full Rocket example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) + +We are building more examples for other web frameworks too. + ## A quick taste of SeaORM ### Select diff --git a/sea-orm-codegen/src/entity/column.rs b/sea-orm-codegen/src/entity/column.rs index 1f6815a0..a031f3c9 100644 --- a/sea-orm-codegen/src/entity/column.rs +++ b/sea-orm-codegen/src/entity/column.rs @@ -91,7 +91,9 @@ impl Column { }, ColumnType::DateTime(_) => quote! { ColumnType::DateTime.def() }, ColumnType::Timestamp(_) => quote! { ColumnType::Timestamp.def() }, - ColumnType::TimestampWithTimeZone(_) => quote! { ColumnType::TimestampWithTimeZone.def() }, + ColumnType::TimestampWithTimeZone(_) => { + quote! { ColumnType::TimestampWithTimeZone.def() } + } ColumnType::Time(_) => quote! { ColumnType::Time.def() }, ColumnType::Date => quote! { ColumnType::Date.def() }, ColumnType::Binary(_) => quote! { ColumnType::Binary.def() }, diff --git a/src/entity/column.rs b/src/entity/column.rs index 26d8ec0e..e510756a 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -334,7 +334,7 @@ mod tests { use sea_query::Query; #[test] - fn test_in_subquery() { + fn test_in_subquery_1() { assert_eq!( cake::Entity::find() .filter( @@ -357,6 +357,30 @@ mod tests { ); } + #[test] + fn test_in_subquery_2() { + assert_eq!( + cake::Entity::find() + .filter( + Condition::any().add( + cake::Column::Id.in_subquery( + Query::select() + .column(cake_filling::Column::CakeId) + .from(cake_filling::Entity) + .to_owned() + ) + ) + ) + .build(DbBackend::MySql) + .to_string(), + [ + "SELECT `cake`.`id`, `cake`.`name` FROM `cake`", + "WHERE `cake`.`id` IN (SELECT `cake_id` FROM `cake_filling`)", + ] + .join(" ") + ); + } + #[test] fn test_col_from_str() { use std::str::FromStr; diff --git a/src/lib.rs b/src/lib.rs index 3a281ecd..9fea7569 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,18 +38,138 @@ //! //! Relying on [SQLx](https://github.com/launchbadge/sqlx), SeaORM is a new library with async support from day 1. //! +//! ``` +//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*}; +//! # async fn function(db: &DbConn) -> Result<(), DbErr> { +//! // execute multiple queries in parallel +//! let cakes_and_fruits: (Vec, Vec) = +//! futures::try_join!(Cake::find().all(&db), Fruit::find().all(&db))?; +//! +//! # Ok(()) +//! # } +//! ``` +//! //! 2. Dynamic //! //! Built upon [SeaQuery](https://github.com/SeaQL/sea-query), SeaORM allows you to build complex queries without 'fighting the ORM'. //! +//! ``` +//! # use sea_query::Query; +//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*}; +//! # async fn function(db: &DbConn) -> Result<(), DbErr> { +//! // build subquery with ease +//! let cakes_with_filling: Vec = cake::Entity::find() +//! .filter( +//! Condition::any().add( +//! cake::Column::Id.in_subquery( +//! Query::select() +//! .column(cake_filling::Column::CakeId) +//! .from(cake_filling::Entity) +//! .to_owned(), +//! ), +//! ), +//! ) +//! .all(&db) +//! .await?; +//! +//! # Ok(()) +//! # } +//! ``` +//! +//! [more on SeaQuery](https://docs.rs/sea-query/*/sea_query/) +//! //! 3. Testable //! //! Use mock connections to write unit tests for your logic. //! +//! ``` +//! # use sea_orm::{error::*, entity::*, query::*, tests_cfg::*, DbConn, MockDatabase, Transaction, DbBackend}; +//! # async fn function(db: &DbConn) -> Result<(), DbErr> { +//! // Setup mock connection +//! let db = MockDatabase::new(DbBackend::Postgres) +//! .append_query_results(vec![ +//! vec![ +//! cake::Model { +//! id: 1, +//! name: "New York Cheese".to_owned(), +//! }, +//! ], +//! ]) +//! .into_connection(); +//! +//! // Perform your application logic +//! assert_eq!( +//! cake::Entity::find().one(&db).await?, +//! Some(cake::Model { +//! id: 1, +//! name: "New York Cheese".to_owned(), +//! }) +//! ); +//! +//! // Compare it against the expected transaction log +//! 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()] +//! ), +//! ] +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! [more on testing](/docs/write-test/mock) +//! //! 4. Service oriented //! //! Quickly build services that join, filter, sort and paginate data in APIs. //! +//! ```ignore +//! #[get("/?&")] +//! async fn list( +//! conn: Connection, +//! posts_per_page: Option, +//! page: Option, +//! flash: Option>, +//! ) -> Template { +//! // Set page number and items per page +//! let page = page.unwrap_or(0); +//! let posts_per_page = posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE); +//! +//! // Setup paginator +//! let paginator = Post::find() +//! .order_by_asc(post::Column::Id) +//! .paginate(&conn, posts_per_page); +//! let num_pages = paginator.num_pages().await.ok().unwrap(); +//! +//! // Fetch paginated posts +//! let posts = paginator +//! .fetch_page(page) +//! .await +//! .expect("could not retrieve posts"); +//! +//! let flash = flash.map(FlashMessage::into_inner); +//! +//! Template::render( +//! "index", +//! context! { +//! posts: posts, +//! flash: flash, +//! page: page, +//! posts_per_page: posts_per_page, +//! num_pages: num_pages, +//! }, +//! ) +//! } +//! ``` +//! +//! [full Rocket example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) +//! +//! We are building more examples for other web frameworks too. +//! //! ## A quick taste of SeaORM //! //! ### Select From b22753bebf3280b24d5726e7aca5c1fff8b7b473 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 20 Sep 2021 15:05:39 +0800 Subject: [PATCH 04/14] Test `try_join!` --- README.md | 1 - src/database/mock.rs | 32 ++++++++++++++++----------- src/lib.rs | 51 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index ae9e7bfc..c362a592 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,6 @@ Relying on [SQLx](https://github.com/launchbadge/sqlx), SeaORM is a new library // execute multiple queries in parallel let cakes_and_fruits: (Vec, Vec) = futures::try_join!(Cake::find().all(&db), Fruit::find().all(&db))?; - ``` 2. Dynamic diff --git a/src/database/mock.rs b/src/database/mock.rs index e35280cc..ccb34a49 100644 --- a/src/database/mock.rs +++ b/src/database/mock.rs @@ -29,19 +29,6 @@ pub trait IntoMockRow { fn into_mock_row(self) -> MockRow; } -impl IntoMockRow for M -where - M: ModelTrait, -{ - fn into_mock_row(self) -> MockRow { - let mut values = BTreeMap::new(); - for col in <::Column>::iter() { - values.insert(col.to_string(), self.get(col)); - } - MockRow { values } - } -} - impl MockDatabase { pub fn new(db_backend: DbBackend) -> Self { Self { @@ -121,6 +108,25 @@ impl MockRow { } } +impl IntoMockRow for MockRow { + fn into_mock_row(self) -> MockRow { + self + } +} + +impl IntoMockRow for M +where + M: ModelTrait, +{ + fn into_mock_row(self) -> MockRow { + let mut values = BTreeMap::new(); + for col in <::Column>::iter() { + values.insert(col.to_string(), self.get(col)); + } + MockRow { values } + } +} + impl IntoMockRow for BTreeMap<&str, Value> { fn into_mock_row(self) -> MockRow { MockRow { diff --git a/src/lib.rs b/src/lib.rs index 9fea7569..7b54e2fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,14 +39,57 @@ //! Relying on [SQLx](https://github.com/launchbadge/sqlx), SeaORM is a new library with async support from day 1. //! //! ``` -//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*}; -//! # async fn function(db: &DbConn) -> Result<(), DbErr> { +//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*, DatabaseConnection, DbBackend, MockDatabase, Transaction, IntoMockRow}; +//! # let db = MockDatabase::new(DbBackend::Postgres) +//! # .append_query_results(vec![ +//! # vec![cake::Model { +//! # id: 1, +//! # name: "New York Cheese".to_owned(), +//! # } +//! # .into_mock_row()], +//! # vec![fruit::Model { +//! # id: 1, +//! # name: "Apple".to_owned(), +//! # cake_id: Some(1), +//! # } +//! # .into_mock_row()], +//! # ]) +//! # .into_connection(); +//! # let _: Result<(), DbErr> = smol::block_on(async { //! // execute multiple queries in parallel //! let cakes_and_fruits: (Vec, Vec) = //! futures::try_join!(Cake::find().all(&db), Fruit::find().all(&db))?; -//! +//! # assert_eq!( +//! # cakes_and_fruits, +//! # ( +//! # vec![cake::Model { +//! # id: 1, +//! # name: "New York Cheese".to_owned(), +//! # }], +//! # vec![fruit::Model { +//! # id: 1, +//! # name: "Apple".to_owned(), +//! # cake_id: Some(1), +//! # }] +//! # ) +//! # ); +//! # assert_eq!( +//! # db.into_transaction_log(), +//! # vec![ +//! # 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 "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit""#, +//! # vec![] +//! # ), +//! # ] +//! # ); //! # Ok(()) -//! # } +//! # }); //! ``` //! //! 2. Dynamic From ce35317dafdd08234bb45647f25dda5fa33909fd Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 20 Sep 2021 15:15:39 +0800 Subject: [PATCH 05/14] Refactor rocket example --- README.md | 9 +++------ examples/rocket_example/src/main.rs | 13 +++++++------ src/lib.rs | 15 ++++++--------- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index c362a592..362be72c 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,6 @@ async fn list( let paginator = Post::find() .order_by_asc(post::Column::Id) .paginate(&conn, posts_per_page); - let num_pages = paginator.num_pages().await.ok().unwrap(); // Fetch paginated posts let posts = paginator @@ -130,16 +129,14 @@ async fn list( .await .expect("could not retrieve posts"); - let flash = flash.map(FlashMessage::into_inner); - Template::render( "index", context! { - posts: posts, - flash: flash, page: page, posts_per_page: posts_per_page, - num_pages: num_pages, + posts: posts, + flash: flash.map(FlashMessage::into_inner), + num_pages: paginator.num_pages().await.ok().unwrap(), }, ) } diff --git a/examples/rocket_example/src/main.rs b/examples/rocket_example/src/main.rs index 87899d84..c1df1ee1 100644 --- a/examples/rocket_example/src/main.rs +++ b/examples/rocket_example/src/main.rs @@ -79,28 +79,29 @@ async fn list( page: Option, flash: Option>, ) -> Template { + // Set page number and items per page let page = page.unwrap_or(0); let posts_per_page = posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE); + + // Setup paginator let paginator = Post::find() .order_by_asc(post::Column::Id) .paginate(&conn, posts_per_page); - let num_pages = paginator.num_pages().await.ok().unwrap(); + // Fetch paginated posts let posts = paginator .fetch_page(page) .await .expect("could not retrieve posts"); - let flash = flash.map(FlashMessage::into_inner); - Template::render( "index", context! { - posts: posts, - flash: flash, page: page, posts_per_page: posts_per_page, - num_pages: num_pages, + posts: posts, + flash: flash.map(FlashMessage::into_inner), + num_pages: paginator.num_pages().await.ok().unwrap(), }, ) } diff --git a/src/lib.rs b/src/lib.rs index 7b54e2fc..6e58def9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -181,29 +181,26 @@ //! // Set page number and items per page //! let page = page.unwrap_or(0); //! let posts_per_page = posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE); -//! +//! //! // Setup paginator //! let paginator = Post::find() //! .order_by_asc(post::Column::Id) //! .paginate(&conn, posts_per_page); -//! let num_pages = paginator.num_pages().await.ok().unwrap(); -//! +//! //! // Fetch paginated posts //! let posts = paginator //! .fetch_page(page) //! .await //! .expect("could not retrieve posts"); -//! -//! let flash = flash.map(FlashMessage::into_inner); -//! +//! //! Template::render( //! "index", //! context! { -//! posts: posts, -//! flash: flash, //! page: page, //! posts_per_page: posts_per_page, -//! num_pages: num_pages, +//! posts: posts, +//! flash: flash.map(FlashMessage::into_inner), +//! num_pages: paginator.num_pages().await.ok().unwrap(), //! }, //! ) //! } From bb225b12ee504f069099bf6b3b9113d82c387e4b Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 20 Sep 2021 16:06:17 +0800 Subject: [PATCH 06/14] Rocket example page number starts from one --- examples/rocket_example/src/main.rs | 7 +++++-- examples/rocket_example/templates/index.html.tera | 4 ++-- src/executor/paginator.rs | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/rocket_example/src/main.rs b/examples/rocket_example/src/main.rs index c1df1ee1..e73df30f 100644 --- a/examples/rocket_example/src/main.rs +++ b/examples/rocket_example/src/main.rs @@ -80,8 +80,11 @@ async fn list( flash: Option>, ) -> Template { // Set page number and items per page - let page = page.unwrap_or(0); + let page = page.unwrap_or(1); let posts_per_page = posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE); + if page == 0 { + panic!("Page number cannot be zero"); + } // Setup paginator let paginator = Post::find() @@ -90,7 +93,7 @@ async fn list( // Fetch paginated posts let posts = paginator - .fetch_page(page) + .fetch_page(page - 1) .await .expect("could not retrieve posts"); diff --git a/examples/rocket_example/templates/index.html.tera b/examples/rocket_example/templates/index.html.tera index 0cba1b7d..e755e1f7 100644 --- a/examples/rocket_example/templates/index.html.tera +++ b/examples/rocket_example/templates/index.html.tera @@ -26,9 +26,9 @@ - {% if page == 0 %} Previous {% else %} + {% if page == 1 %} Previous {% else %} Previous - {% endif %} | {% if page == num_pages - 1 %} Next {% else %} + {% endif %} | {% if page == num_pages %} Next {% else %} Next {% endif %} diff --git a/src/executor/paginator.rs b/src/executor/paginator.rs index 0cc7acbc..24822111 100644 --- a/src/executor/paginator.rs +++ b/src/executor/paginator.rs @@ -24,7 +24,7 @@ impl<'db, S> Paginator<'db, S> where S: SelectorTrait + 'db, { - /// Fetch a specific page + /// Fetch a specific page; page index starts from zero pub async fn fetch_page(&self, page: usize) -> Result, DbErr> { let query = self .query From 5c8caddf1f2ef08b7ddce4012d7d214a153888ff Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 20 Sep 2021 16:09:21 +0800 Subject: [PATCH 07/14] Update docs --- README.md | 8 +++----- src/lib.rs | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 362be72c..7e9e2b49 100644 --- a/README.md +++ b/README.md @@ -112,11 +112,10 @@ async fn list( conn: Connection, posts_per_page: Option, page: Option, - flash: Option>, ) -> Template { // Set page number and items per page - let page = page.unwrap_or(0); - let posts_per_page = posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE); + let page = page.unwrap_or(1); + let posts_per_page = posts_per_page.unwrap_or(10); // Setup paginator let paginator = Post::find() @@ -125,7 +124,7 @@ async fn list( // Fetch paginated posts let posts = paginator - .fetch_page(page) + .fetch_page(page - 1) .await .expect("could not retrieve posts"); @@ -135,7 +134,6 @@ async fn list( page: page, posts_per_page: posts_per_page, posts: posts, - flash: flash.map(FlashMessage::into_inner), num_pages: paginator.num_pages().await.ok().unwrap(), }, ) diff --git a/src/lib.rs b/src/lib.rs index 6e58def9..f82c4241 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -176,11 +176,10 @@ //! conn: Connection, //! posts_per_page: Option, //! page: Option, -//! flash: Option>, //! ) -> Template { //! // Set page number and items per page -//! let page = page.unwrap_or(0); -//! let posts_per_page = posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE); +//! let page = page.unwrap_or(1); +//! let posts_per_page = posts_per_page.unwrap_or(10); //! //! // Setup paginator //! let paginator = Post::find() @@ -189,7 +188,7 @@ //! //! // Fetch paginated posts //! let posts = paginator -//! .fetch_page(page) +//! .fetch_page(page - 1) //! .await //! .expect("could not retrieve posts"); //! @@ -199,7 +198,6 @@ //! page: page, //! posts_per_page: posts_per_page, //! posts: posts, -//! flash: flash.map(FlashMessage::into_inner), //! num_pages: paginator.num_pages().await.ok().unwrap(), //! }, //! ) From 2176bad009213439e684e84f49cd2d0ba9b7f172 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 20 Sep 2021 16:26:03 +0800 Subject: [PATCH 08/14] Indentation --- README.md | 16 ++++------------ src/lib.rs | 16 ++++------------ 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 7e9e2b49..c0f99ecc 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ SeaORM is a relational ORM to help you build light weight and concurrent web ser 1. Async -Relying on [SQLx](https://github.com/launchbadge/sqlx), SeaORM is a new library with async support from day 1. + Relying on [SQLx](https://github.com/launchbadge/sqlx), SeaORM is a new library with async support from day 1. ```rust // execute multiple queries in parallel @@ -39,7 +39,7 @@ let cakes_and_fruits: (Vec, Vec) = 2. Dynamic -Built upon [SeaQuery](https://github.com/SeaQL/sea-query), SeaORM allows you to build complex queries without 'fighting the ORM'. + Built upon [SeaQuery](https://github.com/SeaQL/sea-query), SeaORM allows you to build complex queries without 'fighting the ORM'. ```rust // build subquery with ease @@ -59,11 +59,9 @@ let cakes_with_filling: Vec = cake::Entity::find() ``` -[more on SeaQuery](https://docs.rs/sea-query/*/sea_query/) - 3. Testable -Use mock connections to write unit tests for your logic. + Use mock connections to write unit tests for your logic. ```rust // Setup mock connection @@ -100,11 +98,9 @@ assert_eq!( ); ``` -[more on testing](/docs/write-test/mock) - 4. Service oriented -Quickly build services that join, filter, sort and paginate data in APIs. + Quickly build services that join, filter, sort and paginate data in APIs. ```rust #[get("/?&")] @@ -140,10 +136,6 @@ async fn list( } ``` -[full Rocket example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) - -We are building more examples for other web frameworks too. - ## A quick taste of SeaORM ### Select diff --git a/src/lib.rs b/src/lib.rs index f82c4241..4eed86c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,7 @@ //! //! 1. Async //! -//! Relying on [SQLx](https://github.com/launchbadge/sqlx), SeaORM is a new library with async support from day 1. +//! Relying on [SQLx](https://github.com/launchbadge/sqlx), SeaORM is a new library with async support from day 1. //! //! ``` //! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*, DatabaseConnection, DbBackend, MockDatabase, Transaction, IntoMockRow}; @@ -94,7 +94,7 @@ //! //! 2. Dynamic //! -//! Built upon [SeaQuery](https://github.com/SeaQL/sea-query), SeaORM allows you to build complex queries without 'fighting the ORM'. +//! Built upon [SeaQuery](https://github.com/SeaQL/sea-query), SeaORM allows you to build complex queries without 'fighting the ORM'. //! //! ``` //! # use sea_query::Query; @@ -119,11 +119,9 @@ //! # } //! ``` //! -//! [more on SeaQuery](https://docs.rs/sea-query/*/sea_query/) -//! //! 3. Testable //! -//! Use mock connections to write unit tests for your logic. +//! Use mock connections to write unit tests for your logic. //! //! ``` //! # use sea_orm::{error::*, entity::*, query::*, tests_cfg::*, DbConn, MockDatabase, Transaction, DbBackend}; @@ -164,11 +162,9 @@ //! # } //! ``` //! -//! [more on testing](/docs/write-test/mock) -//! //! 4. Service oriented //! -//! Quickly build services that join, filter, sort and paginate data in APIs. +//! Quickly build services that join, filter, sort and paginate data in APIs. //! //! ```ignore //! #[get("/?&")] @@ -204,10 +200,6 @@ //! } //! ``` //! -//! [full Rocket example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) -//! -//! We are building more examples for other web frameworks too. -//! //! ## A quick taste of SeaORM //! //! ### Select From a2ef73783c87182776be4ecec3d519951ca4b519 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 20 Sep 2021 16:59:53 +0800 Subject: [PATCH 09/14] cargo fmt --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4eed86c6..db9c39d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -176,18 +176,18 @@ //! // Set page number and items per page //! let page = page.unwrap_or(1); //! let posts_per_page = posts_per_page.unwrap_or(10); -//! +//! //! // Setup paginator //! let paginator = Post::find() //! .order_by_asc(post::Column::Id) //! .paginate(&conn, posts_per_page); -//! +//! //! // Fetch paginated posts //! let posts = paginator //! .fetch_page(page - 1) //! .await //! .expect("could not retrieve posts"); -//! +//! //! Template::render( //! "index", //! context! { From 20123ef8cd30e6237a1df0ab9352c5440d11e924 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 20 Sep 2021 17:09:33 +0800 Subject: [PATCH 10/14] Test parallel CRUD --- tests/parallel_tests.rs | 108 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 tests/parallel_tests.rs diff --git a/tests/parallel_tests.rs b/tests/parallel_tests.rs new file mode 100644 index 00000000..dc997c7f --- /dev/null +++ b/tests/parallel_tests.rs @@ -0,0 +1,108 @@ +pub mod common; + +pub use common::{bakery_chain::*, setup::*, TestContext}; +use pretty_assertions::assert_eq; +use sea_orm::{entity::prelude::*, DatabaseConnection, IntoActiveModel, Set}; + +#[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_parallel_tests").await; + crud_in_parallel(&ctx.db).await?; + ctx.delete().await; + + Ok(()) +} + +pub async fn crud_in_parallel(db: &DatabaseConnection) -> Result<(), DbErr> { + let metadata = vec![ + metadata::Model { + uuid: Uuid::new_v4(), + key: "markup".to_owned(), + value: "1.18".to_owned(), + bytes: vec![1, 2, 3], + }, + metadata::Model { + uuid: Uuid::new_v4(), + key: "exchange_rate".to_owned(), + value: "0.78".to_owned(), + bytes: vec![1, 2, 3], + }, + metadata::Model { + uuid: Uuid::new_v4(), + key: "service_charge".to_owned(), + value: "1.1".to_owned(), + bytes: vec![1, 2, 3], + }, + ]; + + let _insert_res = futures::try_join!( + metadata[0].clone().into_active_model().insert(db), + metadata[1].clone().into_active_model().insert(db), + metadata[2].clone().into_active_model().insert(db), + )?; + + let find_res = futures::try_join!( + Metadata::find_by_id(metadata[0].uuid.clone()).one(db), + Metadata::find_by_id(metadata[1].uuid.clone()).one(db), + Metadata::find_by_id(metadata[2].uuid.clone()).one(db), + )?; + + assert_eq!( + metadata, + vec![ + find_res.0.clone().unwrap(), + find_res.1.clone().unwrap(), + find_res.2.clone().unwrap(), + ] + ); + + let mut active_models = ( + find_res.0.unwrap().into_active_model(), + find_res.1.unwrap().into_active_model(), + find_res.2.unwrap().into_active_model(), + ); + + active_models.0.bytes = Set(vec![0]); + active_models.1.bytes = Set(vec![1]); + active_models.2.bytes = Set(vec![2]); + + let _update_res = futures::try_join!( + active_models.0.clone().update(db), + active_models.1.clone().update(db), + active_models.2.clone().update(db), + )?; + + let find_res = futures::try_join!( + Metadata::find_by_id(metadata[0].uuid.clone()).one(db), + Metadata::find_by_id(metadata[1].uuid.clone()).one(db), + Metadata::find_by_id(metadata[2].uuid.clone()).one(db), + )?; + + assert_eq!( + vec![ + active_models.0.bytes.clone().unwrap(), + active_models.1.bytes.clone().unwrap(), + active_models.2.bytes.clone().unwrap(), + ], + vec![ + find_res.0.clone().unwrap().bytes, + find_res.1.clone().unwrap().bytes, + find_res.2.clone().unwrap().bytes, + ] + ); + + let _delete_res = futures::try_join!( + active_models.0.delete(db), + active_models.1.delete(db), + active_models.2.delete(db), + )?; + + assert_eq!(Metadata::find().all(db).await?, vec![]); + + Ok(()) +} From c66a2491cea72f1728ebb1e3e05fc73ac60a1941 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 20 Sep 2021 18:06:51 +0800 Subject: [PATCH 11/14] Edit README --- README.md | 11 ++++++----- src/lib.rs | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c0f99ecc..212ca758 100644 --- a/README.md +++ b/README.md @@ -106,17 +106,18 @@ assert_eq!( #[get("/?&")] async fn list( conn: Connection, - posts_per_page: Option, page: Option, + per_page: Option, ) -> Template { // Set page number and items per page let page = page.unwrap_or(1); - let posts_per_page = posts_per_page.unwrap_or(10); + let per_page = per_page.unwrap_or(10); // Setup paginator let paginator = Post::find() .order_by_asc(post::Column::Id) - .paginate(&conn, posts_per_page); + .paginate(&conn, per_page); + let num_pages = paginator.num_pages().await.unwrap(); // Fetch paginated posts let posts = paginator @@ -128,9 +129,9 @@ async fn list( "index", context! { page: page, - posts_per_page: posts_per_page, + per_page: per_page, posts: posts, - num_pages: paginator.num_pages().await.ok().unwrap(), + num_pages: num_pages, }, ) } diff --git a/src/lib.rs b/src/lib.rs index db9c39d4..e8c80b4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -170,17 +170,18 @@ //! #[get("/?&")] //! async fn list( //! conn: Connection, -//! posts_per_page: Option, //! page: Option, +//! per_page: Option, //! ) -> Template { //! // Set page number and items per page //! let page = page.unwrap_or(1); -//! let posts_per_page = posts_per_page.unwrap_or(10); +//! let per_page = per_page.unwrap_or(10); //! //! // Setup paginator //! let paginator = Post::find() //! .order_by_asc(post::Column::Id) -//! .paginate(&conn, posts_per_page); +//! .paginate(&conn, per_page); +//! let num_pages = paginator.num_pages().await.unwrap(); //! //! // Fetch paginated posts //! let posts = paginator @@ -192,9 +193,9 @@ //! "index", //! context! { //! page: page, -//! posts_per_page: posts_per_page, +//! per_page: per_page, //! posts: posts, -//! num_pages: paginator.num_pages().await.ok().unwrap(), +//! num_pages: num_pages, //! }, //! ) //! } From f7b1c9b59a4eff7193399dc57d31c76724b8c787 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 20 Sep 2021 18:10:15 +0800 Subject: [PATCH 12/14] Edit README --- README.md | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 212ca758..1c0e83d1 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ assert_eq!( ); ``` -4. Service oriented +4. Service Oriented Quickly build services that join, filter, sort and paginate data in APIs. diff --git a/src/lib.rs b/src/lib.rs index e8c80b4e..4fd492c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -162,7 +162,7 @@ //! # } //! ``` //! -//! 4. Service oriented +//! 4. Service Oriented //! //! Quickly build services that join, filter, sort and paginate data in APIs. //! From 8dfbbc838501c1033de10f94f3aa171825a76f05 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 20 Sep 2021 18:15:38 +0800 Subject: [PATCH 13/14] Fix clippy warnings --- sea-orm-codegen/src/entity/column.rs | 1 + tests/parallel_tests.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/sea-orm-codegen/src/entity/column.rs b/sea-orm-codegen/src/entity/column.rs index a031f3c9..532f2e91 100644 --- a/sea-orm-codegen/src/entity/column.rs +++ b/sea-orm-codegen/src/entity/column.rs @@ -109,6 +109,7 @@ impl Column { let s = s.to_string(); quote! { ColumnType::Custom(#s.to_owned()).def() } } + #[allow(unreachable_patterns)] _ => unimplemented!(), }; if !self.not_null { diff --git a/tests/parallel_tests.rs b/tests/parallel_tests.rs index dc997c7f..0ac09fd6 100644 --- a/tests/parallel_tests.rs +++ b/tests/parallel_tests.rs @@ -47,9 +47,9 @@ pub async fn crud_in_parallel(db: &DatabaseConnection) -> Result<(), DbErr> { )?; let find_res = futures::try_join!( - Metadata::find_by_id(metadata[0].uuid.clone()).one(db), - Metadata::find_by_id(metadata[1].uuid.clone()).one(db), - Metadata::find_by_id(metadata[2].uuid.clone()).one(db), + Metadata::find_by_id(metadata[0].uuid).one(db), + Metadata::find_by_id(metadata[1].uuid).one(db), + Metadata::find_by_id(metadata[2].uuid).one(db), )?; assert_eq!( @@ -78,9 +78,9 @@ pub async fn crud_in_parallel(db: &DatabaseConnection) -> Result<(), DbErr> { )?; let find_res = futures::try_join!( - Metadata::find_by_id(metadata[0].uuid.clone()).one(db), - Metadata::find_by_id(metadata[1].uuid.clone()).one(db), - Metadata::find_by_id(metadata[2].uuid.clone()).one(db), + Metadata::find_by_id(metadata[0].uuid).one(db), + Metadata::find_by_id(metadata[1].uuid).one(db), + Metadata::find_by_id(metadata[2].uuid).one(db), )?; assert_eq!( From bfa66915e36152c5952c21bc9bf6db6c13623d2e Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Tue, 21 Sep 2021 11:10:54 +0800 Subject: [PATCH 14/14] Update Entity::update example --- src/entity/base_entity.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index 6f770162..7ba1e965 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -386,7 +386,10 @@ pub trait EntityTrait: EntityName { /// # let _: Result<(), DbErr> = smol::block_on(async { /// # /// assert_eq!( - /// orange.clone().update(&db).await?, // Clone here because we need to assert_eq + /// fruit::Entity::update(orange.clone()) + /// .filter(fruit::Column::Name.contains("orange")) + /// .exec(&db) + /// .await?, /// orange /// ); /// # @@ -396,7 +399,8 @@ pub trait EntityTrait: EntityName { /// assert_eq!( /// db.into_transaction_log(), /// vec![Transaction::from_sql_and_values( - /// DbBackend::Postgres, r#"UPDATE "fruit" SET "name" = $1 WHERE "fruit"."id" = $2"#, vec!["Orange".into(), 1i32.into()] + /// DbBackend::Postgres, r#"UPDATE "fruit" SET "name" = $1 WHERE "fruit"."id" = $2 AND "fruit"."name" LIKE $3"#, + /// vec!["Orange".into(), 1i32.into(), "%orange%".into()] /// )]); /// ``` fn update(model: A) -> UpdateOne