From 3ff8864eecbe2e16e4085854cde7fca92d3c9d95 Mon Sep 17 00:00:00 2001 From: Yiu Tin Cheung Ivan Date: Thu, 15 Jun 2023 19:15:43 +0800 Subject: [PATCH 1/7] end-of-day commit (WIP) --- src/executor/insert.rs | 47 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 96ad56cc1..26a3b31d5 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -221,3 +221,50 @@ where )), } } + +#[derive(Debug)] +/// show if the +pub enum InsertReturn { + Empty, + Conflicted, + Inserted(T), +} + +#[derive(Debug)] +/// struct for safe insert +pub struct InsertAttempt +where + A: ActiveModelTrait, +{ + pub(crate) insert: Insert, + pub(crate) State: InsertReturn, +} + +impl InsertAttempt +where + A: ActiveModelTrait, +{ + /// Add a Model to Self + /// + /// # Panics + /// + /// Panics if the column value has discrepancy across rows + #[allow(clippy::should_implement_trait)] + pub fn add(mut self, m: M) -> Self + where + M: IntoActiveModel, + { + self.insert = self.insert.add(m); + self + } + + /// Add many Models to Self + pub fn add_many(mut self, models: I) -> Self + where + M: IntoActiveModel, + I: IntoIterator, + { + self.insert = self.insert.add_many(models); + self + } +} From 4c858f1bfa97f6ce4719a4f881057dcbd0045647 Mon Sep 17 00:00:00 2001 From: Yiu Tin Cheung Ivan Date: Fri, 16 Jun 2023 15:52:01 +0800 Subject: [PATCH 2/7] progress commit (WIP) --- src/entity/base_entity.rs | 2 +- src/executor/insert.rs | 47 ------------------- src/query/insert.rs | 91 ++++--------------------------------- src/query/traits.rs | 95 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 104 insertions(+), 131 deletions(-) diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index 02917749f..940168ce3 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -1,7 +1,7 @@ use crate::{ ActiveModelTrait, ColumnTrait, Delete, DeleteMany, DeleteOne, FromQueryResult, Insert, ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, Related, RelationBuilder, - RelationTrait, RelationType, Select, Update, UpdateMany, UpdateOne, + RelationTrait, RelationType, Select, Update, UpdateMany, UpdateOne, query::traits }; use sea_query::{Alias, Iden, IntoIden, IntoTableRef, IntoValueTuple, TableRef}; use std::fmt::Debug; diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 26a3b31d5..96ad56cc1 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -221,50 +221,3 @@ where )), } } - -#[derive(Debug)] -/// show if the -pub enum InsertReturn { - Empty, - Conflicted, - Inserted(T), -} - -#[derive(Debug)] -/// struct for safe insert -pub struct InsertAttempt -where - A: ActiveModelTrait, -{ - pub(crate) insert: Insert, - pub(crate) State: InsertReturn, -} - -impl InsertAttempt -where - A: ActiveModelTrait, -{ - /// Add a Model to Self - /// - /// # Panics - /// - /// Panics if the column value has discrepancy across rows - #[allow(clippy::should_implement_trait)] - pub fn add(mut self, m: M) -> Self - where - M: IntoActiveModel, - { - self.insert = self.insert.add(m); - self - } - - /// Add many Models to Self - pub fn add_many(mut self, models: I) -> Self - where - M: IntoActiveModel, - I: IntoIterator, - { - self.insert = self.insert.add_many(models); - self - } -} diff --git a/src/query/insert.rs b/src/query/insert.rs index 64dc248b1..d7a3fbe0e 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -1,6 +1,6 @@ use crate::{ ActiveModelTrait, ActiveValue, ColumnTrait, EntityName, EntityTrait, IntoActiveModel, Iterable, - PrimaryKeyTrait, QueryTrait, + PrimaryKeyTrait, QueryTrait, InsertTrait, }; use core::marker::PhantomData; use sea_query::{Expr, InsertStatement, OnConflict, ValueTuple}; @@ -26,11 +26,11 @@ where } } -impl Insert +impl InsertTrait for Insert where A: ActiveModelTrait, { - pub(crate) fn new() -> Self { + fn new() -> Self { Self { query: InsertStatement::new() .into_table(A::Entity::default().table_ref()) @@ -42,79 +42,13 @@ where } } - /// Insert one Model or ActiveModel - /// - /// Model - /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; - /// - /// assert_eq!( - /// Insert::one(cake::Model { - /// id: 1, - /// name: "Apple Pie".to_owned(), - /// }) - /// .build(DbBackend::Postgres) - /// .to_string(), - /// r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie')"#, - /// ); - /// ``` - /// ActiveModel - /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; - /// - /// assert_eq!( - /// Insert::one(cake::ActiveModel { - /// id: NotSet, - /// name: Set("Apple Pie".to_owned()), - /// }) - /// .build(DbBackend::Postgres) - /// .to_string(), - /// r#"INSERT INTO "cake" ("name") VALUES ('Apple Pie')"#, - /// ); - /// ``` - pub fn one(m: M) -> Insert - where - M: IntoActiveModel, - { - Self::new().add(m) - } - - /// Insert many Model or ActiveModel - /// - /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; - /// - /// assert_eq!( - /// Insert::many([ - /// cake::Model { - /// id: 1, - /// name: "Apple Pie".to_owned(), - /// }, - /// cake::Model { - /// id: 2, - /// name: "Orange Scone".to_owned(), - /// } - /// ]) - /// .build(DbBackend::Postgres) - /// .to_string(), - /// r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie'), (2, 'Orange Scone')"#, - /// ); - /// ``` - pub fn many(models: I) -> Self - where - M: IntoActiveModel, - I: IntoIterator, - { - Self::new().add_many(models) - } - /// Add a Model to Self /// /// # Panics /// /// Panics if the column value has discrepancy across rows #[allow(clippy::should_implement_trait)] - pub fn add(mut self, m: M) -> Self + fn add(mut self, m: M) -> Self where M: IntoActiveModel, { @@ -148,19 +82,12 @@ where self.query.values_panic(values); self } +} - /// Add many Models to Self - pub fn add_many(mut self, models: I) -> Self - where - M: IntoActiveModel, - I: IntoIterator, - { - for model in models.into_iter() { - self = self.add(model); - } - self - } - +impl Insert +where + A: ActiveModelTrait, +{ /// On conflict /// /// on conflict do nothing diff --git a/src/query/traits.rs b/src/query/traits.rs index a0e0a6461..9988d5599 100644 --- a/src/query/traits.rs +++ b/src/query/traits.rs @@ -1,4 +1,4 @@ -use crate::{ColumnTrait, DbBackend, IntoIdentity, IntoSimpleExpr, QuerySelect, Statement}; +use crate::{ColumnTrait, DbBackend, IntoIdentity, IntoSimpleExpr, QuerySelect, Statement, ActiveModelTrait, IntoActiveModel}; use sea_query::QueryStatementBuilder; /// A Trait for any type performing queries on a Model or ActiveModel @@ -88,3 +88,96 @@ where QuerySelect::column_as(self, col, alias) } } + +/// Insert query Trait +pub trait InsertTrait : Sized +where + A: ActiveModelTrait, +{ + /// required function for new self + fn new() -> Self; + + /// required function for add value + fn add(self, m: M) -> Self + where + M: IntoActiveModel; + + /// Insert one Model or ActiveModel + /// + /// Model + /// ``` + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; + /// + /// assert_eq!( + /// Insert::one(cake::Model { + /// id: 1, + /// name: "Apple Pie".to_owned(), + /// }) + /// .build(DbBackend::Postgres) + /// .to_string(), + /// r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie')"#, + /// ); + /// ``` + /// ActiveModel + /// ``` + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; + /// + /// assert_eq!( + /// Insert::one(cake::ActiveModel { + /// id: NotSet, + /// name: Set("Apple Pie".to_owned()), + /// }) + /// .build(DbBackend::Postgres) + /// .to_string(), + /// r#"INSERT INTO "cake" ("name") VALUES ('Apple Pie')"#, + /// ); + /// ``` + fn one(m: M) -> Self + where + M: IntoActiveModel, + { + Self::new().add(m) + } + + /// Insert many Model or ActiveModel + /// + /// ``` + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; + /// + /// assert_eq!( + /// Insert::many([ + /// cake::Model { + /// id: 1, + /// name: "Apple Pie".to_owned(), + /// }, + /// cake::Model { + /// id: 2, + /// name: "Orange Scone".to_owned(), + /// } + /// ]) + /// .build(DbBackend::Postgres) + /// .to_string(), + /// r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie'), (2, 'Orange Scone')"#, + /// ); + /// ``` + fn many(models: I) -> Self + where + M: IntoActiveModel, + I: IntoIterator, + { + Self::new().add_many(models) + } + + + /// Add many Models to Self + fn add_many(mut self, models: I) -> Self + where + M: IntoActiveModel, + I: IntoIterator, + { + for model in models.into_iter() { + self = self.add(model); + } + self + } +} From 912170103f21ec13b03083fe28fcdb3459c1b2c4 Mon Sep 17 00:00:00 2001 From: Yiu Tin Cheung Ivan Date: Fri, 16 Jun 2023 16:35:32 +0800 Subject: [PATCH 3/7] refactored and added InsertAttempt --- src/entity/base_entity.rs | 2 +- src/query/insert.rs | 127 +++++++++++++++++++++++++++++++++++++- 2 files changed, 127 insertions(+), 2 deletions(-) diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index 940168ce3..370a6ec7c 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -1,7 +1,7 @@ use crate::{ ActiveModelTrait, ColumnTrait, Delete, DeleteMany, DeleteOne, FromQueryResult, Insert, ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, Related, RelationBuilder, - RelationTrait, RelationType, Select, Update, UpdateMany, UpdateOne, query::traits + RelationTrait, RelationType, Select, Update, UpdateMany, UpdateOne, InsertTrait }; use sea_query::{Alias, Iden, IntoIden, IntoTableRef, IntoValueTuple, TableRef}; use std::fmt::Debug; diff --git a/src/query/insert.rs b/src/query/insert.rs index d7a3fbe0e..9ca611d4d 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -135,6 +135,15 @@ where self.query.on_conflict(on_conflict); self } + + /// Allow insert statement return safely if inserting nothing. + /// The database will not be affected. + pub fn on_empty_do_nothing(self) -> InsertAttempt + where + A: ActiveModelTrait, + { + InsertAttempt::from_insert(self) + } } impl QueryTrait for Insert @@ -156,12 +165,128 @@ where } } +/// Performs INSERT operations on a ActiveModel, will do nothing if input is empty. +#[derive(Debug)] +pub struct InsertAttempt +where + A: ActiveModelTrait +{ + pub(crate) insert_struct: Insert, + pub(crate) on_empty_do_nothing: bool, +} + +impl InsertTrait for InsertAttempt +where + A: ActiveModelTrait, +{ + fn new() -> Self { + Self { + insert_struct : Insert::new(), + on_empty_do_nothing : true, + } + } + + /// Add a Model to Self + /// + /// # Panics + /// + /// Panics if the column value has discrepancy across rows + #[allow(clippy::should_implement_trait)] + fn add(mut self, m: M) -> Self + where + M: IntoActiveModel, + { + self.insert_struct = self.insert_struct.add(m); + self + } +} + +impl InsertAttempt +where + A: ActiveModelTrait, +{ + /// helper function for conversion + pub fn from_insert(insert: Insert) -> Self{ + Self { + insert_struct : insert, + on_empty_do_nothing : true, + } + } + + /// On conflict + /// + /// on conflict do nothing + /// ``` + /// use sea_orm::{entity::*, query::*, sea_query::OnConflict, tests_cfg::cake, DbBackend}; + /// + /// let orange = cake::ActiveModel { + /// id: ActiveValue::set(2), + /// name: ActiveValue::set("Orange".to_owned()), + /// }; + /// assert_eq!( + /// cake::Entity::insert(orange) + /// .on_conflict( + /// OnConflict::column(cake::Column::Name) + /// .do_nothing() + /// .to_owned() + /// ) + /// .build(DbBackend::Postgres) + /// .to_string(), + /// r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO NOTHING"#, + /// ); + /// ``` + /// + /// on conflict do update + /// ``` + /// use sea_orm::{entity::*, query::*, sea_query::OnConflict, tests_cfg::cake, DbBackend}; + /// + /// let orange = cake::ActiveModel { + /// id: ActiveValue::set(2), + /// name: ActiveValue::set("Orange".to_owned()), + /// }; + /// assert_eq!( + /// cake::Entity::insert(orange) + /// .on_conflict( + /// OnConflict::column(cake::Column::Name) + /// .update_column(cake::Column::Name) + /// .to_owned() + /// ) + /// .build(DbBackend::Postgres) + /// .to_string(), + /// r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO UPDATE SET "name" = "excluded"."name""#, + /// ); + /// ``` + pub fn on_conflict(mut self, on_conflict: OnConflict) -> Self { + self.insert_struct.query.on_conflict(on_conflict); + self + } +} + +impl QueryTrait for InsertAttempt +where + A: ActiveModelTrait, +{ + type QueryStatement = InsertStatement; + + fn query(&mut self) -> &mut InsertStatement { + &mut self.insert_struct.query + } + + fn as_query(&self) -> &InsertStatement { + &self.insert_struct.query + } + + fn into_query(self) -> InsertStatement { + self.insert_struct.query + } +} + #[cfg(test)] mod tests { use sea_query::OnConflict; use crate::tests_cfg::cake; - use crate::{ActiveValue, DbBackend, DbErr, EntityTrait, Insert, IntoActiveModel, QueryTrait}; + use crate::{ActiveValue, DbBackend, DbErr, EntityTrait, Insert, IntoActiveModel, QueryTrait, InsertTrait}; #[test] fn insert_1() { From d0af00bc80e9b6c25cd83f8263b69bcf39bd4629 Mon Sep 17 00:00:00 2001 From: Yiu Tin Cheung Ivan Date: Fri, 16 Jun 2023 18:03:10 +0800 Subject: [PATCH 4/7] async asjusting --- src/executor/insert.rs | 61 ++++++++++++++++++++++++++++++++++++++++++ src/query/insert.rs | 3 --- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 96ad56cc1..983a87ef8 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,6 +1,7 @@ use crate::{ error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, Insert, IntoActiveModel, Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, SelectModel, SelectorRaw, Statement, TryFromU64, + InsertAttempt, }; use sea_query::{Expr, FromValueTuple, Iden, InsertStatement, IntoColumnRef, Query, ValueTuple}; use std::{future::Future, marker::PhantomData}; @@ -26,6 +27,66 @@ where pub last_insert_id: <<::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType, } +/// The types of results for an INSERT operation +#[derive(Debug)] +pub enum InsertAttemptResultType +{ + /// The INSERT operation did not insert any value + Empty, + /// Reserved + Conflicted, + /// Successfully inserted + Inserted(T), +} + +impl InsertAttempt +where + A: ActiveModelTrait, +{ + /// Execute an insert operation + #[allow(unused_mut)] + pub async fn exec<'a, C>(self, db: &'a C) -> InsertAttemptResultType, DbErr>> + where + C: ConnectionTrait, + A: 'a, + { + if self.insert_struct.columns.is_empty() { + return InsertAttemptResultType::Empty + } + else{ + return InsertAttemptResultType::Inserted(self.insert_struct.exec(db).await) + } + } + + /// Execute an insert operation without returning (don't use `RETURNING` syntax) + /// Number of rows affected is returned + pub fn exec_without_returning<'a, C>( + self, + db: &'a C, + ) -> impl Future> + '_ + where + ::Model: IntoActiveModel, + C: ConnectionTrait, + A: 'a, + { + Inserter::::new(self.insert_struct.primary_key, self.insert_struct.query).exec_without_returning(db) + } + + /// 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, + ) -> impl Future::Model, DbErr>> + '_ + where + ::Model: IntoActiveModel, + C: ConnectionTrait, + A: 'a, + { + + Inserter::::new(self.insert_struct.primary_key, self.insert_struct.query).exec_with_returning(db) + } +} + impl Insert where A: ActiveModelTrait, diff --git a/src/query/insert.rs b/src/query/insert.rs index 9ca611d4d..4a0b95140 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -172,7 +172,6 @@ where A: ActiveModelTrait { pub(crate) insert_struct: Insert, - pub(crate) on_empty_do_nothing: bool, } impl InsertTrait for InsertAttempt @@ -182,7 +181,6 @@ where fn new() -> Self { Self { insert_struct : Insert::new(), - on_empty_do_nothing : true, } } @@ -209,7 +207,6 @@ where pub fn from_insert(insert: Insert) -> Self{ Self { insert_struct : insert, - on_empty_do_nothing : true, } } From e30661eafa0077c893ea44d2d72faa68a9a901bc Mon Sep 17 00:00:00 2001 From: Yiu Tin Cheung Ivan Date: Mon, 19 Jun 2023 12:24:45 +0800 Subject: [PATCH 5/7] completed implementation for insertAttempt in execution Added in tests for insertAttempt --- src/entity/base_entity.rs | 4 +-- src/executor/insert.rs | 44 +++++++++++++++++------------- src/query/insert.rs | 29 +++++++++++--------- src/query/traits.rs | 12 +++++---- tests/empty_insert_tests.rs | 54 +++++++++++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+), 37 deletions(-) create mode 100644 tests/empty_insert_tests.rs diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index 370a6ec7c..f0d40ad33 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -1,7 +1,7 @@ use crate::{ ActiveModelTrait, ColumnTrait, Delete, DeleteMany, DeleteOne, FromQueryResult, Insert, - ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, Related, RelationBuilder, - RelationTrait, RelationType, Select, Update, UpdateMany, UpdateOne, InsertTrait + InsertTrait, ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, Related, + RelationBuilder, RelationTrait, RelationType, Select, Update, UpdateMany, UpdateOne, }; use sea_query::{Alias, Iden, IntoIden, IntoTableRef, IntoValueTuple, TableRef}; use std::fmt::Debug; diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 983a87ef8..35cad456e 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,7 +1,7 @@ use crate::{ - error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, Insert, IntoActiveModel, - Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, SelectModel, SelectorRaw, Statement, TryFromU64, - InsertAttempt, + error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, Insert, InsertAttempt, + IntoActiveModel, Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, SelectModel, SelectorRaw, + Statement, TryFromU64, }; use sea_query::{Expr, FromValueTuple, Iden, InsertStatement, IntoColumnRef, Query, ValueTuple}; use std::{future::Future, marker::PhantomData}; @@ -29,12 +29,11 @@ where /// The types of results for an INSERT operation #[derive(Debug)] -pub enum InsertAttemptResultType -{ +pub enum InsertAttemptResultType { /// The INSERT operation did not insert any value Empty, /// Reserved - Conflicted, + Conflicted, /// Successfully inserted Inserted(T), } @@ -45,45 +44,54 @@ where { /// Execute an insert operation #[allow(unused_mut)] - pub async fn exec<'a, C>(self, db: &'a C) -> InsertAttemptResultType, DbErr>> + pub async fn exec<'a, C>( + self, + db: &'a C, + ) -> InsertAttemptResultType, DbErr>> where C: ConnectionTrait, A: 'a, { if self.insert_struct.columns.is_empty() { - return InsertAttemptResultType::Empty - } - else{ - return InsertAttemptResultType::Inserted(self.insert_struct.exec(db).await) + InsertAttemptResultType::Empty + } else { + InsertAttemptResultType::Inserted(self.insert_struct.exec(db).await) } } /// Execute an insert operation without returning (don't use `RETURNING` syntax) /// Number of rows affected is returned - pub fn exec_without_returning<'a, C>( + pub async fn exec_without_returning<'a, C>( self, db: &'a C, - ) -> impl Future> + '_ + ) -> InsertAttemptResultType> where ::Model: IntoActiveModel, C: ConnectionTrait, A: 'a, { - Inserter::::new(self.insert_struct.primary_key, self.insert_struct.query).exec_without_returning(db) + if self.insert_struct.columns.is_empty() { + InsertAttemptResultType::Empty + } else { + InsertAttemptResultType::Inserted(self.insert_struct.exec_without_returning(db).await) + } } /// Execute an insert operation and return the inserted model (use `RETURNING` syntax if database supported) - pub fn exec_with_returning<'a, C>( + pub async fn exec_with_returning<'a, C>( self, db: &'a C, - ) -> impl Future::Model, DbErr>> + '_ + ) -> InsertAttemptResultType::Model, DbErr>> where ::Model: IntoActiveModel, C: ConnectionTrait, A: 'a, { - - Inserter::::new(self.insert_struct.primary_key, self.insert_struct.query).exec_with_returning(db) + if self.insert_struct.columns.is_empty() { + InsertAttemptResultType::Empty + } else { + InsertAttemptResultType::Inserted(self.insert_struct.exec_with_returning(db).await) + } } } diff --git a/src/query/insert.rs b/src/query/insert.rs index 4a0b95140..b2e444664 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -1,6 +1,6 @@ use crate::{ - ActiveModelTrait, ActiveValue, ColumnTrait, EntityName, EntityTrait, IntoActiveModel, Iterable, - PrimaryKeyTrait, QueryTrait, InsertTrait, + ActiveModelTrait, ActiveValue, ColumnTrait, EntityName, EntityTrait, InsertTrait, + IntoActiveModel, Iterable, PrimaryKeyTrait, QueryTrait, }; use core::marker::PhantomData; use sea_query::{Expr, InsertStatement, OnConflict, ValueTuple}; @@ -139,7 +139,7 @@ where /// Allow insert statement return safely if inserting nothing. /// The database will not be affected. pub fn on_empty_do_nothing(self) -> InsertAttempt - where + where A: ActiveModelTrait, { InsertAttempt::from_insert(self) @@ -168,8 +168,8 @@ where /// Performs INSERT operations on a ActiveModel, will do nothing if input is empty. #[derive(Debug)] pub struct InsertAttempt -where - A: ActiveModelTrait +where + A: ActiveModelTrait, { pub(crate) insert_struct: Insert, } @@ -180,7 +180,7 @@ where { fn new() -> Self { Self { - insert_struct : Insert::new(), + insert_struct: Insert::new(), } } @@ -203,10 +203,10 @@ impl InsertAttempt where A: ActiveModelTrait, { - /// helper function for conversion - pub fn from_insert(insert: Insert) -> Self{ + /// The conversion function from insert to InsertAttempt + pub fn from_insert(insert: Insert) -> Self { Self { - insert_struct : insert, + insert_struct: insert, } } @@ -222,6 +222,7 @@ where /// }; /// assert_eq!( /// cake::Entity::insert(orange) + /// .on_empty_do_nothing() /// .on_conflict( /// OnConflict::column(cake::Column::Name) /// .do_nothing() @@ -243,6 +244,7 @@ where /// }; /// assert_eq!( /// cake::Entity::insert(orange) + /// .on_empty_do_nothing() /// .on_conflict( /// OnConflict::column(cake::Column::Name) /// .update_column(cake::Column::Name) @@ -276,14 +278,17 @@ where fn into_query(self) -> InsertStatement { self.insert_struct.query } -} +} #[cfg(test)] mod tests { use sea_query::OnConflict; - use crate::tests_cfg::cake; - use crate::{ActiveValue, DbBackend, DbErr, EntityTrait, Insert, IntoActiveModel, QueryTrait, InsertTrait}; + use crate::tests_cfg::cake::{self, ActiveModel}; + use crate::{ + ActiveValue, DbBackend, DbErr, EntityTrait, Insert, InsertTrait, IntoActiveModel, + QueryTrait, + }; #[test] fn insert_1() { diff --git a/src/query/traits.rs b/src/query/traits.rs index 9988d5599..abf53b9a5 100644 --- a/src/query/traits.rs +++ b/src/query/traits.rs @@ -1,4 +1,7 @@ -use crate::{ColumnTrait, DbBackend, IntoIdentity, IntoSimpleExpr, QuerySelect, Statement, ActiveModelTrait, IntoActiveModel}; +use crate::{ + ActiveModelTrait, ColumnTrait, DbBackend, IntoActiveModel, IntoIdentity, IntoSimpleExpr, + QuerySelect, Statement, +}; use sea_query::QueryStatementBuilder; /// A Trait for any type performing queries on a Model or ActiveModel @@ -89,15 +92,15 @@ where } } -/// Insert query Trait -pub trait InsertTrait : Sized +/// Insert query Trait +pub trait InsertTrait: Sized where A: ActiveModelTrait, { /// required function for new self fn new() -> Self; - /// required function for add value + /// required function for add value fn add(self, m: M) -> Self where M: IntoActiveModel; @@ -168,7 +171,6 @@ where Self::new().add_many(models) } - /// Add many Models to Self fn add_many(mut self, models: I) -> Self where diff --git a/tests/empty_insert_tests.rs b/tests/empty_insert_tests.rs new file mode 100644 index 000000000..5a09af4b7 --- /dev/null +++ b/tests/empty_insert_tests.rs @@ -0,0 +1,54 @@ +pub mod common; +mod crud; + +pub use common::{bakery_chain::*, setup::*, TestContext}; +pub use sea_orm::{ + entity::*, error::DbErr, tests_cfg, DatabaseConnection, DbBackend, EntityName, ExecResult, +}; + +pub use crud::*; +// use common::bakery_chain::*; +use sea_orm::{DbConn, InsertAttemptResultType}; + +#[sea_orm_macros::test] +#[cfg(any( + feature = "sqlx-mysql", + feature = "sqlx-sqlite", + feature = "sqlx-postgres" +))] +async fn main() { + let ctx = TestContext::new("bakery_chain_empty_insert_tests").await; + create_tables(&ctx.db).await.unwrap(); + test(&ctx.db).await; + ctx.delete().await; +} + +pub async fn test(db: &DbConn) { + let seaside_bakery = bakery::ActiveModel { + name: Set("SeaSide Bakery".to_owned()), + profit_margin: Set(10.4), + ..Default::default() + }; + + let res = Bakery::insert(seaside_bakery) + .on_empty_do_nothing() + .exec(db) + .await; + + assert!(matches!(res, InsertAttemptResultType::Inserted(_))); + + let empty_iterator = [bakery::ActiveModel { + name: Set("SeaSide Bakery".to_owned()), + profit_margin: Set(10.4), + ..Default::default() + }] + .into_iter() + .filter(|_| false); + + let empty_insert = Bakery::insert_many(empty_iterator) + .on_empty_do_nothing() + .exec(db) + .await; + + assert!(matches!(empty_insert, InsertAttemptResultType::Empty)); +} From d53deefdcf0b2987a0a8079ffc73b1617bbdfba4 Mon Sep 17 00:00:00 2001 From: Yiu Tin Cheung Ivan Date: Mon, 19 Jun 2023 17:48:12 +0800 Subject: [PATCH 6/7] updated wording for new INSERT type --- src/executor/insert.rs | 31 ++++++++++++++----------------- src/query/insert.rs | 14 +++++++------- tests/empty_insert_tests.rs | 6 +++--- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 35cad456e..be00dbed3 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,7 +1,7 @@ use crate::{ - error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, Insert, InsertAttempt, - IntoActiveModel, Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, SelectModel, SelectorRaw, - Statement, TryFromU64, + error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, Insert, IntoActiveModel, + Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, SelectModel, SelectorRaw, Statement, TryFromU64, + TryInsert, }; use sea_query::{Expr, FromValueTuple, Iden, InsertStatement, IntoColumnRef, Query, ValueTuple}; use std::{future::Future, marker::PhantomData}; @@ -29,7 +29,7 @@ where /// The types of results for an INSERT operation #[derive(Debug)] -pub enum InsertAttemptResultType { +pub enum TryInsertResult { /// The INSERT operation did not insert any value Empty, /// Reserved @@ -38,24 +38,21 @@ pub enum InsertAttemptResultType { Inserted(T), } -impl InsertAttempt +impl TryInsert where A: ActiveModelTrait, { /// Execute an insert operation #[allow(unused_mut)] - pub async fn exec<'a, C>( - self, - db: &'a C, - ) -> InsertAttemptResultType, DbErr>> + pub async fn exec<'a, C>(self, db: &'a C) -> TryInsertResult, DbErr>> where C: ConnectionTrait, A: 'a, { if self.insert_struct.columns.is_empty() { - InsertAttemptResultType::Empty + TryInsertResult::Empty } else { - InsertAttemptResultType::Inserted(self.insert_struct.exec(db).await) + TryInsertResult::Inserted(self.insert_struct.exec(db).await) } } @@ -64,16 +61,16 @@ where pub async fn exec_without_returning<'a, C>( self, db: &'a C, - ) -> InsertAttemptResultType> + ) -> TryInsertResult> where ::Model: IntoActiveModel, C: ConnectionTrait, A: 'a, { if self.insert_struct.columns.is_empty() { - InsertAttemptResultType::Empty + TryInsertResult::Empty } else { - InsertAttemptResultType::Inserted(self.insert_struct.exec_without_returning(db).await) + TryInsertResult::Inserted(self.insert_struct.exec_without_returning(db).await) } } @@ -81,16 +78,16 @@ where pub async fn exec_with_returning<'a, C>( self, db: &'a C, - ) -> InsertAttemptResultType::Model, DbErr>> + ) -> TryInsertResult::Model, DbErr>> where ::Model: IntoActiveModel, C: ConnectionTrait, A: 'a, { if self.insert_struct.columns.is_empty() { - InsertAttemptResultType::Empty + TryInsertResult::Empty } else { - InsertAttemptResultType::Inserted(self.insert_struct.exec_with_returning(db).await) + TryInsertResult::Inserted(self.insert_struct.exec_with_returning(db).await) } } } diff --git a/src/query/insert.rs b/src/query/insert.rs index b2e444664..4fe0ad075 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -138,11 +138,11 @@ where /// Allow insert statement return safely if inserting nothing. /// The database will not be affected. - pub fn on_empty_do_nothing(self) -> InsertAttempt + pub fn on_empty_do_nothing(self) -> TryInsert where A: ActiveModelTrait, { - InsertAttempt::from_insert(self) + TryInsert::from_insert(self) } } @@ -167,14 +167,14 @@ where /// Performs INSERT operations on a ActiveModel, will do nothing if input is empty. #[derive(Debug)] -pub struct InsertAttempt +pub struct TryInsert where A: ActiveModelTrait, { pub(crate) insert_struct: Insert, } -impl InsertTrait for InsertAttempt +impl InsertTrait for TryInsert where A: ActiveModelTrait, { @@ -199,11 +199,11 @@ where } } -impl InsertAttempt +impl TryInsert where A: ActiveModelTrait, { - /// The conversion function from insert to InsertAttempt + /// The conversion function from insert to TryInsert pub fn from_insert(insert: Insert) -> Self { Self { insert_struct: insert, @@ -261,7 +261,7 @@ where } } -impl QueryTrait for InsertAttempt +impl QueryTrait for TryInsert where A: ActiveModelTrait, { diff --git a/tests/empty_insert_tests.rs b/tests/empty_insert_tests.rs index 5a09af4b7..962947a9b 100644 --- a/tests/empty_insert_tests.rs +++ b/tests/empty_insert_tests.rs @@ -8,7 +8,7 @@ pub use sea_orm::{ pub use crud::*; // use common::bakery_chain::*; -use sea_orm::{DbConn, InsertAttemptResultType}; +use sea_orm::{DbConn, TryInsertResult}; #[sea_orm_macros::test] #[cfg(any( @@ -35,7 +35,7 @@ pub async fn test(db: &DbConn) { .exec(db) .await; - assert!(matches!(res, InsertAttemptResultType::Inserted(_))); + assert!(matches!(res, TryInsertResult::Inserted(_))); let empty_iterator = [bakery::ActiveModel { name: Set("SeaSide Bakery".to_owned()), @@ -50,5 +50,5 @@ pub async fn test(db: &DbConn) { .exec(db) .await; - assert!(matches!(empty_insert, InsertAttemptResultType::Empty)); + assert!(matches!(empty_insert, TryInsertResult::Empty)); } From 9348057d00a02eb966c2bb15d1661df4053ba1a9 Mon Sep 17 00:00:00 2001 From: Yiu Tin Cheung Ivan Date: Mon, 19 Jun 2023 18:41:20 +0800 Subject: [PATCH 7/7] removed InsertTrait --- src/entity/base_entity.rs | 4 +- src/query/insert.rs | 206 ++++++++++++++++++++++++-------------- src/query/traits.rs | 97 +----------------- 3 files changed, 132 insertions(+), 175 deletions(-) diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index f0d40ad33..02917749f 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -1,7 +1,7 @@ use crate::{ ActiveModelTrait, ColumnTrait, Delete, DeleteMany, DeleteOne, FromQueryResult, Insert, - InsertTrait, ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, Related, - RelationBuilder, RelationTrait, RelationType, Select, Update, UpdateMany, UpdateOne, + ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, Related, RelationBuilder, + RelationTrait, RelationType, Select, Update, UpdateMany, UpdateOne, }; use sea_query::{Alias, Iden, IntoIden, IntoTableRef, IntoValueTuple, TableRef}; use std::fmt::Debug; diff --git a/src/query/insert.rs b/src/query/insert.rs index 4fe0ad075..3fdb343c3 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -1,6 +1,6 @@ use crate::{ - ActiveModelTrait, ActiveValue, ColumnTrait, EntityName, EntityTrait, InsertTrait, - IntoActiveModel, Iterable, PrimaryKeyTrait, QueryTrait, + ActiveModelTrait, ActiveValue, ColumnTrait, EntityName, EntityTrait, IntoActiveModel, Iterable, + PrimaryKeyTrait, QueryTrait, }; use core::marker::PhantomData; use sea_query::{Expr, InsertStatement, OnConflict, ValueTuple}; @@ -26,11 +26,11 @@ where } } -impl InsertTrait for Insert +impl Insert where A: ActiveModelTrait, { - fn new() -> Self { + pub(crate) fn new() -> Self { Self { query: InsertStatement::new() .into_table(A::Entity::default().table_ref()) @@ -42,13 +42,79 @@ where } } + /// Insert one Model or ActiveModel + /// + /// Model + /// ``` + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; + /// + /// assert_eq!( + /// Insert::one(cake::Model { + /// id: 1, + /// name: "Apple Pie".to_owned(), + /// }) + /// .build(DbBackend::Postgres) + /// .to_string(), + /// r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie')"#, + /// ); + /// ``` + /// ActiveModel + /// ``` + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; + /// + /// assert_eq!( + /// Insert::one(cake::ActiveModel { + /// id: NotSet, + /// name: Set("Apple Pie".to_owned()), + /// }) + /// .build(DbBackend::Postgres) + /// .to_string(), + /// r#"INSERT INTO "cake" ("name") VALUES ('Apple Pie')"#, + /// ); + /// ``` + pub fn one(m: M) -> Self + where + M: IntoActiveModel, + { + Self::new().add(m) + } + + /// Insert many Model or ActiveModel + /// + /// ``` + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; + /// + /// assert_eq!( + /// Insert::many([ + /// cake::Model { + /// id: 1, + /// name: "Apple Pie".to_owned(), + /// }, + /// cake::Model { + /// id: 2, + /// name: "Orange Scone".to_owned(), + /// } + /// ]) + /// .build(DbBackend::Postgres) + /// .to_string(), + /// r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie'), (2, 'Orange Scone')"#, + /// ); + /// ``` + pub fn many(models: I) -> Self + where + M: IntoActiveModel, + I: IntoIterator, + { + Self::new().add_many(models) + } + /// Add a Model to Self /// /// # Panics /// /// Panics if the column value has discrepancy across rows #[allow(clippy::should_implement_trait)] - fn add(mut self, m: M) -> Self + pub fn add(mut self, m: M) -> Self where M: IntoActiveModel, { @@ -82,12 +148,19 @@ where self.query.values_panic(values); self } -} -impl Insert -where - A: ActiveModelTrait, -{ + /// Add many Models to Self + pub fn add_many(mut self, models: I) -> Self + where + M: IntoActiveModel, + I: IntoIterator, + { + for model in models.into_iter() { + self = self.add(model); + } + self + } + /// On conflict /// /// on conflict do nothing @@ -166,6 +239,8 @@ where } /// Performs INSERT operations on a ActiveModel, will do nothing if input is empty. +/// +/// All functions works the same as if it is Insert. Please refer to Insert page for more information #[derive(Debug)] pub struct TryInsert where @@ -174,91 +249,72 @@ where pub(crate) insert_struct: Insert, } -impl InsertTrait for TryInsert +impl Default for TryInsert where A: ActiveModelTrait, { - fn new() -> Self { + fn default() -> Self { + Self::new() + } +} + +#[allow(missing_docs)] +impl TryInsert +where + A: ActiveModelTrait, +{ + pub(crate) fn new() -> Self { Self { insert_struct: Insert::new(), } } - /// Add a Model to Self - /// - /// # Panics - /// - /// Panics if the column value has discrepancy across rows + pub fn one(m: M) -> Self + where + M: IntoActiveModel, + { + Self::new().add(m) + } + + pub fn many(models: I) -> Self + where + M: IntoActiveModel, + I: IntoIterator, + { + Self::new().add_many(models) + } + #[allow(clippy::should_implement_trait)] - fn add(mut self, m: M) -> Self + pub fn add(mut self, m: M) -> Self where M: IntoActiveModel, { self.insert_struct = self.insert_struct.add(m); self } -} -impl TryInsert -where - A: ActiveModelTrait, -{ - /// The conversion function from insert to TryInsert - pub fn from_insert(insert: Insert) -> Self { - Self { - insert_struct: insert, + pub fn add_many(mut self, models: I) -> Self + where + M: IntoActiveModel, + I: IntoIterator, + { + for model in models.into_iter() { + self.insert_struct = self.insert_struct.add(model); } + self } - /// On conflict - /// - /// on conflict do nothing - /// ``` - /// use sea_orm::{entity::*, query::*, sea_query::OnConflict, tests_cfg::cake, DbBackend}; - /// - /// let orange = cake::ActiveModel { - /// id: ActiveValue::set(2), - /// name: ActiveValue::set("Orange".to_owned()), - /// }; - /// assert_eq!( - /// cake::Entity::insert(orange) - /// .on_empty_do_nothing() - /// .on_conflict( - /// OnConflict::column(cake::Column::Name) - /// .do_nothing() - /// .to_owned() - /// ) - /// .build(DbBackend::Postgres) - /// .to_string(), - /// r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO NOTHING"#, - /// ); - /// ``` - /// - /// on conflict do update - /// ``` - /// use sea_orm::{entity::*, query::*, sea_query::OnConflict, tests_cfg::cake, DbBackend}; - /// - /// let orange = cake::ActiveModel { - /// id: ActiveValue::set(2), - /// name: ActiveValue::set("Orange".to_owned()), - /// }; - /// assert_eq!( - /// cake::Entity::insert(orange) - /// .on_empty_do_nothing() - /// .on_conflict( - /// OnConflict::column(cake::Column::Name) - /// .update_column(cake::Column::Name) - /// .to_owned() - /// ) - /// .build(DbBackend::Postgres) - /// .to_string(), - /// r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO UPDATE SET "name" = "excluded"."name""#, - /// ); - /// ``` pub fn on_conflict(mut self, on_conflict: OnConflict) -> Self { self.insert_struct.query.on_conflict(on_conflict); self } + + // helper function for on_empty_do_nothing in Insert + pub fn from_insert(insert: Insert) -> Self { + Self { + insert_struct: insert, + } + } } impl QueryTrait for TryInsert @@ -279,16 +335,12 @@ where self.insert_struct.query } } - #[cfg(test)] mod tests { use sea_query::OnConflict; use crate::tests_cfg::cake::{self, ActiveModel}; - use crate::{ - ActiveValue, DbBackend, DbErr, EntityTrait, Insert, InsertTrait, IntoActiveModel, - QueryTrait, - }; + use crate::{ActiveValue, DbBackend, DbErr, EntityTrait, Insert, IntoActiveModel, QueryTrait}; #[test] fn insert_1() { diff --git a/src/query/traits.rs b/src/query/traits.rs index abf53b9a5..a0e0a6461 100644 --- a/src/query/traits.rs +++ b/src/query/traits.rs @@ -1,7 +1,4 @@ -use crate::{ - ActiveModelTrait, ColumnTrait, DbBackend, IntoActiveModel, IntoIdentity, IntoSimpleExpr, - QuerySelect, Statement, -}; +use crate::{ColumnTrait, DbBackend, IntoIdentity, IntoSimpleExpr, QuerySelect, Statement}; use sea_query::QueryStatementBuilder; /// A Trait for any type performing queries on a Model or ActiveModel @@ -91,95 +88,3 @@ where QuerySelect::column_as(self, col, alias) } } - -/// Insert query Trait -pub trait InsertTrait: Sized -where - A: ActiveModelTrait, -{ - /// required function for new self - fn new() -> Self; - - /// required function for add value - fn add(self, m: M) -> Self - where - M: IntoActiveModel; - - /// Insert one Model or ActiveModel - /// - /// Model - /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; - /// - /// assert_eq!( - /// Insert::one(cake::Model { - /// id: 1, - /// name: "Apple Pie".to_owned(), - /// }) - /// .build(DbBackend::Postgres) - /// .to_string(), - /// r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie')"#, - /// ); - /// ``` - /// ActiveModel - /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; - /// - /// assert_eq!( - /// Insert::one(cake::ActiveModel { - /// id: NotSet, - /// name: Set("Apple Pie".to_owned()), - /// }) - /// .build(DbBackend::Postgres) - /// .to_string(), - /// r#"INSERT INTO "cake" ("name") VALUES ('Apple Pie')"#, - /// ); - /// ``` - fn one(m: M) -> Self - where - M: IntoActiveModel, - { - Self::new().add(m) - } - - /// Insert many Model or ActiveModel - /// - /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; - /// - /// assert_eq!( - /// Insert::many([ - /// cake::Model { - /// id: 1, - /// name: "Apple Pie".to_owned(), - /// }, - /// cake::Model { - /// id: 2, - /// name: "Orange Scone".to_owned(), - /// } - /// ]) - /// .build(DbBackend::Postgres) - /// .to_string(), - /// r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie'), (2, 'Orange Scone')"#, - /// ); - /// ``` - fn many(models: I) -> Self - where - M: IntoActiveModel, - I: IntoIterator, - { - Self::new().add_many(models) - } - - /// Add many Models to Self - fn add_many(mut self, models: I) -> Self - where - M: IntoActiveModel, - I: IntoIterator, - { - for model in models.into_iter() { - self = self.add(model); - } - self - } -}