From c5468eb92f01324c248585990bc8196c6f5eab40 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 5 Nov 2021 12:41:49 +0800 Subject: [PATCH 01/31] Use "marlon-sousa/sea-query" --- Cargo.toml | 2 +- src/executor/insert.rs | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4244c3058..db5abc698 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ futures-util = { version = "^0.3" } log = { version = "^0.4", optional = true } rust_decimal = { version = "^1", optional = true } sea-orm-macros = { version = "^0.3.1", path = "sea-orm-macros", optional = true } -sea-query = { version = "^0.18.0", features = ["thread-safe"] } +sea-query = { version = "^0.18.0", git = "https://github.com/marlon-sousa/sea-query.git", branch = "extended-returning-support", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1", optional = true } diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 64b6c145b..8e97ccbc4 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,8 +1,8 @@ use crate::{ - error::*, ActiveModelTrait, ConnectionTrait, DbBackend, EntityTrait, Insert, PrimaryKeyTrait, - Statement, TryFromU64, + error::*, ActiveModelTrait, ConnectionTrait, DbBackend, EntityTrait, Insert, Iterable, + PrimaryKeyTrait, Statement, TryFromU64, }; -use sea_query::{FromValueTuple, InsertStatement, ValueTuple}; +use sea_query::{FromValueTuple, InsertStatement, IntoColumnRef, Returning, ValueTuple}; use std::{future::Future, marker::PhantomData}; /// Defines a structure to perform INSERT operations in an ActiveModel @@ -39,15 +39,14 @@ where { // so that self is dropped before entering await let mut query = self.query; - if db.get_database_backend() == DbBackend::Postgres { - use crate::{sea_query::Query, Iterable}; - if ::PrimaryKey::iter().count() > 0 { - query.returning( - Query::select() - .columns(::PrimaryKey::iter()) - .take(), - ); - } + if db.get_database_backend() == DbBackend::Postgres + && ::PrimaryKey::iter().count() > 0 + { + query.returning(Returning::Columns( + ::PrimaryKey::iter() + .map(|c| c.into_column_ref()) + .collect(), + )); } Inserter::::new(self.primary_key, query).exec(db) } From c39a3b8cb26ca7ec6bc230897dcb524d363a243f Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 5 Nov 2021 18:20:25 +0800 Subject: [PATCH 02/31] Insert with returning for Postgres --- src/entity/active_model.rs | 9 ++--- src/executor/insert.rs | 70 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index d46c6dbfc..ea8d47a2b 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -147,14 +147,9 @@ pub trait ActiveModelTrait: Clone + Debug { C: ConnectionTrait<'a>, { let am = ActiveModelBehavior::before_save(self, true)?; - let res = ::insert(am).exec(db).await?; - let found = ::find_by_id(res.last_insert_id) - .one(db) + let am = ::insert(am) + .exec_with_returning(db) .await?; - let am = match found { - Some(model) => model.into_active_model(), - None => return Err(DbErr::Exec("Failed to find inserted item".to_owned())), - }; ActiveModelBehavior::after_save(am, true) } diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 8e97ccbc4..363dccd9c 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,6 +1,6 @@ use crate::{ - error::*, ActiveModelTrait, ConnectionTrait, DbBackend, EntityTrait, Insert, Iterable, - PrimaryKeyTrait, Statement, TryFromU64, + error::*, ActiveModelTrait, ConnectionTrait, DbBackend, EntityTrait, Insert, IntoActiveModel, + Iterable, PrimaryKeyTrait, SelectModel, SelectorRaw, Statement, TryFromU64, }; use sea_query::{FromValueTuple, InsertStatement, IntoColumnRef, Returning, ValueTuple}; use std::{future::Future, marker::PhantomData}; @@ -50,6 +50,19 @@ where } Inserter::::new(self.primary_key, query).exec(db) } + + /// Execute an insert operation and return inserted row + pub fn exec_with_returning<'a, C>( + self, + db: &'a C, + ) -> impl Future> + '_ + where + ::Model: IntoActiveModel, + C: ConnectionTrait<'a>, + A: 'a, + { + Inserter::::new(self.primary_key, self.query).exec_with_returning(db) + } } impl Inserter @@ -74,6 +87,19 @@ where let builder = db.get_database_backend(); exec_insert(self.primary_key, builder.build(&self.query), db) } + + /// Execute an insert operation and return inserted row + pub fn exec_with_returning<'a, C>( + self, + db: &'a C, + ) -> impl Future> + '_ + where + ::Model: IntoActiveModel, + C: ConnectionTrait<'a>, + A: 'a, + { + exec_insert_with_returning(self.primary_key, self.query, db) + } } async fn exec_insert<'a, A, C>( @@ -89,7 +115,7 @@ where type ValueTypeOf = as PrimaryKeyTrait>::ValueType; let last_insert_id_opt = match db.get_database_backend() { DbBackend::Postgres => { - use crate::{sea_query::Iden, Iterable}; + use crate::sea_query::Iden; let cols = PrimaryKey::::iter() .map(|col| col.to_string()) .collect::>(); @@ -110,3 +136,41 @@ where }; Ok(InsertResult { last_insert_id }) } + +async fn exec_insert_with_returning<'a, A, C>( + primary_key: Option, + mut insert_statement: InsertStatement, + db: &'a C, +) -> Result +where + ::Model: IntoActiveModel, + C: ConnectionTrait<'a>, + A: ActiveModelTrait, +{ + let db_backend = db.get_database_backend(); + let found = match db_backend { + DbBackend::Postgres => { + insert_statement.returning(Returning::Columns( + ::Column::iter() + .map(|c| c.into_column_ref()) + .collect(), + )); + SelectorRaw::::Model>>::from_statement( + db_backend.build(&insert_statement), + ) + .one(db) + .await? + } + _ => { + let insert_res = + exec_insert::(primary_key, db_backend.build(&insert_statement), db).await?; + ::find_by_id(insert_res.last_insert_id) + .one(db) + .await? + } + }; + match found { + Some(model) => Ok(model.into_active_model()), + None => Err(DbErr::Exec("Failed to find inserted item".to_owned())), + } +} From 52ff9430e19db004f724c7d800976e20cfd8ae00 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 5 Nov 2021 18:25:20 +0800 Subject: [PATCH 03/31] Docs --- src/executor/insert.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 363dccd9c..f198826d0 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -51,7 +51,7 @@ where Inserter::::new(self.primary_key, query).exec(db) } - /// Execute an insert operation and return inserted row + /// Execute an insert operation and return the inserted model pub fn exec_with_returning<'a, C>( self, db: &'a C, @@ -88,7 +88,7 @@ where exec_insert(self.primary_key, builder.build(&self.query), db) } - /// Execute an insert operation and return inserted row + /// Execute an insert operation and return the inserted model pub fn exec_with_returning<'a, C>( self, db: &'a C, From a977572762634b36fa9fddfa132dd421d3bed980 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 5 Nov 2021 22:13:52 +0800 Subject: [PATCH 04/31] Update with returning for Postgres --- src/entity/active_model.rs | 1 + src/executor/update.rs | 44 ++++++++++++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index ea8d47a2b..945b956a1 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -156,6 +156,7 @@ pub trait ActiveModelTrait: Clone + Debug { /// Perform the `UPDATE` operation on an ActiveModel async fn update<'a, C>(self, db: &'a C) -> Result where + ::Model: IntoActiveModel, Self: ActiveModelBehavior + 'a, C: ConnectionTrait<'a>, { diff --git a/src/executor/update.rs b/src/executor/update.rs index 402f29acd..439bb8f9b 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -1,7 +1,8 @@ use crate::{ - error::*, ActiveModelTrait, ConnectionTrait, EntityTrait, Statement, UpdateMany, UpdateOne, + error::*, ActiveModelTrait, ConnectionTrait, DbBackend, EntityTrait, IntoActiveModel, Iterable, + SelectModel, SelectorRaw, Statement, UpdateMany, UpdateOne, }; -use sea_query::UpdateStatement; +use sea_query::{FromValueTuple, IntoColumnRef, Returning, UpdateStatement}; use std::future::Future; /// Defines an update operation @@ -25,10 +26,11 @@ where /// Execute an update operation on an ActiveModel pub async fn exec<'b, C>(self, db: &'b C) -> Result where + ::Model: IntoActiveModel, C: ConnectionTrait<'b>, { // so that self is dropped before entering await - exec_update_and_return_original(self.query, self.model, db).await + exec_update_and_return_updated(self.query, self.model, db).await } } @@ -78,17 +80,45 @@ where Updater::new(query).exec(db).await } -async fn exec_update_and_return_original<'a, A, C>( - query: UpdateStatement, +async fn exec_update_and_return_updated<'a, A, C>( + mut query: UpdateStatement, model: A, db: &'a C, ) -> Result where + ::Model: IntoActiveModel, A: ActiveModelTrait, C: ConnectionTrait<'a>, { - Updater::new(query).check_record_exists().exec(db).await?; - Ok(model) + let db_backend = db.get_database_backend(); + let found = match db_backend { + DbBackend::Postgres => { + query.returning(Returning::Columns( + ::Column::iter() + .map(|c| c.into_column_ref()) + .collect(), + )); + SelectorRaw::::Model>>::from_statement( + db_backend.build(&query), + ) + .one(db) + .await? + } + _ => { + Updater::new(query).check_record_exists().exec(db).await?; + let primary_key_value = match model.get_primary_key_value() { + Some(val) => FromValueTuple::from_value_tuple(val), + None => return Err(DbErr::Exec("Fail to get primary key from model".to_owned())), + }; + ::find_by_id(primary_key_value) + .one(db) + .await? + } + }; + match found { + Some(model) => Ok(model.into_active_model()), + None => Err(DbErr::Exec("Failed to find inserted item".to_owned())), + } } async fn exec_update<'a, C>( From 50605c731b840e8abda7061158953a4fdb79625a Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 5 Nov 2021 22:39:25 +0800 Subject: [PATCH 05/31] FIXME: breaking behaviors --- src/entity/base_entity.rs | 2 +- src/executor/update.rs | 108 +++++++++++++++++++++----------------- 2 files changed, 60 insertions(+), 50 deletions(-) diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index c03199d78..93d8ac8ae 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -413,7 +413,7 @@ 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 AND "fruit"."name" LIKE $3"#, + /// DbBackend::Postgres, r#"UPDATE "fruit" SET "name" = $1 WHERE "fruit"."id" = $2 AND "fruit"."name" LIKE $3 RETURNING "id", "name", "cake_id""#, /// vec!["Orange".into(), 1i32.into(), "%orange%".into()] /// )]); /// ``` diff --git a/src/executor/update.rs b/src/executor/update.rs index 439bb8f9b..b202ca374 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -149,6 +149,10 @@ mod tests { #[smol_potat::test] async fn update_record_not_found_1() -> Result<(), DbErr> { let db = MockDatabase::new(DbBackend::Postgres) + .append_query_results(vec![vec![cake::Model { + id: 1, + name: "Cheese Cake".to_owned(), + }]]) .append_exec_results(vec![ MockExecResult { last_insert_id: 0, @@ -197,41 +201,43 @@ mod tests { name: "New York Cheese".to_owned(), }; - assert_eq!( - cake::ActiveModel { - name: Set("Cheese Cake".to_owned()), - ..model.clone().into_active_model() - } - .update(&db) - .await, - Err(DbErr::RecordNotFound( - "None of the database rows are affected".to_owned() - )) - ); + // FIXME: Breaking! - assert_eq!( - cake::Entity::update(cake::ActiveModel { - name: Set("Cheese Cake".to_owned()), - ..model.clone().into_active_model() - }) - .exec(&db) - .await, - Err(DbErr::RecordNotFound( - "None of the database rows are affected".to_owned() - )) - ); + // assert_eq!( + // cake::ActiveModel { + // name: Set("Cheese Cake".to_owned()), + // ..model.clone().into_active_model() + // } + // .update(&db) + // .await, + // Err(DbErr::RecordNotFound( + // "None of the database rows are affected".to_owned() + // )) + // ); - assert_eq!( - Update::one(cake::ActiveModel { - name: Set("Cheese Cake".to_owned()), - ..model.into_active_model() - }) - .exec(&db) - .await, - Err(DbErr::RecordNotFound( - "None of the database rows are affected".to_owned() - )) - ); + // assert_eq!( + // cake::Entity::update(cake::ActiveModel { + // name: Set("Cheese Cake".to_owned()), + // ..model.clone().into_active_model() + // }) + // .exec(&db) + // .await, + // Err(DbErr::RecordNotFound( + // "None of the database rows are affected".to_owned() + // )) + // ); + + // assert_eq!( + // Update::one(cake::ActiveModel { + // name: Set("Cheese Cake".to_owned()), + // ..model.into_active_model() + // }) + // .exec(&db) + // .await, + // Err(DbErr::RecordNotFound( + // "None of the database rows are affected".to_owned() + // )) + // ); assert_eq!( Update::many(cake::Entity) @@ -247,24 +253,28 @@ mod tests { vec![ Transaction::from_sql_and_values( DbBackend::Postgres, - r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, + r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2 RETURNING "id", "name""#, vec!["Cheese Cake".into(), 1i32.into()] ), - Transaction::from_sql_and_values( - DbBackend::Postgres, - r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, - vec!["Cheese Cake".into(), 2i32.into()] - ), - Transaction::from_sql_and_values( - DbBackend::Postgres, - r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, - vec!["Cheese Cake".into(), 2i32.into()] - ), - Transaction::from_sql_and_values( - DbBackend::Postgres, - r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, - vec!["Cheese Cake".into(), 2i32.into()] - ), + // FIXME: Breaking! + + // Transaction::from_sql_and_values( + // DbBackend::Postgres, + // r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, + // vec!["Cheese Cake".into(), 2i32.into()] + // ), + + // Transaction::from_sql_and_values( + // DbBackend::Postgres, + // r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, + // vec!["Cheese Cake".into(), 2i32.into()] + // ), + + // Transaction::from_sql_and_values( + // DbBackend::Postgres, + // r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, + // vec!["Cheese Cake".into(), 2i32.into()] + // ), Transaction::from_sql_and_values( DbBackend::Postgres, r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, From 623873678b4e3599431e55700d172140fb4ccabe Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 8 Nov 2021 15:12:47 +0800 Subject: [PATCH 06/31] Handle "None of the database rows are affected" for Postgres --- src/executor/update.rs | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/executor/update.rs b/src/executor/update.rs index b202ca374..b7277ccd2 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -91,33 +91,43 @@ where C: ConnectionTrait<'a>, { let db_backend = db.get_database_backend(); - let found = match db_backend { + match db_backend { DbBackend::Postgres => { query.returning(Returning::Columns( ::Column::iter() .map(|c| c.into_column_ref()) .collect(), )); - SelectorRaw::::Model>>::from_statement( - db_backend.build(&query), - ) - .one(db) - .await? + let found: Option<::Model> = + SelectorRaw::::Model>>::from_statement( + db_backend.build(&query), + ) + .one(db) + .await?; + // If we got `None` then we are updating a row that does not exist. + match found { + Some(model) => Ok(model.into_active_model()), + None => Err(DbErr::RecordNotFound( + "None of the database rows are affected".to_owned(), + )), + } } _ => { + // If we updating a row that does not exist, error will be thrown here. Updater::new(query).check_record_exists().exec(db).await?; let primary_key_value = match model.get_primary_key_value() { Some(val) => FromValueTuple::from_value_tuple(val), None => return Err(DbErr::Exec("Fail to get primary key from model".to_owned())), }; - ::find_by_id(primary_key_value) + let found = ::find_by_id(primary_key_value) .one(db) - .await? + .await?; + // If we cannot select the updated row from db by the cached primary key + match found { + Some(model) => Ok(model.into_active_model()), + None => Err(DbErr::Exec("Failed to find inserted item".to_owned())), + } } - }; - match found { - Some(model) => Ok(model.into_active_model()), - None => Err(DbErr::Exec("Failed to find inserted item".to_owned())), } } From 2f7cffa74ddf833efdff5071cad0c2db8d14c478 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 8 Nov 2021 15:12:56 +0800 Subject: [PATCH 07/31] Fix test cases --- src/executor/update.rs | 115 ++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/src/executor/update.rs b/src/executor/update.rs index b7277ccd2..0cd42903c 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -159,10 +159,15 @@ mod tests { #[smol_potat::test] async fn update_record_not_found_1() -> Result<(), DbErr> { let db = MockDatabase::new(DbBackend::Postgres) - .append_query_results(vec![vec![cake::Model { - id: 1, - name: "Cheese Cake".to_owned(), - }]]) + .append_query_results(vec![ + vec![cake::Model { + id: 1, + name: "Cheese Cake".to_owned(), + }], + vec![], + vec![], + vec![], + ]) .append_exec_results(vec![ MockExecResult { last_insert_id: 0, @@ -211,43 +216,41 @@ mod tests { name: "New York Cheese".to_owned(), }; - // FIXME: Breaking! - - // assert_eq!( - // cake::ActiveModel { - // name: Set("Cheese Cake".to_owned()), - // ..model.clone().into_active_model() - // } - // .update(&db) - // .await, - // Err(DbErr::RecordNotFound( - // "None of the database rows are affected".to_owned() - // )) - // ); + assert_eq!( + cake::ActiveModel { + name: Set("Cheese Cake".to_owned()), + ..model.clone().into_active_model() + } + .update(&db) + .await, + Err(DbErr::RecordNotFound( + "None of the database rows are affected".to_owned() + )) + ); - // assert_eq!( - // cake::Entity::update(cake::ActiveModel { - // name: Set("Cheese Cake".to_owned()), - // ..model.clone().into_active_model() - // }) - // .exec(&db) - // .await, - // Err(DbErr::RecordNotFound( - // "None of the database rows are affected".to_owned() - // )) - // ); + assert_eq!( + cake::Entity::update(cake::ActiveModel { + name: Set("Cheese Cake".to_owned()), + ..model.clone().into_active_model() + }) + .exec(&db) + .await, + Err(DbErr::RecordNotFound( + "None of the database rows are affected".to_owned() + )) + ); - // assert_eq!( - // Update::one(cake::ActiveModel { - // name: Set("Cheese Cake".to_owned()), - // ..model.into_active_model() - // }) - // .exec(&db) - // .await, - // Err(DbErr::RecordNotFound( - // "None of the database rows are affected".to_owned() - // )) - // ); + assert_eq!( + Update::one(cake::ActiveModel { + name: Set("Cheese Cake".to_owned()), + ..model.into_active_model() + }) + .exec(&db) + .await, + Err(DbErr::RecordNotFound( + "None of the database rows are affected".to_owned() + )) + ); assert_eq!( Update::many(cake::Entity) @@ -266,25 +269,21 @@ mod tests { r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2 RETURNING "id", "name""#, vec!["Cheese Cake".into(), 1i32.into()] ), - // FIXME: Breaking! - - // Transaction::from_sql_and_values( - // DbBackend::Postgres, - // r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, - // vec!["Cheese Cake".into(), 2i32.into()] - // ), - - // Transaction::from_sql_and_values( - // DbBackend::Postgres, - // r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, - // vec!["Cheese Cake".into(), 2i32.into()] - // ), - - // Transaction::from_sql_and_values( - // DbBackend::Postgres, - // r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, - // vec!["Cheese Cake".into(), 2i32.into()] - // ), + Transaction::from_sql_and_values( + DbBackend::Postgres, + r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2 RETURNING "id", "name""#, + vec!["Cheese Cake".into(), 2i32.into()] + ), + Transaction::from_sql_and_values( + DbBackend::Postgres, + r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2 RETURNING "id", "name""#, + vec!["Cheese Cake".into(), 2i32.into()] + ), + Transaction::from_sql_and_values( + DbBackend::Postgres, + r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2 RETURNING "id", "name""#, + vec!["Cheese Cake".into(), 2i32.into()] + ), Transaction::from_sql_and_values( DbBackend::Postgres, r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, From 732d08002062cb5f17cb039ac3288ef077654ea2 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 8 Nov 2021 15:24:51 +0800 Subject: [PATCH 08/31] Update docs --- src/executor/insert.rs | 7 +++---- src/executor/update.rs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/executor/insert.rs b/src/executor/insert.rs index f198826d0..6117e782b 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -2,7 +2,7 @@ use crate::{ error::*, ActiveModelTrait, ConnectionTrait, DbBackend, EntityTrait, Insert, IntoActiveModel, Iterable, PrimaryKeyTrait, SelectModel, SelectorRaw, Statement, TryFromU64, }; -use sea_query::{FromValueTuple, InsertStatement, IntoColumnRef, Returning, ValueTuple}; +use sea_query::{FromValueTuple, Iden, InsertStatement, IntoColumnRef, Returning, ValueTuple}; use std::{future::Future, marker::PhantomData}; /// Defines a structure to perform INSERT operations in an ActiveModel @@ -51,7 +51,7 @@ where Inserter::::new(self.primary_key, query).exec(db) } - /// Execute an insert operation and return the inserted model + /// Execute an insert operation and return the inserted model (use `RETURNING` syntax if database supported) pub fn exec_with_returning<'a, C>( self, db: &'a C, @@ -88,7 +88,7 @@ where exec_insert(self.primary_key, builder.build(&self.query), db) } - /// Execute an insert operation and return the inserted model + /// Execute an insert operation and return the inserted model (use `RETURNING` syntax if database supported) pub fn exec_with_returning<'a, C>( self, db: &'a C, @@ -115,7 +115,6 @@ where type ValueTypeOf = as PrimaryKeyTrait>::ValueType; let last_insert_id_opt = match db.get_database_backend() { DbBackend::Postgres => { - use crate::sea_query::Iden; let cols = PrimaryKey::::iter() .map(|col| col.to_string()) .collect::>(); diff --git a/src/executor/update.rs b/src/executor/update.rs index 0cd42903c..c16d4644c 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -113,7 +113,7 @@ where } } _ => { - // If we updating a row that does not exist, error will be thrown here. + // If we updating a row that does not exist then an error will be thrown here. Updater::new(query).check_record_exists().exec(db).await?; let primary_key_value = match model.get_primary_key_value() { Some(val) => FromValueTuple::from_value_tuple(val), From 0eafacc2a1bc34f0499c0b8a020fbe41504bb97d Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 8 Nov 2021 17:36:30 +0800 Subject: [PATCH 09/31] Try returning on MariaDB --- Cargo.toml | 1 + src/database/connection.rs | 3 +++ src/database/db_connection.rs | 45 ++++++++++++++++++++++++------- src/database/transaction.rs | 8 ++++++ src/driver/sqlx_mysql.rs | 51 ++++++++++++++++++++++++++++++----- src/executor/insert.rs | 20 +++++++------- src/executor/update.rs | 10 +++---- 7 files changed, 107 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index db5abc698..32589dea3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ sqlx = { version = "^0.5", optional = true } uuid = { version = "0.8", features = ["serde", "v4"], optional = true } ouroboros = "0.11" url = "^2.2" +regex = "^1" [dev-dependencies] smol = { version = "^1.2" } diff --git a/src/database/connection.rs b/src/database/connection.rs index be47f9da8..e06c6e576 100644 --- a/src/database/connection.rs +++ b/src/database/connection.rs @@ -45,6 +45,9 @@ pub trait ConnectionTrait<'a>: Sync { T: Send, E: std::error::Error + Send; + /// Check if the connection supports `RETURNING` syntax + fn support_returning(&self) -> bool; + /// Check if the connection is a test connection for the Mock database fn is_mock_connection(&self) -> bool { false diff --git a/src/database/db_connection.rs b/src/database/db_connection.rs index 681903ddd..9038dc20a 100644 --- a/src/database/db_connection.rs +++ b/src/database/db_connection.rs @@ -18,7 +18,12 @@ use std::sync::Arc; pub enum DatabaseConnection { /// Create a MYSQL database connection and pool #[cfg(feature = "sqlx-mysql")] - SqlxMySqlPoolConnection(crate::SqlxMySqlPoolConnection), + SqlxMySqlPoolConnection { + /// A SQLx MySQL pool + conn: crate::SqlxMySqlPoolConnection, + /// A flag indicating whether `RETURNING` syntax is supported + support_returning: bool, + }, /// Create a PostgreSQL database connection and pool #[cfg(feature = "sqlx-postgres")] SqlxPostgresPoolConnection(crate::SqlxPostgresPoolConnection), @@ -73,7 +78,7 @@ impl std::fmt::Debug for DatabaseConnection { "{}", match self { #[cfg(feature = "sqlx-mysql")] - Self::SqlxMySqlPoolConnection(_) => "SqlxMySqlPoolConnection", + Self::SqlxMySqlPoolConnection { .. } => "SqlxMySqlPoolConnection", #[cfg(feature = "sqlx-postgres")] Self::SqlxPostgresPoolConnection(_) => "SqlxPostgresPoolConnection", #[cfg(feature = "sqlx-sqlite")] @@ -93,7 +98,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { fn get_database_backend(&self) -> DbBackend { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection(_) => DbBackend::MySql, + DatabaseConnection::SqlxMySqlPoolConnection { .. } => DbBackend::MySql, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(_) => DbBackend::Postgres, #[cfg(feature = "sqlx-sqlite")] @@ -107,7 +112,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { async fn execute(&self, stmt: Statement) -> Result { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.execute(stmt).await, + DatabaseConnection::SqlxMySqlPoolConnection { conn, .. } => conn.execute(stmt).await, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => conn.execute(stmt).await, #[cfg(feature = "sqlx-sqlite")] @@ -121,7 +126,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { async fn query_one(&self, stmt: Statement) -> Result, DbErr> { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_one(stmt).await, + DatabaseConnection::SqlxMySqlPoolConnection { conn, .. } => conn.query_one(stmt).await, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => conn.query_one(stmt).await, #[cfg(feature = "sqlx-sqlite")] @@ -135,7 +140,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { async fn query_all(&self, stmt: Statement) -> Result, DbErr> { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_all(stmt).await, + DatabaseConnection::SqlxMySqlPoolConnection { conn, .. } => conn.query_all(stmt).await, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => conn.query_all(stmt).await, #[cfg(feature = "sqlx-sqlite")] @@ -153,7 +158,9 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { Box::pin(async move { Ok(match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.stream(stmt).await?, + DatabaseConnection::SqlxMySqlPoolConnection { conn, .. } => { + conn.stream(stmt).await? + } #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => conn.stream(stmt).await?, #[cfg(feature = "sqlx-sqlite")] @@ -170,7 +177,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { async fn begin(&self) -> Result { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.begin().await, + DatabaseConnection::SqlxMySqlPoolConnection { conn, .. } => conn.begin().await, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => conn.begin().await, #[cfg(feature = "sqlx-sqlite")] @@ -196,7 +203,9 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.transaction(_callback).await, + DatabaseConnection::SqlxMySqlPoolConnection { conn, .. } => { + conn.transaction(_callback).await + } #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => { conn.transaction(_callback).await @@ -214,6 +223,24 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { } } + fn support_returning(&self) -> bool { + match self { + #[cfg(feature = "sqlx-mysql")] + DatabaseConnection::SqlxMySqlPoolConnection { .. } => false, + #[cfg(feature = "sqlx-postgres")] + DatabaseConnection::SqlxPostgresPoolConnection(_) => true, + #[cfg(feature = "sqlx-sqlite")] + DatabaseConnection::SqlxSqlitePoolConnection(_) => false, + #[cfg(feature = "mock")] + DatabaseConnection::MockDatabaseConnection(conn) => match conn.get_database_backend() { + DbBackend::MySql => false, + DbBackend::Postgres => true, + DbBackend::Sqlite => false, + }, + DatabaseConnection::Disconnected => panic!("Disconnected"), + } + } + #[cfg(feature = "mock")] fn is_mock_connection(&self) -> bool { matches!(self, DatabaseConnection::MockDatabaseConnection(_)) diff --git a/src/database/transaction.rs b/src/database/transaction.rs index f4a1b6787..77394acd6 100644 --- a/src/database/transaction.rs +++ b/src/database/transaction.rs @@ -347,6 +347,14 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction { let transaction = self.begin().await.map_err(TransactionError::Connection)?; transaction.run(_callback).await } + + fn support_returning(&self) -> bool { + match self.backend { + DbBackend::MySql => false, + DbBackend::Postgres => true, + DbBackend::Sqlite => false, + } + } } /// Defines errors for handling transaction failures diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs index b2b89c680..b8803edbe 100644 --- a/src/driver/sqlx_mysql.rs +++ b/src/driver/sqlx_mysql.rs @@ -1,3 +1,4 @@ +use regex::Regex; use std::{future::Future, pin::Pin}; use sqlx::{ @@ -10,7 +11,7 @@ use sea_query_driver_mysql::bind_query; use crate::{ debug_print, error::*, executor::*, ConnectOptions, DatabaseConnection, DatabaseTransaction, - QueryStream, Statement, TransactionError, + DbBackend, QueryStream, Statement, TransactionError, }; use super::sqlx_common::*; @@ -42,9 +43,7 @@ impl SqlxMySqlConnector { opt.disable_statement_logging(); } if let Ok(pool) = options.pool_options().connect_with(opt).await { - Ok(DatabaseConnection::SqlxMySqlPoolConnection( - SqlxMySqlPoolConnection { pool }, - )) + into_db_connection(pool).await } else { Err(DbErr::Conn("Failed to connect.".to_owned())) } @@ -53,8 +52,8 @@ impl SqlxMySqlConnector { impl SqlxMySqlConnector { /// Instantiate a sqlx pool connection to a [DatabaseConnection] - pub fn from_sqlx_mysql_pool(pool: MySqlPool) -> DatabaseConnection { - DatabaseConnection::SqlxMySqlPoolConnection(SqlxMySqlPoolConnection { pool }) + pub async fn from_sqlx_mysql_pool(pool: MySqlPool) -> Result { + into_db_connection(pool).await } } @@ -183,3 +182,43 @@ pub(crate) fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, MySql, MySq } query } + +async fn into_db_connection(pool: MySqlPool) -> Result { + let conn = SqlxMySqlPoolConnection { pool }; + let res = conn + .query_one(Statement::from_string( + DbBackend::MySql, + r#"SHOW VARIABLES LIKE "version""#.to_owned(), + )) + .await?; + let support_returning = if let Some(query_result) = res { + let version: String = query_result.try_get("", "Value")?; + if !version.contains("MariaDB") { + // This is MySQL + false + } else { + // This is MariaDB + let regex = Regex::new(r"^(\d+)?.(\d+)?.(\*|\d+)").unwrap(); + let captures = regex.captures(&version).unwrap(); + macro_rules! parse_captures { + ( $idx: expr ) => { + captures.get($idx).map_or(0, |m| { + m.as_str() + .parse::() + .map_err(|e| DbErr::Conn(e.to_string())) + .unwrap() + }) + }; + } + let ver_major = parse_captures!(1); + let ver_minor = parse_captures!(2); + ver_major >= 10 && ver_minor >= 5 + } + } else { + return Err(DbErr::Conn("Fail to parse MySQL version".to_owned())); + }; + Ok(DatabaseConnection::SqlxMySqlPoolConnection { + conn, + support_returning, + }) +} diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 6117e782b..9f3713736 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,6 +1,6 @@ use crate::{ - error::*, ActiveModelTrait, ConnectionTrait, DbBackend, EntityTrait, Insert, IntoActiveModel, - Iterable, PrimaryKeyTrait, SelectModel, SelectorRaw, Statement, TryFromU64, + error::*, ActiveModelTrait, ConnectionTrait, EntityTrait, Insert, IntoActiveModel, Iterable, + PrimaryKeyTrait, SelectModel, SelectorRaw, Statement, TryFromU64, }; use sea_query::{FromValueTuple, Iden, InsertStatement, IntoColumnRef, Returning, ValueTuple}; use std::{future::Future, marker::PhantomData}; @@ -39,9 +39,7 @@ where { // so that self is dropped before entering await let mut query = self.query; - if db.get_database_backend() == DbBackend::Postgres - && ::PrimaryKey::iter().count() > 0 - { + if db.support_returning() && ::PrimaryKey::iter().count() > 0 { query.returning(Returning::Columns( ::PrimaryKey::iter() .map(|c| c.into_column_ref()) @@ -113,15 +111,15 @@ where { type PrimaryKey = <::Entity as EntityTrait>::PrimaryKey; type ValueTypeOf = as PrimaryKeyTrait>::ValueType; - let last_insert_id_opt = match db.get_database_backend() { - DbBackend::Postgres => { + let last_insert_id_opt = match db.support_returning() { + true => { let cols = PrimaryKey::::iter() .map(|col| col.to_string()) .collect::>(); let res = db.query_one(statement).await?.unwrap(); res.try_get_many("", cols.as_ref()).ok() } - _ => { + false => { let last_insert_id = db.execute(statement).await?.last_insert_id(); ValueTypeOf::::try_from_u64(last_insert_id).ok() } @@ -147,8 +145,8 @@ where A: ActiveModelTrait, { let db_backend = db.get_database_backend(); - let found = match db_backend { - DbBackend::Postgres => { + let found = match db.support_returning() { + true => { insert_statement.returning(Returning::Columns( ::Column::iter() .map(|c| c.into_column_ref()) @@ -160,7 +158,7 @@ where .one(db) .await? } - _ => { + false => { let insert_res = exec_insert::(primary_key, db_backend.build(&insert_statement), db).await?; ::find_by_id(insert_res.last_insert_id) diff --git a/src/executor/update.rs b/src/executor/update.rs index c16d4644c..f83e8efb6 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -1,5 +1,5 @@ use crate::{ - error::*, ActiveModelTrait, ConnectionTrait, DbBackend, EntityTrait, IntoActiveModel, Iterable, + error::*, ActiveModelTrait, ConnectionTrait, EntityTrait, IntoActiveModel, Iterable, SelectModel, SelectorRaw, Statement, UpdateMany, UpdateOne, }; use sea_query::{FromValueTuple, IntoColumnRef, Returning, UpdateStatement}; @@ -90,14 +90,14 @@ where A: ActiveModelTrait, C: ConnectionTrait<'a>, { - let db_backend = db.get_database_backend(); - match db_backend { - DbBackend::Postgres => { + match db.support_returning() { + true => { query.returning(Returning::Columns( ::Column::iter() .map(|c| c.into_column_ref()) .collect(), )); + let db_backend = db.get_database_backend(); let found: Option<::Model> = SelectorRaw::::Model>>::from_statement( db_backend.build(&query), @@ -112,7 +112,7 @@ where )), } } - _ => { + false => { // If we updating a row that does not exist then an error will be thrown here. Updater::new(query).check_record_exists().exec(db).await?; let primary_key_value = match model.get_primary_key_value() { From 30f43b64c6151dfc5423ed8842db9138071b521d Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 8 Nov 2021 19:03:06 +0800 Subject: [PATCH 10/31] Fixup --- .github/workflows/rust.yml | 6 ++++- Cargo.toml | 2 +- src/database/db_connection.rs | 42 ++++++++++++++++++++++++++++++++--- src/database/transaction.rs | 6 +---- src/driver/sqlx_mysql.rs | 8 ++++--- src/executor/insert.rs | 33 ++++++++++++++++----------- src/executor/update.rs | 22 +++++++++++------- tests/returning_tests.rs | 37 ++++++++++++++++++++++++++++++ 8 files changed, 122 insertions(+), 34 deletions(-) create mode 100644 tests/returning_tests.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ad18203e4..0734f3acf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -350,6 +350,7 @@ jobs: env: DATABASE_URL: "sqlite::memory:" strategy: + fail-fast: false matrix: runtime: [async-std, actix, tokio] tls: [native-tls, rustls] @@ -392,6 +393,7 @@ jobs: env: DATABASE_URL: "mysql://root:@localhost" strategy: + fail-fast: false matrix: version: [8.0, 5.7] runtime: [async-std, actix, tokio] @@ -452,8 +454,9 @@ jobs: env: DATABASE_URL: "mysql://root:@localhost" strategy: + fail-fast: false matrix: - version: [10.6] + version: [10.7, 10.6, 10.5, 10.0, 5.5] runtime: [async-std, actix, tokio] tls: [native-tls] services: @@ -512,6 +515,7 @@ jobs: env: DATABASE_URL: "postgres://root:root@localhost" strategy: + fail-fast: false matrix: version: [13.3, 12.7, 11.12, 10.17, 9.6.22] runtime: [tokio] diff --git a/Cargo.toml b/Cargo.toml index 32589dea3..46d8c617b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ futures-util = { version = "^0.3" } log = { version = "^0.4", optional = true } rust_decimal = { version = "^1", optional = true } sea-orm-macros = { version = "^0.3.1", path = "sea-orm-macros", optional = true } -sea-query = { version = "^0.18.0", git = "https://github.com/marlon-sousa/sea-query.git", branch = "extended-returning-support", features = ["thread-safe"] } +sea-query = { version = "^0.18.2", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1", optional = true } diff --git a/src/database/db_connection.rs b/src/database/db_connection.rs index 9038dc20a..59aaf5f2d 100644 --- a/src/database/db_connection.rs +++ b/src/database/db_connection.rs @@ -19,9 +19,11 @@ pub enum DatabaseConnection { /// Create a MYSQL database connection and pool #[cfg(feature = "sqlx-mysql")] SqlxMySqlPoolConnection { - /// A SQLx MySQL pool + /// The SQLx MySQL pool conn: crate::SqlxMySqlPoolConnection, - /// A flag indicating whether `RETURNING` syntax is supported + /// The MySQL version + version: String, + /// The flag indicating whether `RETURNING` syntax is supported support_returning: bool, }, /// Create a PostgreSQL database connection and pool @@ -226,7 +228,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { fn support_returning(&self) -> bool { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { .. } => false, + DatabaseConnection::SqlxMySqlPoolConnection { support_returning, .. } => *support_returning, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(_) => true, #[cfg(feature = "sqlx-sqlite")] @@ -264,6 +266,40 @@ impl DatabaseConnection { } } +impl DatabaseConnection { + /// Get database version + pub fn db_version(&self) -> String { + match self { + #[cfg(feature = "sqlx-mysql")] + DatabaseConnection::SqlxMySqlPoolConnection { version, .. } => version.to_string(), + // #[cfg(feature = "sqlx-postgres")] + // DatabaseConnection::SqlxPostgresPoolConnection(conn) => , + // #[cfg(feature = "sqlx-sqlite")] + // DatabaseConnection::SqlxSqlitePoolConnection(conn) => , + // #[cfg(feature = "mock")] + // DatabaseConnection::MockDatabaseConnection(conn) => , + DatabaseConnection::Disconnected => panic!("Disconnected"), + _ => unimplemented!(), + } + } + + /// Check if database supports `RETURNING` + pub fn db_support_returning(&self) -> bool { + match self { + #[cfg(feature = "sqlx-mysql")] + DatabaseConnection::SqlxMySqlPoolConnection { support_returning, .. } => *support_returning, + #[cfg(feature = "sqlx-postgres")] + DatabaseConnection::SqlxPostgresPoolConnection(_) => true, + // #[cfg(feature = "sqlx-sqlite")] + // DatabaseConnection::SqlxSqlitePoolConnection(conn) => , + // #[cfg(feature = "mock")] + // DatabaseConnection::MockDatabaseConnection(conn) => , + DatabaseConnection::Disconnected => panic!("Disconnected"), + _ => unimplemented!(), + } + } +} + impl DbBackend { /// Check if the URI is the same as the specified database backend. /// Returns true if they match. diff --git a/src/database/transaction.rs b/src/database/transaction.rs index 77394acd6..474f5ede2 100644 --- a/src/database/transaction.rs +++ b/src/database/transaction.rs @@ -349,11 +349,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction { } fn support_returning(&self) -> bool { - match self.backend { - DbBackend::MySql => false, - DbBackend::Postgres => true, - DbBackend::Sqlite => false, - } + panic!("FIXME: How?") } } diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs index b8803edbe..b66e1078f 100644 --- a/src/driver/sqlx_mysql.rs +++ b/src/driver/sqlx_mysql.rs @@ -191,9 +191,9 @@ async fn into_db_connection(pool: MySqlPool) -> Result Result= 10 && ver_minor >= 5 - } + }; + (version, support_returning) } else { return Err(DbErr::Conn("Fail to parse MySQL version".to_owned())); }; Ok(DatabaseConnection::SqlxMySqlPoolConnection { conn, + version, support_returning, }) } diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 9f3713736..b5c2cf982 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,8 +1,10 @@ use crate::{ - error::*, ActiveModelTrait, ConnectionTrait, EntityTrait, Insert, IntoActiveModel, Iterable, - PrimaryKeyTrait, SelectModel, SelectorRaw, Statement, TryFromU64, + error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, Insert, IntoActiveModel, + Iterable, PrimaryKeyTrait, SelectModel, SelectorRaw, Statement, TryFromU64, +}; +use sea_query::{ + Alias, Expr, FromValueTuple, Iden, InsertStatement, IntoColumnRef, Query, ValueTuple, }; -use sea_query::{FromValueTuple, Iden, InsertStatement, IntoColumnRef, Returning, ValueTuple}; use std::{future::Future, marker::PhantomData}; /// Defines a structure to perform INSERT operations in an ActiveModel @@ -40,11 +42,10 @@ where // so that self is dropped before entering await let mut query = self.query; if db.support_returning() && ::PrimaryKey::iter().count() > 0 { - query.returning(Returning::Columns( - ::PrimaryKey::iter() - .map(|c| c.into_column_ref()) - .collect(), - )); + let mut returning = Query::select(); + returning + .columns(::PrimaryKey::iter().map(|c| c.into_column_ref())); + query.returning(returning); } Inserter::::new(self.primary_key, query).exec(db) } @@ -147,11 +148,17 @@ where let db_backend = db.get_database_backend(); let found = match db.support_returning() { true => { - insert_statement.returning(Returning::Columns( - ::Column::iter() - .map(|c| c.into_column_ref()) - .collect(), - )); + let mut returning = Query::select(); + returning.exprs(::Column::iter().map(|c| { + let col = Expr::col(c); + let col_def = ColumnTrait::def(&c); + let col_type = col_def.get_column_type(); + match col_type.get_enum_name() { + Some(_) => col.as_enum(Alias::new("text")), + None => col.into(), + } + })); + insert_statement.returning(returning); SelectorRaw::::Model>>::from_statement( db_backend.build(&insert_statement), ) diff --git a/src/executor/update.rs b/src/executor/update.rs index f83e8efb6..9870b10d9 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -1,8 +1,8 @@ use crate::{ - error::*, ActiveModelTrait, ConnectionTrait, EntityTrait, IntoActiveModel, Iterable, - SelectModel, SelectorRaw, Statement, UpdateMany, UpdateOne, + error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, IntoActiveModel, + Iterable, SelectModel, SelectorRaw, Statement, UpdateMany, UpdateOne, }; -use sea_query::{FromValueTuple, IntoColumnRef, Returning, UpdateStatement}; +use sea_query::{Alias, Expr, FromValueTuple, Query, UpdateStatement}; use std::future::Future; /// Defines an update operation @@ -92,11 +92,17 @@ where { match db.support_returning() { true => { - query.returning(Returning::Columns( - ::Column::iter() - .map(|c| c.into_column_ref()) - .collect(), - )); + let mut returning = Query::select(); + returning.exprs(::Column::iter().map(|c| { + let col = Expr::col(c); + let col_def = c.def(); + let col_type = col_def.get_column_type(); + match col_type.get_enum_name() { + Some(_) => col.as_enum(Alias::new("text")), + None => col.into(), + } + })); + query.returning(returning); let db_backend = db.get_database_backend(); let found: Option<::Model> = SelectorRaw::::Model>>::from_statement( diff --git a/tests/returning_tests.rs b/tests/returning_tests.rs new file mode 100644 index 000000000..0a1e02c9a --- /dev/null +++ b/tests/returning_tests.rs @@ -0,0 +1,37 @@ +pub mod common; + +pub use common::{features::*, setup::*, TestContext}; +use sea_orm::{entity::prelude::*, entity::*, DatabaseConnection}; + +#[sea_orm_macros::test] +#[cfg(any( + feature = "sqlx-mysql", + feature = "sqlx-sqlite", + feature = "sqlx-postgres" +))] +async fn main() -> Result<(), DbErr> { + let ctx = TestContext::new("returning_tests").await; + let db = &ctx.db; + + match db { + #[cfg(feature = "sqlx-mysql")] + DatabaseConnection::SqlxMySqlPoolConnection { .. } => { + let version = db.db_version(); + match version.as_str() { + "5.7.26" => assert!(!db.db_support_returning()), + _ => unimplemented!("Version {} is not included", version), + }; + }, + #[cfg(feature = "sqlx-postgres")] + DatabaseConnection::SqlxPostgresPoolConnection(_) => { + assert!(db.db_support_returning()); + }, + #[cfg(feature = "sqlx-sqlite")] + DatabaseConnection::SqlxSqlitePoolConnection(_) => {}, + _ => unreachable!(), + } + + ctx.delete().await; + + Ok(()) +} From 2f0ac4ca1d8da7cf89a8f1fe30992eb7cef4fe3d Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 8 Nov 2021 20:11:44 +0800 Subject: [PATCH 11/31] Fixup --- .github/workflows/rust.yml | 4 ++-- src/database/transaction.rs | 3 ++- src/driver/sqlx_mysql.rs | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0734f3acf..32e96980c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -395,7 +395,7 @@ jobs: strategy: fail-fast: false matrix: - version: [8.0, 5.7] + version: [8.0.27, 5.7.36] runtime: [async-std, actix, tokio] tls: [native-tls] services: @@ -456,7 +456,7 @@ jobs: strategy: fail-fast: false matrix: - version: [10.7, 10.6, 10.5, 10.0, 5.5] + version: [10.6, 10.5, 10.0, 5.5] runtime: [async-std, actix, tokio] tls: [native-tls] services: diff --git a/src/database/transaction.rs b/src/database/transaction.rs index 474f5ede2..5865311f5 100644 --- a/src/database/transaction.rs +++ b/src/database/transaction.rs @@ -349,7 +349,8 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction { } fn support_returning(&self) -> bool { - panic!("FIXME: How?") + // FIXME: How? + false } } diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs index b66e1078f..dae6b0d36 100644 --- a/src/driver/sqlx_mysql.rs +++ b/src/driver/sqlx_mysql.rs @@ -221,6 +221,6 @@ async fn into_db_connection(pool: MySqlPool) -> Result Date: Mon, 8 Nov 2021 22:12:09 +0800 Subject: [PATCH 12/31] This will fail loll --- .github/workflows/rust.yml | 4 +-- Cargo.toml | 2 +- src/database/db_connection.rs | 26 +++++-------------- src/executor/insert.rs | 5 ++-- tests/returning_tests.rs | 48 ++++++++++++++++++++++------------- 5 files changed, 43 insertions(+), 42 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 32e96980c..35ef5cc11 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -395,7 +395,7 @@ jobs: strategy: fail-fast: false matrix: - version: [8.0.27, 5.7.36] + version: [8.0, 5.7] runtime: [async-std, actix, tokio] tls: [native-tls] services: @@ -456,7 +456,7 @@ jobs: strategy: fail-fast: false matrix: - version: [10.6, 10.5, 10.0, 5.5] + version: [10.6, 10.5, 10.4] runtime: [async-std, actix, tokio] tls: [native-tls] services: diff --git a/Cargo.toml b/Cargo.toml index 46d8c617b..3f0e0ea31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ futures-util = { version = "^0.3" } log = { version = "^0.4", optional = true } rust_decimal = { version = "^1", optional = true } sea-orm-macros = { version = "^0.3.1", path = "sea-orm-macros", optional = true } -sea-query = { version = "^0.18.2", features = ["thread-safe"] } +sea-query = { version = "^0.18.2", git = "https://github.com/SeaQL/sea-query.git", branch = "sea-orm/returning", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1", optional = true } diff --git a/src/database/db_connection.rs b/src/database/db_connection.rs index 59aaf5f2d..663858765 100644 --- a/src/database/db_connection.rs +++ b/src/database/db_connection.rs @@ -228,7 +228,9 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { fn support_returning(&self) -> bool { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { support_returning, .. } => *support_returning, + DatabaseConnection::SqlxMySqlPoolConnection { + support_returning, .. + } => *support_returning, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(_) => true, #[cfg(feature = "sqlx-sqlite")] @@ -267,27 +269,13 @@ impl DatabaseConnection { } impl DatabaseConnection { - /// Get database version - pub fn db_version(&self) -> String { - match self { - #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { version, .. } => version.to_string(), - // #[cfg(feature = "sqlx-postgres")] - // DatabaseConnection::SqlxPostgresPoolConnection(conn) => , - // #[cfg(feature = "sqlx-sqlite")] - // DatabaseConnection::SqlxSqlitePoolConnection(conn) => , - // #[cfg(feature = "mock")] - // DatabaseConnection::MockDatabaseConnection(conn) => , - DatabaseConnection::Disconnected => panic!("Disconnected"), - _ => unimplemented!(), - } - } - /// Check if database supports `RETURNING` - pub fn db_support_returning(&self) -> bool { + pub fn support_returning(&self) -> bool { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { support_returning, .. } => *support_returning, + DatabaseConnection::SqlxMySqlPoolConnection { + support_returning, .. + } => *support_returning, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(_) => true, // #[cfg(feature = "sqlx-sqlite")] diff --git a/src/executor/insert.rs b/src/executor/insert.rs index b5c2cf982..a6dbcbd53 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -43,8 +43,9 @@ where let mut query = self.query; if db.support_returning() && ::PrimaryKey::iter().count() > 0 { let mut returning = Query::select(); - returning - .columns(::PrimaryKey::iter().map(|c| c.into_column_ref())); + returning.columns( + ::PrimaryKey::iter().map(|c| c.into_column_ref()), + ); query.returning(returning); } Inserter::::new(self.primary_key, query).exec(db) diff --git a/tests/returning_tests.rs b/tests/returning_tests.rs index 0a1e02c9a..df8fc1a99 100644 --- a/tests/returning_tests.rs +++ b/tests/returning_tests.rs @@ -1,7 +1,8 @@ pub mod common; -pub use common::{features::*, setup::*, TestContext}; -use sea_orm::{entity::prelude::*, entity::*, DatabaseConnection}; +pub use common::{bakery_chain::*, setup::*, TestContext}; +use sea_orm::{entity::prelude::*, *}; +use sea_query::Query; #[sea_orm_macros::test] #[cfg(any( @@ -10,27 +11,38 @@ use sea_orm::{entity::prelude::*, entity::*, DatabaseConnection}; feature = "sqlx-postgres" ))] async fn main() -> Result<(), DbErr> { + use bakery::*; + let ctx = TestContext::new("returning_tests").await; let db = &ctx.db; + let builder = db.get_database_backend(); + + let mut insert = Query::insert(); + insert + .into_table(Entity) + .columns(vec![Column::Name, Column::ProfitMargin]) + .values_panic(vec!["Bakery Shop".into(), 0.5.into()]); + + let mut update = Query::update(); + update + .table(Entity) + .values(vec![ + (Column::Name, "Bakery Shop".into()), + (Column::ProfitMargin, 0.5.into()), + ]) + .and_where(Column::Id.eq(1)); - match db { - #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { .. } => { - let version = db.db_version(); - match version.as_str() { - "5.7.26" => assert!(!db.db_support_returning()), - _ => unimplemented!("Version {} is not included", version), - }; - }, - #[cfg(feature = "sqlx-postgres")] - DatabaseConnection::SqlxPostgresPoolConnection(_) => { - assert!(db.db_support_returning()); - }, - #[cfg(feature = "sqlx-sqlite")] - DatabaseConnection::SqlxSqlitePoolConnection(_) => {}, - _ => unreachable!(), + if db.support_returning() { + let mut returning = Query::select(); + returning.columns(vec![Column::Id, Column::Name, Column::ProfitMargin]); + insert.returning(returning.clone()); + update.returning(returning); } + create_tables(db).await?; + db.query_one(builder.build(&insert)).await?; + db.query_one(builder.build(&update)).await?; + assert!(false); ctx.delete().await; Ok(()) From 17232063b3dd9507d26528075a3d28b58f99cd35 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 8 Nov 2021 22:29:18 +0800 Subject: [PATCH 13/31] This will fail loll --- .github/workflows/rust.yml | 2 +- src/database/db_connection.rs | 16 ++++++++++++++++ src/driver/sqlx_mysql.rs | 2 +- tests/returning_tests.rs | 1 + 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 35ef5cc11..c4d112352 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -517,7 +517,7 @@ jobs: strategy: fail-fast: false matrix: - version: [13.3, 12.7, 11.12, 10.17, 9.6.22] + version: [13, 12, 11, 10, 9] runtime: [tokio] tls: [native-tls] services: diff --git a/src/database/db_connection.rs b/src/database/db_connection.rs index 663858765..d428e11e7 100644 --- a/src/database/db_connection.rs +++ b/src/database/db_connection.rs @@ -269,6 +269,22 @@ impl DatabaseConnection { } impl DatabaseConnection { + /// Get database version + pub fn version(&self) -> String { + match self { + #[cfg(feature = "sqlx-mysql")] + DatabaseConnection::SqlxMySqlPoolConnection { version, .. } => version.to_string(), + #[cfg(feature = "sqlx-postgres")] + DatabaseConnection::SqlxPostgresPoolConnection(_) => "".to_string(), + #[cfg(feature = "sqlx-sqlite")] + DatabaseConnection::SqlxSqlitePoolConnection(_) => "".to_string(), + #[cfg(feature = "mock")] + DatabaseConnection::MockDatabaseConnection(_) => "".to_string(), + DatabaseConnection::Disconnected => panic!("Disconnected"), + _ => unimplemented!(), + } + } + /// Check if database supports `RETURNING` pub fn support_returning(&self) -> bool { match self { diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs index dae6b0d36..b66e1078f 100644 --- a/src/driver/sqlx_mysql.rs +++ b/src/driver/sqlx_mysql.rs @@ -221,6 +221,6 @@ async fn into_db_connection(pool: MySqlPool) -> Result Result<(), DbErr> { } create_tables(db).await?; + println!("db_version: {:#?}", db.version()); db.query_one(builder.build(&insert)).await?; db.query_one(builder.build(&update)).await?; assert!(false); From 3e6423aa8afa856a06d786ea103eec497ee8ff0c Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 8 Nov 2021 22:55:00 +0800 Subject: [PATCH 14/31] This will fail loll --- src/database/db_connection.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/database/db_connection.rs b/src/database/db_connection.rs index d428e11e7..9c49927a1 100644 --- a/src/database/db_connection.rs +++ b/src/database/db_connection.rs @@ -294,10 +294,8 @@ impl DatabaseConnection { } => *support_returning, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(_) => true, - // #[cfg(feature = "sqlx-sqlite")] - // DatabaseConnection::SqlxSqlitePoolConnection(conn) => , - // #[cfg(feature = "mock")] - // DatabaseConnection::MockDatabaseConnection(conn) => , + #[cfg(feature = "sqlx-sqlite")] + DatabaseConnection::SqlxSqlitePoolConnection(_) => false, DatabaseConnection::Disconnected => panic!("Disconnected"), _ => unimplemented!(), } From 30a50ca75d67871abee421dd21179e211f49cd18 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 9 Nov 2021 10:22:31 +0800 Subject: [PATCH 15/31] Try --- tests/returning_tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/returning_tests.rs b/tests/returning_tests.rs index d441b68b5..476905067 100644 --- a/tests/returning_tests.rs +++ b/tests/returning_tests.rs @@ -43,7 +43,6 @@ async fn main() -> Result<(), DbErr> { println!("db_version: {:#?}", db.version()); db.query_one(builder.build(&insert)).await?; db.query_one(builder.build(&update)).await?; - assert!(false); ctx.delete().await; Ok(()) From 429b920dedcabc697a5b354ca6acc612c861b56c Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 9 Nov 2021 11:05:55 +0800 Subject: [PATCH 16/31] Fixup --- src/database/connection.rs | 7 ++++-- src/database/db_connection.rs | 40 +++++++++++++---------------------- src/database/transaction.rs | 7 +++++- src/executor/insert.rs | 6 +++--- src/executor/update.rs | 2 +- tests/returning_tests.rs | 12 ++++++----- 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/database/connection.rs b/src/database/connection.rs index e06c6e576..2a16156e9 100644 --- a/src/database/connection.rs +++ b/src/database/connection.rs @@ -45,8 +45,11 @@ pub trait ConnectionTrait<'a>: Sync { T: Send, E: std::error::Error + Send; - /// Check if the connection supports `RETURNING` syntax - fn support_returning(&self) -> bool; + /// Check if the connection supports `RETURNING` syntax on insert + fn returning_on_insert(&self) -> bool; + + /// Check if the connection supports `RETURNING` syntax on update + fn returning_on_update(&self) -> bool; /// Check if the connection is a test connection for the Mock database fn is_mock_connection(&self) -> bool { diff --git a/src/database/db_connection.rs b/src/database/db_connection.rs index 9c49927a1..90a472e4d 100644 --- a/src/database/db_connection.rs +++ b/src/database/db_connection.rs @@ -225,7 +225,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { } } - fn support_returning(&self) -> bool { + fn returning_on_insert(&self) -> bool { match self { #[cfg(feature = "sqlx-mysql")] DatabaseConnection::SqlxMySqlPoolConnection { @@ -235,13 +235,21 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { DatabaseConnection::SqlxPostgresPoolConnection(_) => true, #[cfg(feature = "sqlx-sqlite")] DatabaseConnection::SqlxSqlitePoolConnection(_) => false, - #[cfg(feature = "mock")] - DatabaseConnection::MockDatabaseConnection(conn) => match conn.get_database_backend() { - DbBackend::MySql => false, - DbBackend::Postgres => true, - DbBackend::Sqlite => false, - }, DatabaseConnection::Disconnected => panic!("Disconnected"), + _ => unimplemented!(), + } + } + + fn returning_on_update(&self) -> bool { + match self { + #[cfg(feature = "sqlx-mysql")] + DatabaseConnection::SqlxMySqlPoolConnection { .. } => false, + #[cfg(feature = "sqlx-postgres")] + DatabaseConnection::SqlxPostgresPoolConnection(_) => true, + #[cfg(feature = "sqlx-sqlite")] + DatabaseConnection::SqlxSqlitePoolConnection(_) => false, + DatabaseConnection::Disconnected => panic!("Disconnected"), + _ => unimplemented!(), } } @@ -278,24 +286,6 @@ impl DatabaseConnection { DatabaseConnection::SqlxPostgresPoolConnection(_) => "".to_string(), #[cfg(feature = "sqlx-sqlite")] DatabaseConnection::SqlxSqlitePoolConnection(_) => "".to_string(), - #[cfg(feature = "mock")] - DatabaseConnection::MockDatabaseConnection(_) => "".to_string(), - DatabaseConnection::Disconnected => panic!("Disconnected"), - _ => unimplemented!(), - } - } - - /// Check if database supports `RETURNING` - pub fn support_returning(&self) -> bool { - match self { - #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { - support_returning, .. - } => *support_returning, - #[cfg(feature = "sqlx-postgres")] - DatabaseConnection::SqlxPostgresPoolConnection(_) => true, - #[cfg(feature = "sqlx-sqlite")] - DatabaseConnection::SqlxSqlitePoolConnection(_) => false, DatabaseConnection::Disconnected => panic!("Disconnected"), _ => unimplemented!(), } diff --git a/src/database/transaction.rs b/src/database/transaction.rs index 5865311f5..727f9bc66 100644 --- a/src/database/transaction.rs +++ b/src/database/transaction.rs @@ -348,7 +348,12 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction { transaction.run(_callback).await } - fn support_returning(&self) -> bool { + fn returning_on_insert(&self) -> bool { + // FIXME: How? + false + } + + fn returning_on_update(&self) -> bool { // FIXME: How? false } diff --git a/src/executor/insert.rs b/src/executor/insert.rs index a6dbcbd53..fde1a3ab4 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -41,7 +41,7 @@ where { // so that self is dropped before entering await let mut query = self.query; - if db.support_returning() && ::PrimaryKey::iter().count() > 0 { + if db.returning_on_insert() && ::PrimaryKey::iter().count() > 0 { let mut returning = Query::select(); returning.columns( ::PrimaryKey::iter().map(|c| c.into_column_ref()), @@ -113,7 +113,7 @@ where { type PrimaryKey = <::Entity as EntityTrait>::PrimaryKey; type ValueTypeOf = as PrimaryKeyTrait>::ValueType; - let last_insert_id_opt = match db.support_returning() { + let last_insert_id_opt = match db.returning_on_insert() { true => { let cols = PrimaryKey::::iter() .map(|col| col.to_string()) @@ -147,7 +147,7 @@ where A: ActiveModelTrait, { let db_backend = db.get_database_backend(); - let found = match db.support_returning() { + let found = match db.returning_on_insert() { true => { let mut returning = Query::select(); returning.exprs(::Column::iter().map(|c| { diff --git a/src/executor/update.rs b/src/executor/update.rs index 9870b10d9..d27aa41d4 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -90,7 +90,7 @@ where A: ActiveModelTrait, C: ConnectionTrait<'a>, { - match db.support_returning() { + match db.returning_on_update() { true => { let mut returning = Query::select(); returning.exprs(::Column::iter().map(|c| { diff --git a/tests/returning_tests.rs b/tests/returning_tests.rs index 476905067..561ba2c50 100644 --- a/tests/returning_tests.rs +++ b/tests/returning_tests.rs @@ -31,12 +31,14 @@ async fn main() -> Result<(), DbErr> { (Column::ProfitMargin, 0.5.into()), ]) .and_where(Column::Id.eq(1)); - - if db.support_returning() { - let mut returning = Query::select(); - returning.columns(vec![Column::Id, Column::Name, Column::ProfitMargin]); + + let mut returning = Query::select(); + returning.columns(vec![Column::Id, Column::Name, Column::ProfitMargin]); + if db.returning_on_insert() { insert.returning(returning.clone()); - update.returning(returning); + } + if db.returning_on_update() { + update.returning(returning.clone()); } create_tables(db).await?; From 24fab66d17b6feff2ed628bd93f2e418aded738d Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 9 Nov 2021 12:11:00 +0800 Subject: [PATCH 17/31] Try --- src/database/db_connection.rs | 45 ++++++++++++++++++++++++++++------- src/driver/sqlx_mysql.rs | 2 ++ tests/returning_tests.rs | 16 ++++++++++--- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/database/db_connection.rs b/src/database/db_connection.rs index 90a472e4d..f04b67c7e 100644 --- a/src/database/db_connection.rs +++ b/src/database/db_connection.rs @@ -230,26 +230,55 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { #[cfg(feature = "sqlx-mysql")] DatabaseConnection::SqlxMySqlPoolConnection { support_returning, .. - } => *support_returning, + } => { + // Supported if it's MariaDB on or after version 10.5.0 + // Not supported in all MySQL versions + *support_returning + } #[cfg(feature = "sqlx-postgres")] - DatabaseConnection::SqlxPostgresPoolConnection(_) => true, + DatabaseConnection::SqlxPostgresPoolConnection(_) => { + // Supported by all Postgres versions + true + } #[cfg(feature = "sqlx-sqlite")] - DatabaseConnection::SqlxSqlitePoolConnection(_) => false, + DatabaseConnection::SqlxSqlitePoolConnection(_) => { + // Supported by SQLite on or after version 3.35.0 (2021-03-12) + false + } + #[cfg(feature = "mock")] + DatabaseConnection::MockDatabaseConnection(conn) => match conn.get_database_backend() { + DbBackend::MySql => false, + DbBackend::Postgres => true, + DbBackend::Sqlite => false, + }, DatabaseConnection::Disconnected => panic!("Disconnected"), - _ => unimplemented!(), } } fn returning_on_update(&self) -> bool { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { .. } => false, + DatabaseConnection::SqlxMySqlPoolConnection { .. } => { + // Not supported in all MySQL & MariaDB versions + false + } #[cfg(feature = "sqlx-postgres")] - DatabaseConnection::SqlxPostgresPoolConnection(_) => true, + DatabaseConnection::SqlxPostgresPoolConnection(_) => { + // Supported by all Postgres versions + true + } #[cfg(feature = "sqlx-sqlite")] - DatabaseConnection::SqlxSqlitePoolConnection(_) => false, + DatabaseConnection::SqlxSqlitePoolConnection(_) => { + // Supported by SQLite on or after version 3.35.0 (2021-03-12) + false + } + #[cfg(feature = "mock")] + DatabaseConnection::MockDatabaseConnection(conn) => match conn.get_database_backend() { + DbBackend::MySql => false, + DbBackend::Postgres => true, + DbBackend::Sqlite => false, + }, DatabaseConnection::Disconnected => panic!("Disconnected"), - _ => unimplemented!(), } } diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs index b66e1078f..55f46a945 100644 --- a/src/driver/sqlx_mysql.rs +++ b/src/driver/sqlx_mysql.rs @@ -195,6 +195,7 @@ async fn into_db_connection(pool: MySqlPool) -> Result Result= 10 && ver_minor >= 5 }; (version, support_returning) diff --git a/tests/returning_tests.rs b/tests/returning_tests.rs index 561ba2c50..d67612fe6 100644 --- a/tests/returning_tests.rs +++ b/tests/returning_tests.rs @@ -31,7 +31,7 @@ async fn main() -> Result<(), DbErr> { (Column::ProfitMargin, 0.5.into()), ]) .and_where(Column::Id.eq(1)); - + let mut returning = Query::select(); returning.columns(vec![Column::Id, Column::Name, Column::ProfitMargin]); if db.returning_on_insert() { @@ -43,8 +43,18 @@ async fn main() -> Result<(), DbErr> { create_tables(db).await?; println!("db_version: {:#?}", db.version()); - db.query_one(builder.build(&insert)).await?; - db.query_one(builder.build(&update)).await?; + let insert_res = db.query_one(builder.build(&insert)).await?.expect("Insert failed"); + if db.returning_on_insert() { + let _id: i32 = insert_res.try_get("", "id")?; + let _name: String = insert_res.try_get("", "name")?; + let _profit_margin: f64 = insert_res.try_get("", "profit_margin")?; + } + let update_res = db.query_one(builder.build(&update)).await?.expect("Update filed"); + if db.returning_on_update() { + let _id: i32 = insert_res.try_get("", "id")?; + let _name: String = insert_res.try_get("", "name")?; + let _profit_margin: f64 = insert_res.try_get("", "profit_margin")?; + } ctx.delete().await; Ok(()) From 8020ae1209db35ccc0d7fb7447eb8df5ca7b3c01 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 9 Nov 2021 12:23:49 +0800 Subject: [PATCH 18/31] Fixup --- tests/returning_tests.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/returning_tests.rs b/tests/returning_tests.rs index d67612fe6..4f2a5a884 100644 --- a/tests/returning_tests.rs +++ b/tests/returning_tests.rs @@ -43,18 +43,32 @@ async fn main() -> Result<(), DbErr> { create_tables(db).await?; println!("db_version: {:#?}", db.version()); - let insert_res = db.query_one(builder.build(&insert)).await?.expect("Insert failed"); + if db.returning_on_insert() { + let insert_res = db + .query_one(builder.build(&insert)) + .await? + .expect("Insert failed with query_one"); let _id: i32 = insert_res.try_get("", "id")?; let _name: String = insert_res.try_get("", "name")?; let _profit_margin: f64 = insert_res.try_get("", "profit_margin")?; + } else { + let insert_res = db.execute(builder.build(&insert)).await?; + assert!(insert_res.rows_affected() > 0); } - let update_res = db.query_one(builder.build(&update)).await?.expect("Update filed"); if db.returning_on_update() { - let _id: i32 = insert_res.try_get("", "id")?; - let _name: String = insert_res.try_get("", "name")?; - let _profit_margin: f64 = insert_res.try_get("", "profit_margin")?; + let update_res = db + .query_one(builder.build(&update)) + .await? + .expect("Update filed with query_one"); + let _id: i32 = update_res.try_get("", "id")?; + let _name: String = update_res.try_get("", "name")?; + let _profit_margin: f64 = update_res.try_get("", "profit_margin")?; + } else { + let update_res = db.execute(builder.build(&update)).await?; + assert!(update_res.rows_affected() > 0); } + ctx.delete().await; Ok(()) From 533c3cf175e33aa4ee0fe10eb23d26d689c1255c Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 9 Nov 2021 15:38:28 +0800 Subject: [PATCH 19/31] Try --- src/database/db_connection.rs | 39 +++++---------- src/database/transaction.rs | 54 ++++++++++++++++++--- src/driver/sqlx_mysql.rs | 90 +++++++++++++++++++---------------- 3 files changed, 109 insertions(+), 74 deletions(-) diff --git a/src/database/db_connection.rs b/src/database/db_connection.rs index f04b67c7e..2f78bdd7a 100644 --- a/src/database/db_connection.rs +++ b/src/database/db_connection.rs @@ -18,14 +18,7 @@ use std::sync::Arc; pub enum DatabaseConnection { /// Create a MYSQL database connection and pool #[cfg(feature = "sqlx-mysql")] - SqlxMySqlPoolConnection { - /// The SQLx MySQL pool - conn: crate::SqlxMySqlPoolConnection, - /// The MySQL version - version: String, - /// The flag indicating whether `RETURNING` syntax is supported - support_returning: bool, - }, + SqlxMySqlPoolConnection(crate::SqlxMySqlPoolConnection), /// Create a PostgreSQL database connection and pool #[cfg(feature = "sqlx-postgres")] SqlxPostgresPoolConnection(crate::SqlxPostgresPoolConnection), @@ -80,7 +73,7 @@ impl std::fmt::Debug for DatabaseConnection { "{}", match self { #[cfg(feature = "sqlx-mysql")] - Self::SqlxMySqlPoolConnection { .. } => "SqlxMySqlPoolConnection", + Self::SqlxMySqlPoolConnection(_) => "SqlxMySqlPoolConnection", #[cfg(feature = "sqlx-postgres")] Self::SqlxPostgresPoolConnection(_) => "SqlxPostgresPoolConnection", #[cfg(feature = "sqlx-sqlite")] @@ -100,7 +93,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { fn get_database_backend(&self) -> DbBackend { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { .. } => DbBackend::MySql, + DatabaseConnection::SqlxMySqlPoolConnection(_) => DbBackend::MySql, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(_) => DbBackend::Postgres, #[cfg(feature = "sqlx-sqlite")] @@ -114,7 +107,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { async fn execute(&self, stmt: Statement) -> Result { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { conn, .. } => conn.execute(stmt).await, + DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.execute(stmt).await, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => conn.execute(stmt).await, #[cfg(feature = "sqlx-sqlite")] @@ -128,7 +121,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { async fn query_one(&self, stmt: Statement) -> Result, DbErr> { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { conn, .. } => conn.query_one(stmt).await, + DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_one(stmt).await, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => conn.query_one(stmt).await, #[cfg(feature = "sqlx-sqlite")] @@ -142,7 +135,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { async fn query_all(&self, stmt: Statement) -> Result, DbErr> { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { conn, .. } => conn.query_all(stmt).await, + DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_all(stmt).await, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => conn.query_all(stmt).await, #[cfg(feature = "sqlx-sqlite")] @@ -160,9 +153,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { Box::pin(async move { Ok(match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { conn, .. } => { - conn.stream(stmt).await? - } + DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.stream(stmt).await?, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => conn.stream(stmt).await?, #[cfg(feature = "sqlx-sqlite")] @@ -179,7 +170,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { async fn begin(&self) -> Result { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { conn, .. } => conn.begin().await, + DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.begin().await, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => conn.begin().await, #[cfg(feature = "sqlx-sqlite")] @@ -205,9 +196,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { conn, .. } => { - conn.transaction(_callback).await - } + DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.transaction(_callback).await, #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(conn) => { conn.transaction(_callback).await @@ -228,12 +217,10 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { fn returning_on_insert(&self) -> bool { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { - support_returning, .. - } => { + DatabaseConnection::SqlxMySqlPoolConnection(conn) => { // Supported if it's MariaDB on or after version 10.5.0 // Not supported in all MySQL versions - *support_returning + conn.support_returning } #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(_) => { @@ -258,7 +245,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { fn returning_on_update(&self) -> bool { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { .. } => { + DatabaseConnection::SqlxMySqlPoolConnection(_) => { // Not supported in all MySQL & MariaDB versions false } @@ -310,7 +297,7 @@ impl DatabaseConnection { pub fn version(&self) -> String { match self { #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection { version, .. } => version.to_string(), + DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.version.to_string(), #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(_) => "".to_string(), #[cfg(feature = "sqlx-sqlite")] diff --git a/src/database/transaction.rs b/src/database/transaction.rs index 727f9bc66..71573f0d7 100644 --- a/src/database/transaction.rs +++ b/src/database/transaction.rs @@ -16,6 +16,7 @@ pub struct DatabaseTransaction { conn: Arc>, backend: DbBackend, open: bool, + support_returning: bool, } impl std::fmt::Debug for DatabaseTransaction { @@ -28,10 +29,12 @@ impl DatabaseTransaction { #[cfg(feature = "sqlx-mysql")] pub(crate) async fn new_mysql( inner: PoolConnection, + support_returning: bool, ) -> Result { Self::begin( Arc::new(Mutex::new(InnerConnection::MySql(inner))), DbBackend::MySql, + support_returning, ) .await } @@ -43,6 +46,7 @@ impl DatabaseTransaction { Self::begin( Arc::new(Mutex::new(InnerConnection::Postgres(inner))), DbBackend::Postgres, + true, ) .await } @@ -54,6 +58,7 @@ impl DatabaseTransaction { Self::begin( Arc::new(Mutex::new(InnerConnection::Sqlite(inner))), DbBackend::Sqlite, + false, ) .await } @@ -63,17 +68,28 @@ impl DatabaseTransaction { inner: Arc, ) -> Result { let backend = inner.get_database_backend(); - Self::begin(Arc::new(Mutex::new(InnerConnection::Mock(inner))), backend).await + Self::begin( + Arc::new(Mutex::new(InnerConnection::Mock(inner))), + backend, + match backend { + DbBackend::MySql => false, + DbBackend::Postgres => true, + DbBackend::Sqlite => false, + }, + ) + .await } async fn begin( conn: Arc>, backend: DbBackend, + support_returning: bool, ) -> Result { let res = DatabaseTransaction { conn, backend, open: true, + support_returning, }; match *res.conn.lock().await { #[cfg(feature = "sqlx-mysql")] @@ -330,7 +346,8 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction { } async fn begin(&self) -> Result { - DatabaseTransaction::begin(Arc::clone(&self.conn), self.backend).await + DatabaseTransaction::begin(Arc::clone(&self.conn), self.backend, self.support_returning) + .await } /// Execute the function inside a transaction. @@ -349,13 +366,38 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction { } fn returning_on_insert(&self) -> bool { - // FIXME: How? - false + match self.backend { + DbBackend::MySql => { + // Supported if it's MariaDB on or after version 10.5.0 + // Not supported in all MySQL versions + self.support_returning + } + DbBackend::Postgres => { + // Supported by all Postgres versions + true + } + DbBackend::Sqlite => { + // Supported by SQLite on or after version 3.35.0 (2021-03-12) + false + } + } } fn returning_on_update(&self) -> bool { - // FIXME: How? - false + match self.backend { + DbBackend::MySql => { + // Not supported in all MySQL & MariaDB versions + false + } + DbBackend::Postgres => { + // Supported by all Postgres versions + true + } + DbBackend::Sqlite => { + // Supported by SQLite on or after version 3.35.0 (2021-03-12) + false + } + } } } diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs index 55f46a945..e31307ee1 100644 --- a/src/driver/sqlx_mysql.rs +++ b/src/driver/sqlx_mysql.rs @@ -3,7 +3,7 @@ use std::{future::Future, pin::Pin}; use sqlx::{ mysql::{MySqlArguments, MySqlConnectOptions, MySqlQueryResult, MySqlRow}, - MySql, MySqlPool, + MySql, MySqlPool, Row, }; sea_query::sea_query_driver_mysql!(); @@ -24,6 +24,8 @@ pub struct SqlxMySqlConnector; #[derive(Debug, Clone)] pub struct SqlxMySqlPoolConnection { pool: MySqlPool, + pub(crate) version: String, + pub(crate) support_returning: bool, } impl SqlxMySqlConnector { @@ -128,7 +130,7 @@ impl SqlxMySqlPoolConnection { /// Bundle a set of SQL statements that execute together. pub async fn begin(&self) -> Result { if let Ok(conn) = self.pool.acquire().await { - DatabaseTransaction::new_mysql(conn).await + DatabaseTransaction::new_mysql(conn, self.support_returning).await } else { Err(DbErr::Query( "Failed to acquire connection from pool.".to_owned(), @@ -147,7 +149,7 @@ impl SqlxMySqlPoolConnection { E: std::error::Error + Send, { if let Ok(conn) = self.pool.acquire().await { - let transaction = DatabaseTransaction::new_mysql(conn) + let transaction = DatabaseTransaction::new_mysql(conn, self.support_returning) .await .map_err(|e| TransactionError::Connection(e))?; transaction.run(callback).await @@ -184,45 +186,49 @@ pub(crate) fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, MySql, MySq } async fn into_db_connection(pool: MySqlPool) -> Result { - let conn = SqlxMySqlPoolConnection { pool }; - let res = conn - .query_one(Statement::from_string( - DbBackend::MySql, - r#"SHOW VARIABLES LIKE "version""#.to_owned(), - )) - .await?; - let (version, support_returning) = if let Some(query_result) = res { - let version: String = query_result.try_get("", "Value")?; - let support_returning = if !version.contains("MariaDB") { - // This is MySQL - // Not supported in all MySQL versions - false - } else { - // This is MariaDB - let regex = Regex::new(r"^(\d+)?.(\d+)?.(\*|\d+)").unwrap(); - let captures = regex.captures(&version).unwrap(); - macro_rules! parse_captures { - ( $idx: expr ) => { - captures.get($idx).map_or(0, |m| { - m.as_str() - .parse::() - .map_err(|e| DbErr::Conn(e.to_string())) - .unwrap() - }) - }; - } - let ver_major = parse_captures!(1); - let ver_minor = parse_captures!(2); - // Supported if it's MariaDB with version 10.5.0 or after - ver_major >= 10 && ver_minor >= 5 - }; - (version, support_returning) + let (version, support_returning) = parse_support_returning(&pool).await?; + Ok(DatabaseConnection::SqlxMySqlPoolConnection( + SqlxMySqlPoolConnection { + pool, + version, + support_returning, + }, + )) +} + +async fn parse_support_returning(pool: &MySqlPool) -> Result<(String, bool), DbErr> { + let stmt = Statement::from_string( + DbBackend::MySql, + r#"SHOW VARIABLES LIKE "version""#.to_owned(), + ); + let query = sqlx_query(&stmt); + let row = query + .fetch_one(pool) + .await + .map_err(sqlx_error_to_query_err)?; + let version: String = row.try_get("Value").map_err(sqlx_error_to_query_err)?; + let support_returning = if !version.contains("MariaDB") { + // This is MySQL + // Not supported in all MySQL versions + false } else { - return Err(DbErr::Conn("Fail to parse MySQL version".to_owned())); + // This is MariaDB + let regex = Regex::new(r"^(\d+)?.(\d+)?.(\*|\d+)").unwrap(); + let captures = regex.captures(&version).unwrap(); + macro_rules! parse_captures { + ( $idx: expr ) => { + captures.get($idx).map_or(0, |m| { + m.as_str() + .parse::() + .map_err(|e| DbErr::Conn(e.to_string())) + .unwrap() + }) + }; + } + let ver_major = parse_captures!(1); + let ver_minor = parse_captures!(2); + // Supported if it's MariaDB with version 10.5.0 or after + ver_major >= 10 && ver_minor >= 5 }; - Ok(DatabaseConnection::SqlxMySqlPoolConnection { - conn, - version, - support_returning, - }) + Ok((version, support_returning)) } From ec637b26a04cad37d9907a1727571f55c22a7999 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 9 Nov 2021 16:10:52 +0800 Subject: [PATCH 20/31] Returning support for SQLite --- src/database/db_connection.rs | 10 +++--- src/database/transaction.rs | 7 ++-- src/driver/sqlx_sqlite.rs | 60 +++++++++++++++++++++++++++++------ tests/returning_tests.rs | 8 ++--- 4 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/database/db_connection.rs b/src/database/db_connection.rs index 2f78bdd7a..476fe8e09 100644 --- a/src/database/db_connection.rs +++ b/src/database/db_connection.rs @@ -228,9 +228,9 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { true } #[cfg(feature = "sqlx-sqlite")] - DatabaseConnection::SqlxSqlitePoolConnection(_) => { + DatabaseConnection::SqlxSqlitePoolConnection(conn) => { // Supported by SQLite on or after version 3.35.0 (2021-03-12) - false + conn.support_returning } #[cfg(feature = "mock")] DatabaseConnection::MockDatabaseConnection(conn) => match conn.get_database_backend() { @@ -255,9 +255,9 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { true } #[cfg(feature = "sqlx-sqlite")] - DatabaseConnection::SqlxSqlitePoolConnection(_) => { + DatabaseConnection::SqlxSqlitePoolConnection(conn) => { // Supported by SQLite on or after version 3.35.0 (2021-03-12) - false + conn.support_returning } #[cfg(feature = "mock")] DatabaseConnection::MockDatabaseConnection(conn) => match conn.get_database_backend() { @@ -301,7 +301,7 @@ impl DatabaseConnection { #[cfg(feature = "sqlx-postgres")] DatabaseConnection::SqlxPostgresPoolConnection(_) => "".to_string(), #[cfg(feature = "sqlx-sqlite")] - DatabaseConnection::SqlxSqlitePoolConnection(_) => "".to_string(), + DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.version.to_string(), DatabaseConnection::Disconnected => panic!("Disconnected"), _ => unimplemented!(), } diff --git a/src/database/transaction.rs b/src/database/transaction.rs index 71573f0d7..168461c48 100644 --- a/src/database/transaction.rs +++ b/src/database/transaction.rs @@ -54,11 +54,12 @@ impl DatabaseTransaction { #[cfg(feature = "sqlx-sqlite")] pub(crate) async fn new_sqlite( inner: PoolConnection, + support_returning: bool, ) -> Result { Self::begin( Arc::new(Mutex::new(InnerConnection::Sqlite(inner))), DbBackend::Sqlite, - false, + support_returning, ) .await } @@ -378,7 +379,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction { } DbBackend::Sqlite => { // Supported by SQLite on or after version 3.35.0 (2021-03-12) - false + self.support_returning } } } @@ -395,7 +396,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction { } DbBackend::Sqlite => { // Supported by SQLite on or after version 3.35.0 (2021-03-12) - false + self.support_returning } } } diff --git a/src/driver/sqlx_sqlite.rs b/src/driver/sqlx_sqlite.rs index 69eee5752..c98a3dea4 100644 --- a/src/driver/sqlx_sqlite.rs +++ b/src/driver/sqlx_sqlite.rs @@ -1,8 +1,9 @@ +use regex::Regex; use std::{future::Future, pin::Pin}; use sqlx::{ sqlite::{SqliteArguments, SqliteConnectOptions, SqliteQueryResult, SqliteRow}, - Sqlite, SqlitePool, + Row, Sqlite, SqlitePool, }; sea_query::sea_query_driver_sqlite!(); @@ -10,7 +11,7 @@ use sea_query_driver_sqlite::bind_query; use crate::{ debug_print, error::*, executor::*, ConnectOptions, DatabaseConnection, DatabaseTransaction, - QueryStream, Statement, TransactionError, + DbBackend, QueryStream, Statement, TransactionError, }; use super::sqlx_common::*; @@ -23,6 +24,8 @@ pub struct SqlxSqliteConnector; #[derive(Debug, Clone)] pub struct SqlxSqlitePoolConnection { pool: SqlitePool, + pub(crate) version: String, + pub(crate) support_returning: bool, } impl SqlxSqliteConnector { @@ -46,9 +49,7 @@ impl SqlxSqliteConnector { options.max_connections(1); } if let Ok(pool) = options.pool_options().connect_with(opt).await { - Ok(DatabaseConnection::SqlxSqlitePoolConnection( - SqlxSqlitePoolConnection { pool }, - )) + into_db_connection(pool).await } else { Err(DbErr::Conn("Failed to connect.".to_owned())) } @@ -57,8 +58,8 @@ impl SqlxSqliteConnector { impl SqlxSqliteConnector { /// Instantiate a sqlx pool connection to a [DatabaseConnection] - pub fn from_sqlx_sqlite_pool(pool: SqlitePool) -> DatabaseConnection { - DatabaseConnection::SqlxSqlitePoolConnection(SqlxSqlitePoolConnection { pool }) + pub async fn from_sqlx_sqlite_pool(pool: SqlitePool) -> Result { + into_db_connection(pool).await } } @@ -133,7 +134,7 @@ impl SqlxSqlitePoolConnection { /// Bundle a set of SQL statements that execute together. pub async fn begin(&self) -> Result { if let Ok(conn) = self.pool.acquire().await { - DatabaseTransaction::new_sqlite(conn).await + DatabaseTransaction::new_sqlite(conn, self.support_returning).await } else { Err(DbErr::Query( "Failed to acquire connection from pool.".to_owned(), @@ -152,7 +153,7 @@ impl SqlxSqlitePoolConnection { E: std::error::Error + Send, { if let Ok(conn) = self.pool.acquire().await { - let transaction = DatabaseTransaction::new_sqlite(conn) + let transaction = DatabaseTransaction::new_sqlite(conn, self.support_returning) .await .map_err(|e| TransactionError::Connection(e))?; transaction.run(callback).await @@ -187,3 +188,44 @@ pub(crate) fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, Sqlite, Sql } query } + +async fn into_db_connection(pool: SqlitePool) -> Result { + let (version, support_returning) = parse_support_returning(&pool).await?; + Ok(DatabaseConnection::SqlxSqlitePoolConnection( + SqlxSqlitePoolConnection { + pool, + version, + support_returning, + }, + )) +} + +async fn parse_support_returning(pool: &SqlitePool) -> Result<(String, bool), DbErr> { + let stmt = Statement::from_string( + DbBackend::Sqlite, + r#"SELECT sqlite_version() AS version"#.to_owned(), + ); + let query = sqlx_query(&stmt); + let row = query + .fetch_one(pool) + .await + .map_err(sqlx_error_to_query_err)?; + let version: String = row.try_get("version").map_err(sqlx_error_to_query_err)?; + let regex = Regex::new(r"^(\d+)?.(\d+)?.(\*|\d+)").unwrap(); + let captures = regex.captures(&version).unwrap(); + macro_rules! parse_captures { + ( $idx: expr ) => { + captures.get($idx).map_or(0, |m| { + m.as_str() + .parse::() + .map_err(|e| DbErr::Conn(e.to_string())) + .unwrap() + }) + }; + } + let ver_major = parse_captures!(1); + let ver_minor = parse_captures!(2); + // Supported if it's version 3.35.0 (2021-03-12) or after + let support_returning = ver_major >= 3 && ver_minor >= 35; + Ok((version, support_returning)) +} diff --git a/tests/returning_tests.rs b/tests/returning_tests.rs index 4f2a5a884..94e8a3990 100644 --- a/tests/returning_tests.rs +++ b/tests/returning_tests.rs @@ -34,17 +34,12 @@ async fn main() -> Result<(), DbErr> { let mut returning = Query::select(); returning.columns(vec![Column::Id, Column::Name, Column::ProfitMargin]); - if db.returning_on_insert() { - insert.returning(returning.clone()); - } - if db.returning_on_update() { - update.returning(returning.clone()); - } create_tables(db).await?; println!("db_version: {:#?}", db.version()); if db.returning_on_insert() { + insert.returning(returning.clone()); let insert_res = db .query_one(builder.build(&insert)) .await? @@ -57,6 +52,7 @@ async fn main() -> Result<(), DbErr> { assert!(insert_res.rows_affected() > 0); } if db.returning_on_update() { + update.returning(returning.clone()); let update_res = db .query_one(builder.build(&update)) .await? From c1fae1bc867c12f9d2fd567a7149e1545f8ea7da Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 9 Nov 2021 16:54:29 +0800 Subject: [PATCH 21/31] Debug print --- src/driver/sqlx_mysql.rs | 2 ++ src/driver/sqlx_sqlite.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs index e31307ee1..44efdc0ba 100644 --- a/src/driver/sqlx_mysql.rs +++ b/src/driver/sqlx_mysql.rs @@ -230,5 +230,7 @@ async fn parse_support_returning(pool: &MySqlPool) -> Result<(String, bool), DbE // Supported if it's MariaDB with version 10.5.0 or after ver_major >= 10 && ver_minor >= 5 }; + debug_print!("db_version: {}", version); + debug_print!("db_support_returning: {}", support_returning); Ok((version, support_returning)) } diff --git a/src/driver/sqlx_sqlite.rs b/src/driver/sqlx_sqlite.rs index c98a3dea4..4cbdc2318 100644 --- a/src/driver/sqlx_sqlite.rs +++ b/src/driver/sqlx_sqlite.rs @@ -227,5 +227,7 @@ async fn parse_support_returning(pool: &SqlitePool) -> Result<(String, bool), Db let ver_minor = parse_captures!(2); // Supported if it's version 3.35.0 (2021-03-12) or after let support_returning = ver_major >= 3 && ver_minor >= 35; + debug_print!("db_version: {}", version); + debug_print!("db_support_returning: {}", support_returning); Ok((version, support_returning)) } From cc035d7aa71f2c7f3396c4fb6b705b240a1c5df3 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 9 Nov 2021 18:14:13 +0800 Subject: [PATCH 22/31] Refactoring --- src/database/db_connection.rs | 16 ---------------- src/database/transaction.rs | 30 +++--------------------------- src/driver/sqlx_mysql.rs | 8 +++----- src/driver/sqlx_sqlite.rs | 8 +++----- tests/returning_tests.rs | 1 - 5 files changed, 9 insertions(+), 54 deletions(-) diff --git a/src/database/db_connection.rs b/src/database/db_connection.rs index 476fe8e09..ab9d734a1 100644 --- a/src/database/db_connection.rs +++ b/src/database/db_connection.rs @@ -292,22 +292,6 @@ impl DatabaseConnection { } } -impl DatabaseConnection { - /// Get database version - pub fn version(&self) -> String { - match self { - #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.version.to_string(), - #[cfg(feature = "sqlx-postgres")] - DatabaseConnection::SqlxPostgresPoolConnection(_) => "".to_string(), - #[cfg(feature = "sqlx-sqlite")] - DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.version.to_string(), - DatabaseConnection::Disconnected => panic!("Disconnected"), - _ => unimplemented!(), - } - } -} - impl DbBackend { /// Check if the URI is the same as the specified database backend. /// Returns true if they match. diff --git a/src/database/transaction.rs b/src/database/transaction.rs index 168461c48..cfce9d58a 100644 --- a/src/database/transaction.rs +++ b/src/database/transaction.rs @@ -367,37 +367,13 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction { } fn returning_on_insert(&self) -> bool { - match self.backend { - DbBackend::MySql => { - // Supported if it's MariaDB on or after version 10.5.0 - // Not supported in all MySQL versions - self.support_returning - } - DbBackend::Postgres => { - // Supported by all Postgres versions - true - } - DbBackend::Sqlite => { - // Supported by SQLite on or after version 3.35.0 (2021-03-12) - self.support_returning - } - } + self.support_returning } fn returning_on_update(&self) -> bool { match self.backend { - DbBackend::MySql => { - // Not supported in all MySQL & MariaDB versions - false - } - DbBackend::Postgres => { - // Supported by all Postgres versions - true - } - DbBackend::Sqlite => { - // Supported by SQLite on or after version 3.35.0 (2021-03-12) - self.support_returning - } + DbBackend::MySql => false, + _ => self.support_returning, } } } diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs index 44efdc0ba..e29ee9f80 100644 --- a/src/driver/sqlx_mysql.rs +++ b/src/driver/sqlx_mysql.rs @@ -24,7 +24,6 @@ pub struct SqlxMySqlConnector; #[derive(Debug, Clone)] pub struct SqlxMySqlPoolConnection { pool: MySqlPool, - pub(crate) version: String, pub(crate) support_returning: bool, } @@ -186,17 +185,16 @@ pub(crate) fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, MySql, MySq } async fn into_db_connection(pool: MySqlPool) -> Result { - let (version, support_returning) = parse_support_returning(&pool).await?; + let support_returning = parse_support_returning(&pool).await?; Ok(DatabaseConnection::SqlxMySqlPoolConnection( SqlxMySqlPoolConnection { pool, - version, support_returning, }, )) } -async fn parse_support_returning(pool: &MySqlPool) -> Result<(String, bool), DbErr> { +async fn parse_support_returning(pool: &MySqlPool) -> Result { let stmt = Statement::from_string( DbBackend::MySql, r#"SHOW VARIABLES LIKE "version""#.to_owned(), @@ -232,5 +230,5 @@ async fn parse_support_returning(pool: &MySqlPool) -> Result<(String, bool), DbE }; debug_print!("db_version: {}", version); debug_print!("db_support_returning: {}", support_returning); - Ok((version, support_returning)) + Ok(support_returning) } diff --git a/src/driver/sqlx_sqlite.rs b/src/driver/sqlx_sqlite.rs index 4cbdc2318..4ea160e85 100644 --- a/src/driver/sqlx_sqlite.rs +++ b/src/driver/sqlx_sqlite.rs @@ -24,7 +24,6 @@ pub struct SqlxSqliteConnector; #[derive(Debug, Clone)] pub struct SqlxSqlitePoolConnection { pool: SqlitePool, - pub(crate) version: String, pub(crate) support_returning: bool, } @@ -190,17 +189,16 @@ pub(crate) fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, Sqlite, Sql } async fn into_db_connection(pool: SqlitePool) -> Result { - let (version, support_returning) = parse_support_returning(&pool).await?; + let support_returning = parse_support_returning(&pool).await?; Ok(DatabaseConnection::SqlxSqlitePoolConnection( SqlxSqlitePoolConnection { pool, - version, support_returning, }, )) } -async fn parse_support_returning(pool: &SqlitePool) -> Result<(String, bool), DbErr> { +async fn parse_support_returning(pool: &SqlitePool) -> Result { let stmt = Statement::from_string( DbBackend::Sqlite, r#"SELECT sqlite_version() AS version"#.to_owned(), @@ -229,5 +227,5 @@ async fn parse_support_returning(pool: &SqlitePool) -> Result<(String, bool), Db let support_returning = ver_major >= 3 && ver_minor >= 35; debug_print!("db_version: {}", version); debug_print!("db_support_returning: {}", support_returning); - Ok((version, support_returning)) + Ok(support_returning) } diff --git a/tests/returning_tests.rs b/tests/returning_tests.rs index 94e8a3990..55506399f 100644 --- a/tests/returning_tests.rs +++ b/tests/returning_tests.rs @@ -36,7 +36,6 @@ async fn main() -> Result<(), DbErr> { returning.columns(vec![Column::Id, Column::Name, Column::ProfitMargin]); create_tables(db).await?; - println!("db_version: {:#?}", db.version()); if db.returning_on_insert() { insert.returning(returning.clone()); From 66c23c85dbc998471b9a9ac6be5cea8c36a289f9 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 10 Nov 2021 14:40:44 +0800 Subject: [PATCH 23/31] Revert MySQL & SQLite returning support --- .github/workflows/rust.yml | 2 ++ Cargo.toml | 1 - src/database/connection.rs | 10 +++--- src/database/db_connection.rs | 60 +++---------------------------- src/database/transaction.rs | 33 ++--------------- src/driver/sqlx_mysql.rs | 67 +++++------------------------------ src/driver/sqlx_sqlite.rs | 60 +++++-------------------------- src/executor/insert.rs | 6 ++-- src/executor/update.rs | 2 +- tests/returning_tests.rs | 15 ++++---- 10 files changed, 43 insertions(+), 213 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c4d112352..2926e2ab8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -288,6 +288,7 @@ jobs: name: Examples runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ubuntu-latest] path: [basic, actix_example, actix4_example, axum_example, rocket_example] @@ -312,6 +313,7 @@ jobs: if: ${{ (needs.init.outputs.run-partial == 'true' && needs.init.outputs.run-issues == 'true') }} runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ubuntu-latest] path: [86, 249, 262] diff --git a/Cargo.toml b/Cargo.toml index 3f0e0ea31..d6d41c35e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,6 @@ sqlx = { version = "^0.5", optional = true } uuid = { version = "0.8", features = ["serde", "v4"], optional = true } ouroboros = "0.11" url = "^2.2" -regex = "^1" [dev-dependencies] smol = { version = "^1.2" } diff --git a/src/database/connection.rs b/src/database/connection.rs index 2a16156e9..c5730a4bd 100644 --- a/src/database/connection.rs +++ b/src/database/connection.rs @@ -45,11 +45,11 @@ pub trait ConnectionTrait<'a>: Sync { T: Send, E: std::error::Error + Send; - /// Check if the connection supports `RETURNING` syntax on insert - fn returning_on_insert(&self) -> bool; - - /// Check if the connection supports `RETURNING` syntax on update - fn returning_on_update(&self) -> bool; + /// Check if the connection supports `RETURNING` syntax on insert and update + fn support_returning(&self) -> bool { + let db_backend = self.get_database_backend(); + db_backend.support_returning() + } /// Check if the connection is a test connection for the Mock database fn is_mock_connection(&self) -> bool { diff --git a/src/database/db_connection.rs b/src/database/db_connection.rs index ab9d734a1..99de8633f 100644 --- a/src/database/db_connection.rs +++ b/src/database/db_connection.rs @@ -214,61 +214,6 @@ impl<'a> ConnectionTrait<'a> for DatabaseConnection { } } - fn returning_on_insert(&self) -> bool { - match self { - #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection(conn) => { - // Supported if it's MariaDB on or after version 10.5.0 - // Not supported in all MySQL versions - conn.support_returning - } - #[cfg(feature = "sqlx-postgres")] - DatabaseConnection::SqlxPostgresPoolConnection(_) => { - // Supported by all Postgres versions - true - } - #[cfg(feature = "sqlx-sqlite")] - DatabaseConnection::SqlxSqlitePoolConnection(conn) => { - // Supported by SQLite on or after version 3.35.0 (2021-03-12) - conn.support_returning - } - #[cfg(feature = "mock")] - DatabaseConnection::MockDatabaseConnection(conn) => match conn.get_database_backend() { - DbBackend::MySql => false, - DbBackend::Postgres => true, - DbBackend::Sqlite => false, - }, - DatabaseConnection::Disconnected => panic!("Disconnected"), - } - } - - fn returning_on_update(&self) -> bool { - match self { - #[cfg(feature = "sqlx-mysql")] - DatabaseConnection::SqlxMySqlPoolConnection(_) => { - // Not supported in all MySQL & MariaDB versions - false - } - #[cfg(feature = "sqlx-postgres")] - DatabaseConnection::SqlxPostgresPoolConnection(_) => { - // Supported by all Postgres versions - true - } - #[cfg(feature = "sqlx-sqlite")] - DatabaseConnection::SqlxSqlitePoolConnection(conn) => { - // Supported by SQLite on or after version 3.35.0 (2021-03-12) - conn.support_returning - } - #[cfg(feature = "mock")] - DatabaseConnection::MockDatabaseConnection(conn) => match conn.get_database_backend() { - DbBackend::MySql => false, - DbBackend::Postgres => true, - DbBackend::Sqlite => false, - }, - DatabaseConnection::Disconnected => panic!("Disconnected"), - } - } - #[cfg(feature = "mock")] fn is_mock_connection(&self) -> bool { matches!(self, DatabaseConnection::MockDatabaseConnection(_)) @@ -322,6 +267,11 @@ impl DbBackend { Self::Sqlite => Box::new(SqliteQueryBuilder), } } + + /// Check if the database supports `RETURNING` syntax on insert and update + pub fn support_returning(&self) -> bool { + matches!(self, Self::Postgres) + } } #[cfg(test)] diff --git a/src/database/transaction.rs b/src/database/transaction.rs index cfce9d58a..f4a1b6787 100644 --- a/src/database/transaction.rs +++ b/src/database/transaction.rs @@ -16,7 +16,6 @@ pub struct DatabaseTransaction { conn: Arc>, backend: DbBackend, open: bool, - support_returning: bool, } impl std::fmt::Debug for DatabaseTransaction { @@ -29,12 +28,10 @@ impl DatabaseTransaction { #[cfg(feature = "sqlx-mysql")] pub(crate) async fn new_mysql( inner: PoolConnection, - support_returning: bool, ) -> Result { Self::begin( Arc::new(Mutex::new(InnerConnection::MySql(inner))), DbBackend::MySql, - support_returning, ) .await } @@ -46,7 +43,6 @@ impl DatabaseTransaction { Self::begin( Arc::new(Mutex::new(InnerConnection::Postgres(inner))), DbBackend::Postgres, - true, ) .await } @@ -54,12 +50,10 @@ impl DatabaseTransaction { #[cfg(feature = "sqlx-sqlite")] pub(crate) async fn new_sqlite( inner: PoolConnection, - support_returning: bool, ) -> Result { Self::begin( Arc::new(Mutex::new(InnerConnection::Sqlite(inner))), DbBackend::Sqlite, - support_returning, ) .await } @@ -69,28 +63,17 @@ impl DatabaseTransaction { inner: Arc, ) -> Result { let backend = inner.get_database_backend(); - Self::begin( - Arc::new(Mutex::new(InnerConnection::Mock(inner))), - backend, - match backend { - DbBackend::MySql => false, - DbBackend::Postgres => true, - DbBackend::Sqlite => false, - }, - ) - .await + Self::begin(Arc::new(Mutex::new(InnerConnection::Mock(inner))), backend).await } async fn begin( conn: Arc>, backend: DbBackend, - support_returning: bool, ) -> Result { let res = DatabaseTransaction { conn, backend, open: true, - support_returning, }; match *res.conn.lock().await { #[cfg(feature = "sqlx-mysql")] @@ -347,8 +330,7 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction { } async fn begin(&self) -> Result { - DatabaseTransaction::begin(Arc::clone(&self.conn), self.backend, self.support_returning) - .await + DatabaseTransaction::begin(Arc::clone(&self.conn), self.backend).await } /// Execute the function inside a transaction. @@ -365,17 +347,6 @@ impl<'a> ConnectionTrait<'a> for DatabaseTransaction { let transaction = self.begin().await.map_err(TransactionError::Connection)?; transaction.run(_callback).await } - - fn returning_on_insert(&self) -> bool { - self.support_returning - } - - fn returning_on_update(&self) -> bool { - match self.backend { - DbBackend::MySql => false, - _ => self.support_returning, - } - } } /// Defines errors for handling transaction failures diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs index e29ee9f80..b2b89c680 100644 --- a/src/driver/sqlx_mysql.rs +++ b/src/driver/sqlx_mysql.rs @@ -1,9 +1,8 @@ -use regex::Regex; use std::{future::Future, pin::Pin}; use sqlx::{ mysql::{MySqlArguments, MySqlConnectOptions, MySqlQueryResult, MySqlRow}, - MySql, MySqlPool, Row, + MySql, MySqlPool, }; sea_query::sea_query_driver_mysql!(); @@ -11,7 +10,7 @@ use sea_query_driver_mysql::bind_query; use crate::{ debug_print, error::*, executor::*, ConnectOptions, DatabaseConnection, DatabaseTransaction, - DbBackend, QueryStream, Statement, TransactionError, + QueryStream, Statement, TransactionError, }; use super::sqlx_common::*; @@ -24,7 +23,6 @@ pub struct SqlxMySqlConnector; #[derive(Debug, Clone)] pub struct SqlxMySqlPoolConnection { pool: MySqlPool, - pub(crate) support_returning: bool, } impl SqlxMySqlConnector { @@ -44,7 +42,9 @@ impl SqlxMySqlConnector { opt.disable_statement_logging(); } if let Ok(pool) = options.pool_options().connect_with(opt).await { - into_db_connection(pool).await + Ok(DatabaseConnection::SqlxMySqlPoolConnection( + SqlxMySqlPoolConnection { pool }, + )) } else { Err(DbErr::Conn("Failed to connect.".to_owned())) } @@ -53,8 +53,8 @@ impl SqlxMySqlConnector { impl SqlxMySqlConnector { /// Instantiate a sqlx pool connection to a [DatabaseConnection] - pub async fn from_sqlx_mysql_pool(pool: MySqlPool) -> Result { - into_db_connection(pool).await + pub fn from_sqlx_mysql_pool(pool: MySqlPool) -> DatabaseConnection { + DatabaseConnection::SqlxMySqlPoolConnection(SqlxMySqlPoolConnection { pool }) } } @@ -129,7 +129,7 @@ impl SqlxMySqlPoolConnection { /// Bundle a set of SQL statements that execute together. pub async fn begin(&self) -> Result { if let Ok(conn) = self.pool.acquire().await { - DatabaseTransaction::new_mysql(conn, self.support_returning).await + DatabaseTransaction::new_mysql(conn).await } else { Err(DbErr::Query( "Failed to acquire connection from pool.".to_owned(), @@ -148,7 +148,7 @@ impl SqlxMySqlPoolConnection { E: std::error::Error + Send, { if let Ok(conn) = self.pool.acquire().await { - let transaction = DatabaseTransaction::new_mysql(conn, self.support_returning) + let transaction = DatabaseTransaction::new_mysql(conn) .await .map_err(|e| TransactionError::Connection(e))?; transaction.run(callback).await @@ -183,52 +183,3 @@ pub(crate) fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, MySql, MySq } query } - -async fn into_db_connection(pool: MySqlPool) -> Result { - let support_returning = parse_support_returning(&pool).await?; - Ok(DatabaseConnection::SqlxMySqlPoolConnection( - SqlxMySqlPoolConnection { - pool, - support_returning, - }, - )) -} - -async fn parse_support_returning(pool: &MySqlPool) -> Result { - let stmt = Statement::from_string( - DbBackend::MySql, - r#"SHOW VARIABLES LIKE "version""#.to_owned(), - ); - let query = sqlx_query(&stmt); - let row = query - .fetch_one(pool) - .await - .map_err(sqlx_error_to_query_err)?; - let version: String = row.try_get("Value").map_err(sqlx_error_to_query_err)?; - let support_returning = if !version.contains("MariaDB") { - // This is MySQL - // Not supported in all MySQL versions - false - } else { - // This is MariaDB - let regex = Regex::new(r"^(\d+)?.(\d+)?.(\*|\d+)").unwrap(); - let captures = regex.captures(&version).unwrap(); - macro_rules! parse_captures { - ( $idx: expr ) => { - captures.get($idx).map_or(0, |m| { - m.as_str() - .parse::() - .map_err(|e| DbErr::Conn(e.to_string())) - .unwrap() - }) - }; - } - let ver_major = parse_captures!(1); - let ver_minor = parse_captures!(2); - // Supported if it's MariaDB with version 10.5.0 or after - ver_major >= 10 && ver_minor >= 5 - }; - debug_print!("db_version: {}", version); - debug_print!("db_support_returning: {}", support_returning); - Ok(support_returning) -} diff --git a/src/driver/sqlx_sqlite.rs b/src/driver/sqlx_sqlite.rs index 4ea160e85..69eee5752 100644 --- a/src/driver/sqlx_sqlite.rs +++ b/src/driver/sqlx_sqlite.rs @@ -1,9 +1,8 @@ -use regex::Regex; use std::{future::Future, pin::Pin}; use sqlx::{ sqlite::{SqliteArguments, SqliteConnectOptions, SqliteQueryResult, SqliteRow}, - Row, Sqlite, SqlitePool, + Sqlite, SqlitePool, }; sea_query::sea_query_driver_sqlite!(); @@ -11,7 +10,7 @@ use sea_query_driver_sqlite::bind_query; use crate::{ debug_print, error::*, executor::*, ConnectOptions, DatabaseConnection, DatabaseTransaction, - DbBackend, QueryStream, Statement, TransactionError, + QueryStream, Statement, TransactionError, }; use super::sqlx_common::*; @@ -24,7 +23,6 @@ pub struct SqlxSqliteConnector; #[derive(Debug, Clone)] pub struct SqlxSqlitePoolConnection { pool: SqlitePool, - pub(crate) support_returning: bool, } impl SqlxSqliteConnector { @@ -48,7 +46,9 @@ impl SqlxSqliteConnector { options.max_connections(1); } if let Ok(pool) = options.pool_options().connect_with(opt).await { - into_db_connection(pool).await + Ok(DatabaseConnection::SqlxSqlitePoolConnection( + SqlxSqlitePoolConnection { pool }, + )) } else { Err(DbErr::Conn("Failed to connect.".to_owned())) } @@ -57,8 +57,8 @@ impl SqlxSqliteConnector { impl SqlxSqliteConnector { /// Instantiate a sqlx pool connection to a [DatabaseConnection] - pub async fn from_sqlx_sqlite_pool(pool: SqlitePool) -> Result { - into_db_connection(pool).await + pub fn from_sqlx_sqlite_pool(pool: SqlitePool) -> DatabaseConnection { + DatabaseConnection::SqlxSqlitePoolConnection(SqlxSqlitePoolConnection { pool }) } } @@ -133,7 +133,7 @@ impl SqlxSqlitePoolConnection { /// Bundle a set of SQL statements that execute together. pub async fn begin(&self) -> Result { if let Ok(conn) = self.pool.acquire().await { - DatabaseTransaction::new_sqlite(conn, self.support_returning).await + DatabaseTransaction::new_sqlite(conn).await } else { Err(DbErr::Query( "Failed to acquire connection from pool.".to_owned(), @@ -152,7 +152,7 @@ impl SqlxSqlitePoolConnection { E: std::error::Error + Send, { if let Ok(conn) = self.pool.acquire().await { - let transaction = DatabaseTransaction::new_sqlite(conn, self.support_returning) + let transaction = DatabaseTransaction::new_sqlite(conn) .await .map_err(|e| TransactionError::Connection(e))?; transaction.run(callback).await @@ -187,45 +187,3 @@ pub(crate) fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, Sqlite, Sql } query } - -async fn into_db_connection(pool: SqlitePool) -> Result { - let support_returning = parse_support_returning(&pool).await?; - Ok(DatabaseConnection::SqlxSqlitePoolConnection( - SqlxSqlitePoolConnection { - pool, - support_returning, - }, - )) -} - -async fn parse_support_returning(pool: &SqlitePool) -> Result { - let stmt = Statement::from_string( - DbBackend::Sqlite, - r#"SELECT sqlite_version() AS version"#.to_owned(), - ); - let query = sqlx_query(&stmt); - let row = query - .fetch_one(pool) - .await - .map_err(sqlx_error_to_query_err)?; - let version: String = row.try_get("version").map_err(sqlx_error_to_query_err)?; - let regex = Regex::new(r"^(\d+)?.(\d+)?.(\*|\d+)").unwrap(); - let captures = regex.captures(&version).unwrap(); - macro_rules! parse_captures { - ( $idx: expr ) => { - captures.get($idx).map_or(0, |m| { - m.as_str() - .parse::() - .map_err(|e| DbErr::Conn(e.to_string())) - .unwrap() - }) - }; - } - let ver_major = parse_captures!(1); - let ver_minor = parse_captures!(2); - // Supported if it's version 3.35.0 (2021-03-12) or after - let support_returning = ver_major >= 3 && ver_minor >= 35; - debug_print!("db_version: {}", version); - debug_print!("db_support_returning: {}", support_returning); - Ok(support_returning) -} diff --git a/src/executor/insert.rs b/src/executor/insert.rs index fde1a3ab4..a6dbcbd53 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -41,7 +41,7 @@ where { // so that self is dropped before entering await let mut query = self.query; - if db.returning_on_insert() && ::PrimaryKey::iter().count() > 0 { + if db.support_returning() && ::PrimaryKey::iter().count() > 0 { let mut returning = Query::select(); returning.columns( ::PrimaryKey::iter().map(|c| c.into_column_ref()), @@ -113,7 +113,7 @@ where { type PrimaryKey = <::Entity as EntityTrait>::PrimaryKey; type ValueTypeOf = as PrimaryKeyTrait>::ValueType; - let last_insert_id_opt = match db.returning_on_insert() { + let last_insert_id_opt = match db.support_returning() { true => { let cols = PrimaryKey::::iter() .map(|col| col.to_string()) @@ -147,7 +147,7 @@ where A: ActiveModelTrait, { let db_backend = db.get_database_backend(); - let found = match db.returning_on_insert() { + let found = match db.support_returning() { true => { let mut returning = Query::select(); returning.exprs(::Column::iter().map(|c| { diff --git a/src/executor/update.rs b/src/executor/update.rs index d27aa41d4..9870b10d9 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -90,7 +90,7 @@ where A: ActiveModelTrait, C: ConnectionTrait<'a>, { - match db.returning_on_update() { + match db.support_returning() { true => { let mut returning = Query::select(); returning.exprs(::Column::iter().map(|c| { diff --git a/tests/returning_tests.rs b/tests/returning_tests.rs index 55506399f..7fa0447b4 100644 --- a/tests/returning_tests.rs +++ b/tests/returning_tests.rs @@ -1,8 +1,8 @@ pub mod common; pub use common::{bakery_chain::*, setup::*, TestContext}; -use sea_orm::{entity::prelude::*, *}; -use sea_query::Query; +pub use sea_orm::{entity::prelude::*, *}; +pub use sea_query::Query; #[sea_orm_macros::test] #[cfg(any( @@ -37,7 +37,7 @@ async fn main() -> Result<(), DbErr> { create_tables(db).await?; - if db.returning_on_insert() { + if db.support_returning() { insert.returning(returning.clone()); let insert_res = db .query_one(builder.build(&insert)) @@ -46,11 +46,7 @@ async fn main() -> Result<(), DbErr> { let _id: i32 = insert_res.try_get("", "id")?; let _name: String = insert_res.try_get("", "name")?; let _profit_margin: f64 = insert_res.try_get("", "profit_margin")?; - } else { - let insert_res = db.execute(builder.build(&insert)).await?; - assert!(insert_res.rows_affected() > 0); - } - if db.returning_on_update() { + update.returning(returning.clone()); let update_res = db .query_one(builder.build(&update)) @@ -60,6 +56,9 @@ async fn main() -> Result<(), DbErr> { let _name: String = update_res.try_get("", "name")?; let _profit_margin: f64 = update_res.try_get("", "profit_margin")?; } else { + let insert_res = db.execute(builder.build(&insert)).await?; + assert!(insert_res.rows_affected() > 0); + let update_res = db.execute(builder.build(&update)).await?; assert!(update_res.rows_affected() > 0); } From 257a893e1b1295de063dc94d1edf285fde00cde1 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 10 Nov 2021 15:28:43 +0800 Subject: [PATCH 24/31] Use `sea-query` master --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d6d41c35e..2345844cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ futures-util = { version = "^0.3" } log = { version = "^0.4", optional = true } rust_decimal = { version = "^1", optional = true } sea-orm-macros = { version = "^0.3.1", path = "sea-orm-macros", optional = true } -sea-query = { version = "^0.18.2", git = "https://github.com/SeaQL/sea-query.git", branch = "sea-orm/returning", features = ["thread-safe"] } +sea-query = { version = "^0.18.0", 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"] } serde_json = { version = "^1", optional = true } From 4d44827d2f32e2021478de01377723479e5c9d15 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 11 Nov 2021 13:30:36 +0800 Subject: [PATCH 25/31] Docs --- src/entity/base_entity.rs | 171 +++++++++++++++++++++++++++++++++++--- 1 file changed, 160 insertions(+), 11 deletions(-) diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index 93d8ac8ae..681cce680 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -272,7 +272,7 @@ pub trait EntityTrait: EntityName { /// Insert an model into database /// - /// # Example + /// # Example (Postgres) /// /// ``` /// # #[cfg(feature = "mock")] @@ -299,7 +299,6 @@ pub trait EntityTrait: EntityName { /// let insert_result = cake::Entity::insert(apple).exec(&db).await?; /// /// assert_eq!(insert_result.last_insert_id, 15); - /// // assert_eq!(insert_result.rows_affected, 1); /// # /// # Ok(()) /// # }); @@ -307,7 +306,49 @@ pub trait EntityTrait: EntityName { /// assert_eq!( /// db.into_transaction_log(), /// vec![Transaction::from_sql_and_values( - /// DbBackend::Postgres, r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id""#, vec!["Apple Pie".into()] + /// DbBackend::Postgres, + /// r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id""#, + /// vec!["Apple Pie".into()] + /// )]); + /// ``` + /// + /// # Example (MySQL) + /// + /// ``` + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend}; + /// # + /// # let db = MockDatabase::new(DbBackend::MySql) + /// # .append_exec_results(vec![ + /// # MockExecResult { + /// # last_insert_id: 15, + /// # rows_affected: 1, + /// # }, + /// # ]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; + /// + /// let apple = cake::ActiveModel { + /// name: Set("Apple Pie".to_owned()), + /// ..Default::default() + /// }; + /// + /// # let _: Result<(), DbErr> = smol::block_on(async { + /// # + /// let insert_result = cake::Entity::insert(apple).exec(&db).await?; + /// + /// assert_eq!(insert_result.last_insert_id, 15); + /// # + /// # Ok(()) + /// # }); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// DbBackend::MySql, + /// r#"INSERT INTO `cake` (`name`) VALUES (?)"#, + /// vec!["Apple Pie".into()] /// )]); /// ``` fn insert(model: A) -> Insert @@ -319,7 +360,7 @@ pub trait EntityTrait: EntityName { /// Insert many models into database /// - /// # Example + /// # Example (Postgres) /// /// ``` /// # #[cfg(feature = "mock")] @@ -350,7 +391,6 @@ pub trait EntityTrait: EntityName { /// let insert_result = cake::Entity::insert_many(vec![apple, orange]).exec(&db).await?; /// /// assert_eq!(insert_result.last_insert_id, 28); - /// // assert_eq!(insert_result.rows_affected, 2); /// # /// # Ok(()) /// # }); @@ -358,7 +398,52 @@ pub trait EntityTrait: EntityName { /// assert_eq!( /// db.into_transaction_log(), /// vec![Transaction::from_sql_and_values( - /// DbBackend::Postgres, r#"INSERT INTO "cake" ("name") VALUES ($1), ($2) RETURNING "id""#, + /// DbBackend::Postgres, + /// r#"INSERT INTO "cake" ("name") VALUES ($1), ($2) RETURNING "id""#, + /// vec!["Apple Pie".into(), "Orange Scone".into()] + /// )]); + /// ``` + /// + /// # Example (MySQL) + /// + /// ``` + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend}; + /// # + /// # let db = MockDatabase::new(DbBackend::MySql) + /// # .append_exec_results(vec![ + /// # MockExecResult { + /// # last_insert_id: 28, + /// # rows_affected: 2, + /// # }, + /// # ]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; + /// + /// let apple = cake::ActiveModel { + /// name: Set("Apple Pie".to_owned()), + /// ..Default::default() + /// }; + /// let orange = cake::ActiveModel { + /// name: Set("Orange Scone".to_owned()), + /// ..Default::default() + /// }; + /// + /// # let _: Result<(), DbErr> = smol::block_on(async { + /// # + /// let insert_result = cake::Entity::insert_many(vec![apple, orange]).exec(&db).await?; + /// + /// assert_eq!(insert_result.last_insert_id, 28); + /// # + /// # Ok(()) + /// # }); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// DbBackend::MySql, + /// r#"INSERT INTO `cake` (`name`) VALUES (?), (?)"#, /// vec!["Apple Pie".into(), "Orange Scone".into()] /// )]); /// ``` @@ -374,7 +459,7 @@ pub trait EntityTrait: EntityName { /// /// - To apply where conditions / filters, see [`QueryFilter`](crate::query::QueryFilter) /// - /// # Example + /// # Example (Postgres) /// /// ``` /// # #[cfg(feature = "mock")] @@ -413,10 +498,69 @@ 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 AND "fruit"."name" LIKE $3 RETURNING "id", "name", "cake_id""#, + /// DbBackend::Postgres, + /// r#"UPDATE "fruit" SET "name" = $1 WHERE "fruit"."id" = $2 AND "fruit"."name" LIKE $3 RETURNING "id", "name", "cake_id""#, /// vec!["Orange".into(), 1i32.into(), "%orange%".into()] /// )]); /// ``` + /// + /// # Example (MySQL) + /// + /// ``` + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend}; + /// # + /// # let db = MockDatabase::new(DbBackend::MySql) + /// # .append_query_results(vec![ + /// # vec![fruit::Model { + /// # id: 1, + /// # name: "Orange".to_owned(), + /// # cake_id: None, + /// # }], + /// # ]) + /// # .append_exec_results(vec![ + /// # MockExecResult { + /// # last_insert_id: 0, + /// # rows_affected: 1, + /// # }, + /// # ]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::fruit}; + /// + /// let orange = fruit::ActiveModel { + /// id: Set(1), + /// name: Set("Orange".to_owned()), + /// ..Default::default() + /// }; + /// + /// # let _: Result<(), DbErr> = smol::block_on(async { + /// # + /// assert_eq!( + /// fruit::Entity::update(orange.clone()) + /// .filter(fruit::Column::Name.contains("orange")) + /// .exec(&db) + /// .await?, + /// orange + /// ); + /// # + /// # Ok(()) + /// # }); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![ + /// Transaction::from_sql_and_values( + /// DbBackend::MySql, + /// r#"UPDATE `fruit` SET `name` = ? WHERE `fruit`.`id` = ? AND `fruit`.`name` LIKE ?"#, + /// vec!["Orange".into(), 1i32.into(), "%orange%".into()] + /// ), + /// Transaction::from_sql_and_values( + /// DbBackend::MySql, + /// r#"SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = ? LIMIT ?"#, + /// vec![1i32.into(), 1u64.into()] + /// )]); + /// ``` fn update(model: A) -> UpdateOne where A: ActiveModelTrait, @@ -461,7 +605,9 @@ pub trait EntityTrait: EntityName { /// assert_eq!( /// db.into_transaction_log(), /// vec![Transaction::from_sql_and_values( - /// DbBackend::Postgres, r#"UPDATE "fruit" SET "cake_id" = $1 WHERE "fruit"."name" LIKE $2"#, vec![Value::Int(None), "%Apple%".into()] + /// DbBackend::Postgres, + /// r#"UPDATE "fruit" SET "cake_id" = $1 WHERE "fruit"."name" LIKE $2"#, + /// vec![Value::Int(None), "%Apple%".into()] /// )]); /// ``` fn update_many() -> UpdateMany { @@ -506,7 +652,8 @@ pub trait EntityTrait: EntityName { /// assert_eq!( /// db.into_transaction_log(), /// vec![Transaction::from_sql_and_values( - /// DbBackend::Postgres, r#"DELETE FROM "fruit" WHERE "fruit"."id" = $1"#, vec![3i32.into()] + /// DbBackend::Postgres, r#"DELETE FROM "fruit" WHERE "fruit"."id" = $1"#, + /// vec![3i32.into()] /// )]); /// ``` fn delete(model: A) -> DeleteOne @@ -552,7 +699,9 @@ pub trait EntityTrait: EntityName { /// assert_eq!( /// db.into_transaction_log(), /// vec![Transaction::from_sql_and_values( - /// DbBackend::Postgres, r#"DELETE FROM "fruit" WHERE "fruit"."name" LIKE $1"#, vec!["%Apple%".into()] + /// DbBackend::Postgres, + /// r#"DELETE FROM "fruit" WHERE "fruit"."name" LIKE $1"#, + /// vec!["%Apple%".into()] /// )]); /// ``` fn delete_many() -> DeleteMany { From d5de8b1c4842b4d1097f6871f3af6e655a0dd48d Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 16 Nov 2021 15:38:22 +0800 Subject: [PATCH 26/31] Should fail --- src/entity/base_entity.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index 8ce8060a0..1cefda1a7 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -298,7 +298,8 @@ pub trait EntityTrait: EntityName { /// # /// let insert_result = cake::Entity::insert(apple).exec(&db).await?; /// - /// assert_eq!(insert_result.last_insert_id, 15); + /// assert_eq!(dbg!(insert_result.last_insert_id), 150); + /// assert!(false); /// # /// # Ok(()) /// # }); From 9655805316c8f32ee3015838263c5d40792c4f90 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 16 Nov 2021 15:54:17 +0800 Subject: [PATCH 27/31] Will fail, as expected --- src/entity/base_entity.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index 1cefda1a7..603696233 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -275,8 +275,11 @@ pub trait EntityTrait: EntityName { /// # Example (Postgres) /// /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_exec_results(vec![ @@ -294,15 +297,10 @@ pub trait EntityTrait: EntityName { /// ..Default::default() /// }; /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// let insert_result = cake::Entity::insert(apple).exec(&db).await?; /// /// assert_eq!(dbg!(insert_result.last_insert_id), 150); /// assert!(false); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -311,6 +309,9 @@ pub trait EntityTrait: EntityName { /// r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id""#, /// vec!["Apple Pie".into()] /// )]); + /// # + /// # Ok(()) + /// # } /// ``` /// /// # Example (MySQL) From 4c147a2d24dcbede4a2dac598dedfda43ea45266 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 16 Nov 2021 16:27:54 +0800 Subject: [PATCH 28/31] Rewrite doctests --- src/docs.rs | 11 +- src/entity/active_model.rs | 253 +++++++++++++++++++++++++++++++++++++ src/entity/base_entity.rs | 143 +++++++++++---------- src/entity/model.rs | 12 +- src/executor/paginator.rs | 18 ++- src/executor/query.rs | 13 +- src/executor/select.rs | 78 ++++++------ 7 files changed, 406 insertions(+), 122 deletions(-) diff --git a/src/docs.rs b/src/docs.rs index 4d1226c3a..3870e716f 100644 --- a/src/docs.rs +++ b/src/docs.rs @@ -3,7 +3,12 @@ //! 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}; +//! # use sea_orm::{error::*, tests_cfg::*, *}; +//! # +//! # #[smol_potat::main] +//! # #[cfg(feature = "mock")] +//! # pub async fn main() -> Result<(), DbErr> { +//! # //! # let db = MockDatabase::new(DbBackend::Postgres) //! # .append_query_results(vec![ //! # vec![cake::Model { @@ -19,7 +24,7 @@ //! # .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))?; @@ -53,7 +58,7 @@ //! # ] //! # ); //! # Ok(()) -//! # }); +//! # } //! ``` //! //! 2. Dynamic diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index efb234ea1..c7d26574a 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -140,6 +140,106 @@ pub trait ActiveModelTrait: Clone + Debug { } /// Perform an `INSERT` operation on the ActiveModel + /// + /// # Example (Postgres) + /// + /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] + /// # #[cfg(feature = "mock")] + /// # pub async fn main() -> Result<(), DbErr> { + /// # + /// # let db = MockDatabase::new(DbBackend::Postgres) + /// # .append_exec_results(vec![ + /// # MockExecResult { + /// # last_insert_id: 15, + /// # rows_affected: 1, + /// # }, + /// # ]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; + /// + /// let apple = cake::ActiveModel { + /// name: Set("Apple Pie".to_owned()), + /// ..Default::default() + /// }; + /// + /// assert_eq!( + /// apple + /// .insert(&db) + /// .await?, + /// cake::ActiveModel { + /// id: Set(150), + /// name: Set("Apple Pie".to_owned()), + /// } + /// ); + /// assert!(false); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id", "name""#, + /// vec!["Apple Pie".into()] + /// )]); + /// # + /// # Ok(()) + /// # } + /// ``` + /// + /// # Example (MySQL) + /// + /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] + /// # #[cfg(feature = "mock")] + /// # pub async fn main() -> Result<(), DbErr> { + /// # + /// # let db = MockDatabase::new(DbBackend::MySql) + /// # .append_exec_results(vec![ + /// # MockExecResult { + /// # last_insert_id: 15, + /// # rows_affected: 1, + /// # }, + /// # ]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; + /// + /// let apple = cake::ActiveModel { + /// name: Set("Apple Pie".to_owned()), + /// ..Default::default() + /// }; + /// + /// assert_eq!( + /// apple + /// .insert(&db) + /// .await?, + /// cake::ActiveModel { + /// id: Set(150), + /// name: Set("Apple Pie".to_owned()), + /// } + /// ); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![ + /// Transaction::from_sql_and_values( + /// DbBackend::MySql, + /// r#"INSERT INTO `cake` (`name`) VALUES (?)"#, + /// vec!["Apple Pie".into()] + /// ), + /// Transaction::from_sql_and_values( + /// DbBackend::MySql, + /// r#"SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = ? LIMIT ?"#, + /// vec![15.into(), 1u64.into()])]); + /// # + /// # Ok(()) + /// # } + /// ``` async fn insert<'a, C>(self, db: &'a C) -> Result where ::Model: IntoActiveModel, @@ -154,6 +254,117 @@ pub trait ActiveModelTrait: Clone + Debug { } /// Perform the `UPDATE` operation on an ActiveModel + /// + /// # Example (Postgres) + /// + /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] + /// # #[cfg(feature = "mock")] + /// # pub async fn main() -> Result<(), DbErr> { + /// # + /// # let db = MockDatabase::new(DbBackend::Postgres) + /// # .append_exec_results(vec![ + /// # MockExecResult { + /// # last_insert_id: 0, + /// # rows_affected: 1, + /// # }, + /// # ]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::fruit}; + /// + /// let orange = fruit::ActiveModel { + /// id: Set(1), + /// name: Set("Orange".to_owned()), + /// ..Default::default() + /// }; + /// + /// assert_eq!( + /// orange + /// .update(&db) + /// .await?, + /// fruit::ActiveModel { + /// id: Set(1), + /// name: Set("Orange".to_owned()), + /// cake_id: Set(None), + /// } + /// ); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"UPDATE "fruit" SET "name" = $1 WHERE "fruit"."id" = $2 RETURNING "id", "name", "cake_id""#, + /// vec!["Orange".into(), 1i32.into()] + /// )]); + /// # + /// # Ok(()) + /// # } + /// ``` + /// + /// # Example (MySQL) + /// + /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] + /// # #[cfg(feature = "mock")] + /// # pub async fn main() -> Result<(), DbErr> { + /// # + /// # let db = MockDatabase::new(DbBackend::MySql) + /// # .append_query_results(vec![ + /// # vec![fruit::Model { + /// # id: 1, + /// # name: "Orange".to_owned(), + /// # cake_id: None, + /// # }], + /// # ]) + /// # .append_exec_results(vec![ + /// # MockExecResult { + /// # last_insert_id: 0, + /// # rows_affected: 1, + /// # }, + /// # ]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::fruit}; + /// + /// let orange = fruit::ActiveModel { + /// id: Set(1), + /// name: Set("Orange".to_owned()), + /// ..Default::default() + /// }; + /// + /// assert_eq!( + /// orange + /// .update(&db) + /// .await?, + /// fruit::ActiveModel { + /// id: Set(1), + /// name: Set("Orange".to_owned()), + /// cake_id: Set(None), + /// } + /// ); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![ + /// Transaction::from_sql_and_values( + /// DbBackend::MySql, + /// r#"UPDATE `fruit` SET `name` = ? WHERE `fruit`.`id` = ?"#, + /// vec!["Orange".into(), 1i32.into()] + /// ), + /// Transaction::from_sql_and_values( + /// DbBackend::MySql, + /// r#"SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = ? LIMIT ?"#, + /// vec![1i32.into(), 1u64.into()] + /// )]); + /// # + /// # Ok(()) + /// # } + /// ``` async fn update<'a, C>(self, db: &'a C) -> Result where ::Model: IntoActiveModel, @@ -191,6 +402,48 @@ pub trait ActiveModelTrait: Clone + Debug { } /// Delete an active model by its primary key + /// + /// # Example + /// + /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] + /// # #[cfg(feature = "mock")] + /// # pub async fn main() -> Result<(), DbErr> { + /// # + /// # let db = MockDatabase::new(DbBackend::Postgres) + /// # .append_exec_results(vec![ + /// # MockExecResult { + /// # last_insert_id: 0, + /// # rows_affected: 1, + /// # }, + /// # ]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::fruit}; + /// + /// let orange = fruit::ActiveModel { + /// id: Set(3), + /// ..Default::default() + /// }; + /// + /// let delete_result = orange.delete(&db).await?; + /// + /// assert_eq!(delete_result.rows_affected, 1); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"DELETE FROM "fruit" WHERE "fruit"."id" = $1"#, + /// vec![3i32.into()] + /// )] + /// ); + /// # + /// # Ok(()) + /// # } + /// ``` async fn delete<'a, C>(self, db: &'a C) -> Result where Self: ActiveModelBehavior + 'a, diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index 603696233..8bbb6195d 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -95,8 +95,11 @@ pub trait EntityTrait: EntityName { /// # Example /// /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![ @@ -121,8 +124,6 @@ pub trait EntityTrait: EntityName { /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// assert_eq!( /// cake::Entity::find().one(&db).await?, /// Some(cake::Model { @@ -144,9 +145,6 @@ pub trait EntityTrait: EntityName { /// }, /// ] /// ); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -163,6 +161,9 @@ pub trait EntityTrait: EntityName { /// ), /// ] /// ); + /// # + /// # Ok(()) + /// # } /// ``` fn find() -> Select { Select::new() @@ -173,8 +174,11 @@ pub trait EntityTrait: EntityName { /// # Example /// /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![ @@ -189,8 +193,6 @@ pub trait EntityTrait: EntityName { /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// assert_eq!( /// cake::Entity::find_by_id(11).all(&db).await?, /// vec![cake::Model { @@ -198,9 +200,6 @@ pub trait EntityTrait: EntityName { /// name: "Sponge Cake".to_owned(), /// }] /// ); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -210,11 +209,17 @@ pub trait EntityTrait: EntityName { /// vec![11i32.into()] /// )] /// ); + /// # + /// # Ok(()) + /// # } /// ``` /// Find by composite key /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![ @@ -229,8 +234,6 @@ pub trait EntityTrait: EntityName { /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake_filling}; /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// assert_eq!( /// cake_filling::Entity::find_by_id((2, 3)).all(&db).await?, /// vec![cake_filling::Model { @@ -238,9 +241,6 @@ pub trait EntityTrait: EntityName { /// filling_id: 3, /// }] /// ); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -252,6 +252,9 @@ pub trait EntityTrait: EntityName { /// ].join(" ").as_str(), /// vec![2i32.into(), 3i32.into()] /// )]); + /// # + /// # Ok(()) + /// # } /// ``` fn find_by_id(values: ::ValueType) -> Select { let mut select = Self::find(); @@ -317,8 +320,11 @@ pub trait EntityTrait: EntityName { /// # Example (MySQL) /// /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::MySql) /// # .append_exec_results(vec![ @@ -336,14 +342,9 @@ pub trait EntityTrait: EntityName { /// ..Default::default() /// }; /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// let insert_result = cake::Entity::insert(apple).exec(&db).await?; /// /// assert_eq!(insert_result.last_insert_id, 15); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -352,6 +353,9 @@ pub trait EntityTrait: EntityName { /// r#"INSERT INTO `cake` (`name`) VALUES (?)"#, /// vec!["Apple Pie".into()] /// )]); + /// # + /// # Ok(()) + /// # } /// ``` fn insert(model: A) -> Insert where @@ -365,8 +369,11 @@ pub trait EntityTrait: EntityName { /// # Example (Postgres) /// /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_exec_results(vec![ @@ -388,14 +395,9 @@ pub trait EntityTrait: EntityName { /// ..Default::default() /// }; /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// let insert_result = cake::Entity::insert_many(vec![apple, orange]).exec(&db).await?; /// /// assert_eq!(insert_result.last_insert_id, 28); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -404,13 +406,19 @@ pub trait EntityTrait: EntityName { /// r#"INSERT INTO "cake" ("name") VALUES ($1), ($2) RETURNING "id""#, /// vec!["Apple Pie".into(), "Orange Scone".into()] /// )]); + /// # + /// # Ok(()) + /// # } /// ``` /// /// # Example (MySQL) /// /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::MySql) /// # .append_exec_results(vec![ @@ -432,14 +440,9 @@ pub trait EntityTrait: EntityName { /// ..Default::default() /// }; /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// let insert_result = cake::Entity::insert_many(vec![apple, orange]).exec(&db).await?; /// /// assert_eq!(insert_result.last_insert_id, 28); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -448,6 +451,9 @@ pub trait EntityTrait: EntityName { /// r#"INSERT INTO `cake` (`name`) VALUES (?), (?)"#, /// vec!["Apple Pie".into(), "Orange Scone".into()] /// )]); + /// # + /// # Ok(()) + /// # } /// ``` fn insert_many(models: I) -> Insert where @@ -464,8 +470,11 @@ pub trait EntityTrait: EntityName { /// # Example (Postgres) /// /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_exec_results(vec![ @@ -484,8 +493,6 @@ pub trait EntityTrait: EntityName { /// ..Default::default() /// }; /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// assert_eq!( /// fruit::Entity::update(orange.clone()) /// .filter(fruit::Column::Name.contains("orange")) @@ -493,9 +500,6 @@ pub trait EntityTrait: EntityName { /// .await?, /// orange /// ); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -504,13 +508,19 @@ pub trait EntityTrait: EntityName { /// r#"UPDATE "fruit" SET "name" = $1 WHERE "fruit"."id" = $2 AND "fruit"."name" LIKE $3 RETURNING "id", "name", "cake_id""#, /// vec!["Orange".into(), 1i32.into(), "%orange%".into()] /// )]); + /// # + /// # Ok(()) + /// # } /// ``` /// /// # Example (MySQL) /// /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::MySql) /// # .append_query_results(vec![ @@ -536,8 +546,6 @@ pub trait EntityTrait: EntityName { /// ..Default::default() /// }; /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// assert_eq!( /// fruit::Entity::update(orange.clone()) /// .filter(fruit::Column::Name.contains("orange")) @@ -545,9 +553,6 @@ pub trait EntityTrait: EntityName { /// .await?, /// orange /// ); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -562,6 +567,9 @@ pub trait EntityTrait: EntityName { /// r#"SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = ? LIMIT ?"#, /// vec![1i32.into(), 1u64.into()] /// )]); + /// # + /// # Ok(()) + /// # } /// ``` fn update(model: A) -> UpdateOne where @@ -577,8 +585,11 @@ pub trait EntityTrait: EntityName { /// # Example /// /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_exec_results(vec![ @@ -591,8 +602,6 @@ pub trait EntityTrait: EntityName { /// # /// use sea_orm::{entity::*, query::*, tests_cfg::fruit, sea_query::{Expr, Value}}; /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// let update_result = fruit::Entity::update_many() /// .col_expr(fruit::Column::CakeId, Expr::value(Value::Int(None))) /// .filter(fruit::Column::Name.contains("Apple")) @@ -600,9 +609,6 @@ pub trait EntityTrait: EntityName { /// .await?; /// /// assert_eq!(update_result.rows_affected, 5); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -611,6 +617,9 @@ pub trait EntityTrait: EntityName { /// r#"UPDATE "fruit" SET "cake_id" = $1 WHERE "fruit"."name" LIKE $2"#, /// vec![Value::Int(None), "%Apple%".into()] /// )]); + /// # + /// # Ok(()) + /// # } /// ``` fn update_many() -> UpdateMany { Update::many(Self::default()) @@ -623,8 +632,11 @@ pub trait EntityTrait: EntityName { /// # Example /// /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_exec_results(vec![ @@ -642,14 +654,9 @@ pub trait EntityTrait: EntityName { /// ..Default::default() /// }; /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// let delete_result = fruit::Entity::delete(orange).exec(&db).await?; /// /// assert_eq!(delete_result.rows_affected, 1); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -657,6 +664,9 @@ pub trait EntityTrait: EntityName { /// DbBackend::Postgres, r#"DELETE FROM "fruit" WHERE "fruit"."id" = $1"#, /// vec![3i32.into()] /// )]); + /// # + /// # Ok(()) + /// # } /// ``` fn delete(model: A) -> DeleteOne where @@ -672,8 +682,11 @@ pub trait EntityTrait: EntityName { /// # Example /// /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{entity::*, error::*, query::*, tests_cfg::*, MockDatabase, MockExecResult, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_exec_results(vec![ @@ -686,17 +699,12 @@ pub trait EntityTrait: EntityName { /// # /// use sea_orm::{entity::*, query::*, tests_cfg::fruit}; /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// let delete_result = fruit::Entity::delete_many() /// .filter(fruit::Column::Name.contains("Apple")) /// .exec(&db) /// .await?; /// /// assert_eq!(delete_result.rows_affected, 5); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -705,6 +713,9 @@ pub trait EntityTrait: EntityName { /// r#"DELETE FROM "fruit" WHERE "fruit"."name" LIKE $1"#, /// vec!["%Apple%".into()] /// )]); + /// # + /// # Ok(()) + /// # } /// ``` fn delete_many() -> DeleteMany { Delete::many(Self::default()) diff --git a/src/entity/model.rs b/src/entity/model.rs index b11a700ba..acea83d63 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -47,8 +47,11 @@ pub trait FromQueryResult: Sized { } /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![vec![ @@ -67,8 +70,6 @@ pub trait FromQueryResult: Sized { /// num_of_cakes: i32, /// } /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// let res: Vec = SelectResult::find_by_statement(Statement::from_sql_and_values( /// DbBackend::Postgres, /// r#"SELECT "name", COUNT(*) AS "num_of_cakes" FROM "cake" GROUP BY("name")"#, @@ -85,8 +86,6 @@ pub trait FromQueryResult: Sized { /// },] /// ); /// # - /// # Ok(()) - /// # }); /// # assert_eq!( /// # db.into_transaction_log(), /// # vec![Transaction::from_sql_and_values( @@ -95,6 +94,9 @@ pub trait FromQueryResult: Sized { /// # vec![] /// # ),] /// # ); + /// # + /// # Ok(()) + /// # } /// ``` fn find_by_statement(stmt: Statement) -> SelectorRaw> { SelectorRaw::>::from_statement(stmt) diff --git a/src/executor/paginator.rs b/src/executor/paginator.rs index f52b3bd86..47548a0af 100644 --- a/src/executor/paginator.rs +++ b/src/executor/paginator.rs @@ -97,11 +97,14 @@ where /// Fetch one page and increment the page counter /// /// ```rust + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, MockDatabase, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { + /// # /// # let owned_db = MockDatabase::new(DbBackend::Postgres).into_connection(); /// # let db = &owned_db; - /// # let _: Result<(), DbErr> = smol::block_on(async { /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// let mut cake_pages = cake::Entity::find() @@ -113,7 +116,7 @@ where /// } /// # /// # Ok(()) - /// # }); + /// # } /// ``` pub async fn fetch_and_next(&mut self) -> Result>, DbErr> { let vec = self.fetch().await?; @@ -125,11 +128,14 @@ where /// Convert self into an async stream /// /// ```rust + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, MockDatabase, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { + /// # /// # let owned_db = MockDatabase::new(DbBackend::Postgres).into_connection(); /// # let db = &owned_db; - /// # let _: Result<(), DbErr> = smol::block_on(async { /// # /// use futures::TryStreamExt; /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; @@ -143,7 +149,7 @@ where /// } /// # /// # Ok(()) - /// # }); + /// # } /// ``` pub fn into_stream(mut self) -> PinBoxStream<'db, Result, DbErr>> { Box::pin(stream! { diff --git a/src/executor/query.rs b/src/executor/query.rs index 3c1a9184d..12d0c7cfa 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -320,8 +320,11 @@ pub trait TryGetableMany: Sized { fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result; /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(all(feature = "mock", feature = "macros"))] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![vec![ @@ -344,8 +347,6 @@ pub trait TryGetableMany: Sized { /// NumOfCakes, /// } /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// let res: Vec<(String, i32)> = /// <(String, i32)>::find_by_statement::(Statement::from_sql_and_values( /// DbBackend::Postgres, @@ -362,9 +363,6 @@ pub trait TryGetableMany: Sized { /// ("New York Cheese".to_owned(), 1), /// ] /// ); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -374,6 +372,9 @@ pub trait TryGetableMany: Sized { /// vec![] /// ),] /// ); + /// # + /// # Ok(()) + /// # } /// ``` fn find_by_statement(stmt: Statement) -> SelectorRaw> where diff --git a/src/executor/select.rs b/src/executor/select.rs index 9fb906cf6..3a08392e0 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -143,8 +143,11 @@ where } /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(all(feature = "mock", feature = "macros"))] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![vec![ @@ -164,8 +167,6 @@ where /// CakeName, /// } /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// let res: Vec = cake::Entity::find() /// .select_only() /// .column_as(cake::Column::Name, QueryAs::CakeName) @@ -177,9 +178,6 @@ where /// res, /// vec!["Chocolate Forest".to_owned(), "New York Cheese".to_owned()] /// ); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -189,11 +187,17 @@ where /// vec![] /// )] /// ); + /// # + /// # Ok(()) + /// # } /// ``` /// /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(all(feature = "mock", feature = "macros"))] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![vec![ @@ -212,8 +216,6 @@ where /// NumOfCakes, /// } /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// let res: Vec<(String, i64)> = cake::Entity::find() /// .select_only() /// .column_as(cake::Column::Name, QueryAs::CakeName) @@ -224,9 +226,6 @@ where /// .await?; /// /// assert_eq!(res, vec![("Chocolate Forest".to_owned(), 2i64)]); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -241,6 +240,9 @@ where /// vec![] /// )] /// ); + /// # + /// # Ok(()) + /// # } /// ``` pub fn into_values(self) -> Selector> where @@ -490,8 +492,11 @@ where } /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![vec![ @@ -514,8 +519,6 @@ where /// num_of_cakes: i32, /// } /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// let res: Vec = cake::Entity::find() /// .from_raw_sql(Statement::from_sql_and_values( /// DbBackend::Postgres, @@ -539,9 +542,6 @@ where /// }, /// ] /// ); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -551,6 +551,9 @@ where /// vec![] /// ),] /// ); + /// # + /// # Ok(()) + /// # } /// ``` pub fn into_model(self) -> SelectorRaw> where @@ -563,8 +566,11 @@ where } /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) /// # .append_query_results(vec![vec![ @@ -581,8 +587,6 @@ where /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// - /// # 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"."id", "cake"."name" FROM "cake""#, vec![] @@ -605,9 +609,6 @@ where /// }), /// ] /// ); - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -616,6 +617,9 @@ where /// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, vec![] /// ), /// ]); + /// # + /// # Ok(()) + /// # } /// ``` #[cfg(feature = "with-json")] pub fn into_json(self) -> SelectorRaw> { @@ -627,15 +631,16 @@ where /// Get an item from the Select query /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres).into_connection(); /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// let _: Option = cake::Entity::find() /// .from_raw_sql(Statement::from_sql_and_values( /// DbBackend::Postgres, @@ -644,9 +649,6 @@ where /// )) /// .one(&db) /// .await?; - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -656,6 +658,9 @@ where /// vec![1.into()] /// ),] /// ); + /// # + /// # Ok(()) + /// # } /// ``` pub async fn one<'a, C>(self, db: &C) -> Result, DbErr> where @@ -670,15 +675,16 @@ where /// Get all items from the Select query /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] - /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres).into_connection(); /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # /// let _: Vec = cake::Entity::find() /// .from_raw_sql(Statement::from_sql_and_values( /// DbBackend::Postgres, @@ -687,9 +693,6 @@ where /// )) /// .all(&db) /// .await?; - /// # - /// # Ok(()) - /// # }); /// /// assert_eq!( /// db.into_transaction_log(), @@ -699,6 +702,9 @@ where /// vec![] /// ),] /// ); + /// # + /// # Ok(()) + /// # } /// ``` pub async fn all<'a, C>(self, db: &C) -> Result, DbErr> where From f9d04fc73feae198e0e821b318be571f96e29bd2 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 16 Nov 2021 17:21:44 +0800 Subject: [PATCH 29/31] Hotfix - separate counter for mock exec & query --- src/driver/mock.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/driver/mock.rs b/src/driver/mock.rs index c0ffa0a23..cdded50c8 100644 --- a/src/driver/mock.rs +++ b/src/driver/mock.rs @@ -19,7 +19,8 @@ pub struct MockDatabaseConnector; /// Defines a connection for the [MockDatabase] #[derive(Debug)] pub struct MockDatabaseConnection { - counter: AtomicUsize, + execute_counter: AtomicUsize, + query_counter: AtomicUsize, mocker: Mutex>, } @@ -100,7 +101,8 @@ impl MockDatabaseConnection { M: MockDatabaseTrait, { Self { - counter: AtomicUsize::new(0), + execute_counter: AtomicUsize::new(0), + query_counter: AtomicUsize::new(0), mocker: Mutex::new(Box::new(m)), } } @@ -117,14 +119,14 @@ impl MockDatabaseConnection { /// Execute the SQL statement in the [MockDatabase] pub fn execute(&self, statement: Statement) -> Result { debug_print!("{}", statement); - let counter = self.counter.fetch_add(1, Ordering::SeqCst); + let counter = self.execute_counter.fetch_add(1, Ordering::SeqCst); self.mocker.lock().unwrap().execute(counter, statement) } /// Return one [QueryResult] if the query was successful pub fn query_one(&self, statement: Statement) -> Result, DbErr> { debug_print!("{}", statement); - let counter = self.counter.fetch_add(1, Ordering::SeqCst); + let counter = self.query_counter.fetch_add(1, Ordering::SeqCst); let result = self.mocker.lock().unwrap().query(counter, statement)?; Ok(result.into_iter().next()) } @@ -132,7 +134,7 @@ impl MockDatabaseConnection { /// Return all [QueryResult]s if the query was successful pub fn query_all(&self, statement: Statement) -> Result, DbErr> { debug_print!("{}", statement); - let counter = self.counter.fetch_add(1, Ordering::SeqCst); + let counter = self.query_counter.fetch_add(1, Ordering::SeqCst); self.mocker.lock().unwrap().query(counter, statement) } From 7298fdeda9ef71a661804eab44ac21793f549a37 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 16 Nov 2021 17:35:49 +0800 Subject: [PATCH 30/31] Rewrite doctests --- src/entity/active_model.rs | 84 ++++++++++++++++-------------- src/entity/base_entity.rs | 103 +++++++++++++++++++++++-------------- src/executor/paginator.rs | 24 +++++++-- src/executor/select.rs | 18 ++++++- 4 files changed, 146 insertions(+), 83 deletions(-) diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index c7d26574a..06e01fcbe 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -151,11 +151,11 @@ pub trait ActiveModelTrait: Clone + Debug { /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) - /// # .append_exec_results(vec![ - /// # MockExecResult { - /// # last_insert_id: 15, - /// # rows_affected: 1, - /// # }, + /// # .append_query_results(vec![ + /// # vec![cake::Model { + /// # id: 15, + /// # name: "Apple Pie".to_owned(), + /// # }], /// # ]) /// # .into_connection(); /// # @@ -167,15 +167,13 @@ pub trait ActiveModelTrait: Clone + Debug { /// }; /// /// assert_eq!( - /// apple - /// .insert(&db) - /// .await?, - /// cake::ActiveModel { - /// id: Set(150), - /// name: Set("Apple Pie".to_owned()), + /// apple.insert(&db).await?, + /// cake::Model { + /// id: 15, + /// name: "Apple Pie".to_owned(), /// } + /// .into_active_model() /// ); - /// assert!(false); /// /// assert_eq!( /// db.into_transaction_log(), @@ -183,7 +181,8 @@ pub trait ActiveModelTrait: Clone + Debug { /// DbBackend::Postgres, /// r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id", "name""#, /// vec!["Apple Pie".into()] - /// )]); + /// )] + /// ); /// # /// # Ok(()) /// # } @@ -199,6 +198,12 @@ pub trait ActiveModelTrait: Clone + Debug { /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::MySql) + /// # .append_query_results(vec![ + /// # vec![cake::Model { + /// # id: 15, + /// # name: "Apple Pie".to_owned(), + /// # }], + /// # ]) /// # .append_exec_results(vec![ /// # MockExecResult { /// # last_insert_id: 15, @@ -215,13 +220,12 @@ pub trait ActiveModelTrait: Clone + Debug { /// }; /// /// assert_eq!( - /// apple - /// .insert(&db) - /// .await?, - /// cake::ActiveModel { - /// id: Set(150), - /// name: Set("Apple Pie".to_owned()), + /// apple.insert(&db).await?, + /// cake::Model { + /// id: 15, + /// name: "Apple Pie".to_owned(), /// } + /// .into_active_model() /// ); /// /// assert_eq!( @@ -235,7 +239,10 @@ pub trait ActiveModelTrait: Clone + Debug { /// Transaction::from_sql_and_values( /// DbBackend::MySql, /// r#"SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = ? LIMIT ?"#, - /// vec![15.into(), 1u64.into()])]); + /// vec![15.into(), 1u64.into()] + /// ) + /// ] + /// ); /// # /// # Ok(()) /// # } @@ -265,11 +272,12 @@ pub trait ActiveModelTrait: Clone + Debug { /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) - /// # .append_exec_results(vec![ - /// # MockExecResult { - /// # last_insert_id: 0, - /// # rows_affected: 1, - /// # }, + /// # .append_query_results(vec![ + /// # vec![fruit::Model { + /// # id: 1, + /// # name: "Orange".to_owned(), + /// # cake_id: None, + /// # }], /// # ]) /// # .into_connection(); /// # @@ -282,14 +290,13 @@ pub trait ActiveModelTrait: Clone + Debug { /// }; /// /// assert_eq!( - /// orange - /// .update(&db) - /// .await?, - /// fruit::ActiveModel { - /// id: Set(1), - /// name: Set("Orange".to_owned()), - /// cake_id: Set(None), + /// orange.update(&db).await?, + /// fruit::Model { + /// id: 1, + /// name: "Orange".to_owned(), + /// cake_id: None, /// } + /// .into_active_model() /// ); /// /// assert_eq!( @@ -338,14 +345,13 @@ pub trait ActiveModelTrait: Clone + Debug { /// }; /// /// assert_eq!( - /// orange - /// .update(&db) - /// .await?, - /// fruit::ActiveModel { - /// id: Set(1), - /// name: Set("Orange".to_owned()), - /// cake_id: Set(None), + /// orange.update(&db).await?, + /// fruit::Model { + /// id: 1, + /// name: "Orange".to_owned(), + /// cake_id: None, /// } + /// .into_active_model() /// ); /// /// assert_eq!( diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index 8bbb6195d..e6247d352 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -285,12 +285,9 @@ pub trait EntityTrait: EntityName { /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) - /// # .append_exec_results(vec![ - /// # MockExecResult { - /// # last_insert_id: 15, - /// # rows_affected: 1, - /// # }, - /// # ]) + /// # .append_query_results(vec![vec![maplit::btreemap! { + /// # "id" => Into::::into(15), + /// # }]]) /// # .into_connection(); /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; @@ -302,8 +299,7 @@ pub trait EntityTrait: EntityName { /// /// let insert_result = cake::Entity::insert(apple).exec(&db).await?; /// - /// assert_eq!(dbg!(insert_result.last_insert_id), 150); - /// assert!(false); + /// assert_eq!(dbg!(insert_result.last_insert_id), 15); /// /// assert_eq!( /// db.into_transaction_log(), @@ -311,7 +307,8 @@ pub trait EntityTrait: EntityName { /// DbBackend::Postgres, /// r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id""#, /// vec!["Apple Pie".into()] - /// )]); + /// )] + /// ); /// # /// # Ok(()) /// # } @@ -352,7 +349,8 @@ pub trait EntityTrait: EntityName { /// DbBackend::MySql, /// r#"INSERT INTO `cake` (`name`) VALUES (?)"#, /// vec!["Apple Pie".into()] - /// )]); + /// )] + /// ); /// # /// # Ok(()) /// # } @@ -376,12 +374,9 @@ pub trait EntityTrait: EntityName { /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) - /// # .append_exec_results(vec![ - /// # MockExecResult { - /// # last_insert_id: 28, - /// # rows_affected: 2, - /// # }, - /// # ]) + /// # .append_query_results(vec![vec![maplit::btreemap! { + /// # "id" => Into::::into(28), + /// # }]]) /// # .into_connection(); /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; @@ -395,7 +390,9 @@ pub trait EntityTrait: EntityName { /// ..Default::default() /// }; /// - /// let insert_result = cake::Entity::insert_many(vec![apple, orange]).exec(&db).await?; + /// let insert_result = cake::Entity::insert_many(vec![apple, orange]) + /// .exec(&db) + /// .await?; /// /// assert_eq!(insert_result.last_insert_id, 28); /// @@ -405,7 +402,8 @@ pub trait EntityTrait: EntityName { /// DbBackend::Postgres, /// r#"INSERT INTO "cake" ("name") VALUES ($1), ($2) RETURNING "id""#, /// vec!["Apple Pie".into(), "Orange Scone".into()] - /// )]); + /// )] + /// ); /// # /// # Ok(()) /// # } @@ -440,7 +438,9 @@ pub trait EntityTrait: EntityName { /// ..Default::default() /// }; /// - /// let insert_result = cake::Entity::insert_many(vec![apple, orange]).exec(&db).await?; + /// let insert_result = cake::Entity::insert_many(vec![apple, orange]) + /// .exec(&db) + /// .await?; /// /// assert_eq!(insert_result.last_insert_id, 28); /// @@ -450,7 +450,8 @@ pub trait EntityTrait: EntityName { /// DbBackend::MySql, /// r#"INSERT INTO `cake` (`name`) VALUES (?), (?)"#, /// vec!["Apple Pie".into(), "Orange Scone".into()] - /// )]); + /// )] + /// ); /// # /// # Ok(()) /// # } @@ -477,11 +478,12 @@ pub trait EntityTrait: EntityName { /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::Postgres) - /// # .append_exec_results(vec![ - /// # MockExecResult { - /// # last_insert_id: 0, - /// # rows_affected: 1, - /// # }, + /// # .append_query_results(vec![ + /// # vec![fruit::Model { + /// # id: 1, + /// # name: "Orange".to_owned(), + /// # cake_id: None, + /// # }], /// # ]) /// # .into_connection(); /// # @@ -498,7 +500,12 @@ pub trait EntityTrait: EntityName { /// .filter(fruit::Column::Name.contains("orange")) /// .exec(&db) /// .await?, - /// orange + /// fruit::Model { + /// id: 1, + /// name: "Orange".to_owned(), + /// cake_id: None, + /// } + /// .into_active_model(), /// ); /// /// assert_eq!( @@ -523,6 +530,12 @@ pub trait EntityTrait: EntityName { /// # pub async fn main() -> Result<(), DbErr> { /// # /// # let db = MockDatabase::new(DbBackend::MySql) + /// # .append_exec_results(vec![ + /// # MockExecResult { + /// # last_insert_id: 0, + /// # rows_affected: 1, + /// # }, + /// # ]) /// # .append_query_results(vec![ /// # vec![fruit::Model { /// # id: 1, @@ -530,12 +543,6 @@ pub trait EntityTrait: EntityName { /// # cake_id: None, /// # }], /// # ]) - /// # .append_exec_results(vec![ - /// # MockExecResult { - /// # last_insert_id: 0, - /// # rows_affected: 1, - /// # }, - /// # ]) /// # .into_connection(); /// # /// use sea_orm::{entity::*, query::*, tests_cfg::fruit}; @@ -551,7 +558,12 @@ pub trait EntityTrait: EntityName { /// .filter(fruit::Column::Name.contains("orange")) /// .exec(&db) /// .await?, - /// orange + /// fruit::Model { + /// id: 1, + /// name: "Orange".to_owned(), + /// cake_id: None, + /// } + /// .into_active_model(), /// ); /// /// assert_eq!( @@ -600,7 +612,12 @@ pub trait EntityTrait: EntityName { /// # ]) /// # .into_connection(); /// # - /// use sea_orm::{entity::*, query::*, tests_cfg::fruit, sea_query::{Expr, Value}}; + /// use sea_orm::{ + /// entity::*, + /// query::*, + /// sea_query::{Expr, Value}, + /// tests_cfg::fruit, + /// }; /// /// let update_result = fruit::Entity::update_many() /// .col_expr(fruit::Column::CakeId, Expr::value(Value::Int(None))) @@ -616,7 +633,8 @@ pub trait EntityTrait: EntityName { /// DbBackend::Postgres, /// r#"UPDATE "fruit" SET "cake_id" = $1 WHERE "fruit"."name" LIKE $2"#, /// vec![Value::Int(None), "%Apple%".into()] - /// )]); + /// )] + /// ); /// # /// # Ok(()) /// # } @@ -661,9 +679,11 @@ pub trait EntityTrait: EntityName { /// assert_eq!( /// db.into_transaction_log(), /// vec![Transaction::from_sql_and_values( - /// DbBackend::Postgres, r#"DELETE FROM "fruit" WHERE "fruit"."id" = $1"#, + /// DbBackend::Postgres, + /// r#"DELETE FROM "fruit" WHERE "fruit"."id" = $1"#, /// vec![3i32.into()] - /// )]); + /// )] + /// ); /// # /// # Ok(()) /// # } @@ -695,6 +715,12 @@ pub trait EntityTrait: EntityName { /// # rows_affected: 5, /// # }, /// # ]) + /// # .append_query_results(vec![ + /// # vec![cake::Model { + /// # id: 15, + /// # name: "Apple Pie".to_owned(), + /// # }], + /// # ]) /// # .into_connection(); /// # /// use sea_orm::{entity::*, query::*, tests_cfg::fruit}; @@ -712,7 +738,8 @@ pub trait EntityTrait: EntityName { /// DbBackend::Postgres, /// r#"DELETE FROM "fruit" WHERE "fruit"."name" LIKE $1"#, /// vec!["%Apple%".into()] - /// )]); + /// )] + /// ); /// # /// # Ok(()) /// # } diff --git a/src/executor/paginator.rs b/src/executor/paginator.rs index 47548a0af..e8f37bea7 100644 --- a/src/executor/paginator.rs +++ b/src/executor/paginator.rs @@ -96,14 +96,22 @@ where /// Fetch one page and increment the page counter /// - /// ```rust + /// ``` /// # use sea_orm::{error::*, tests_cfg::*, *}; /// # /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] /// # pub async fn main() -> Result<(), DbErr> { /// # - /// # let owned_db = MockDatabase::new(DbBackend::Postgres).into_connection(); + /// # let owned_db = MockDatabase::new(DbBackend::Postgres) + /// # .append_query_results(vec![ + /// # vec![cake::Model { + /// # id: 1, + /// # name: "Cake".to_owned(), + /// # }], + /// # vec![], + /// # ]) + /// # .into_connection(); /// # let db = &owned_db; /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; @@ -127,14 +135,22 @@ where /// Convert self into an async stream /// - /// ```rust + /// ``` /// # use sea_orm::{error::*, tests_cfg::*, *}; /// # /// # #[smol_potat::main] /// # #[cfg(feature = "mock")] /// # pub async fn main() -> Result<(), DbErr> { /// # - /// # let owned_db = MockDatabase::new(DbBackend::Postgres).into_connection(); + /// # let owned_db = MockDatabase::new(DbBackend::Postgres) + /// # .append_query_results(vec![ + /// # vec![cake::Model { + /// # id: 1, + /// # name: "Cake".to_owned(), + /// # }], + /// # vec![], + /// # ]) + /// # .into_connection(); /// # let db = &owned_db; /// # /// use futures::TryStreamExt; diff --git a/src/executor/select.rs b/src/executor/select.rs index 3a08392e0..4c0d8ecfc 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -637,7 +637,14 @@ where /// # #[cfg(feature = "mock")] /// # pub async fn main() -> Result<(), DbErr> { /// # - /// # let db = MockDatabase::new(DbBackend::Postgres).into_connection(); + /// # let db = MockDatabase::new(DbBackend::Postgres) + /// # .append_query_results(vec![ + /// # vec![cake::Model { + /// # id: 1, + /// # name: "Cake".to_owned(), + /// # }], + /// # ]) + /// # .into_connection(); /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// @@ -681,7 +688,14 @@ where /// # #[cfg(feature = "mock")] /// # pub async fn main() -> Result<(), DbErr> { /// # - /// # let db = MockDatabase::new(DbBackend::Postgres).into_connection(); + /// # let db = MockDatabase::new(DbBackend::Postgres) + /// # .append_query_results(vec![ + /// # vec![cake::Model { + /// # id: 1, + /// # name: "Cake".to_owned(), + /// # }], + /// # ]) + /// # .into_connection(); /// # /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// From 42404eb5253dee050443b3251d710ba9001d96f3 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 16 Nov 2021 17:56:22 +0800 Subject: [PATCH 31/31] Fixup --- src/executor/update.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/executor/update.rs b/src/executor/update.rs index 9870b10d9..83747e2d6 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -175,22 +175,6 @@ mod tests { vec![], ]) .append_exec_results(vec![ - MockExecResult { - last_insert_id: 0, - rows_affected: 1, - }, - MockExecResult { - last_insert_id: 0, - rows_affected: 0, - }, - MockExecResult { - last_insert_id: 0, - rows_affected: 0, - }, - MockExecResult { - last_insert_id: 0, - rows_affected: 0, - }, MockExecResult { last_insert_id: 0, rows_affected: 0,