Make insert statement not panic when inserting nothing (#1708)
* end-of-day commit (WIP) * progress commit (WIP) * refactored and added InsertAttempt * async asjusting * completed implementation for insertAttempt in execution Added in tests for insertAttempt * updated wording for new INSERT type * removed InsertTrait
This commit is contained in:
parent
7c6ab8fa2d
commit
92ea837cdf
@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
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};
|
||||
@ -26,6 +27,71 @@ where
|
||||
pub last_insert_id: <<<A as ActiveModelTrait>::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType,
|
||||
}
|
||||
|
||||
/// The types of results for an INSERT operation
|
||||
#[derive(Debug)]
|
||||
pub enum TryInsertResult<T> {
|
||||
/// The INSERT operation did not insert any value
|
||||
Empty,
|
||||
/// Reserved
|
||||
Conflicted,
|
||||
/// Successfully inserted
|
||||
Inserted(T),
|
||||
}
|
||||
|
||||
impl<A> TryInsert<A>
|
||||
where
|
||||
A: ActiveModelTrait,
|
||||
{
|
||||
/// Execute an insert operation
|
||||
#[allow(unused_mut)]
|
||||
pub async fn exec<'a, C>(self, db: &'a C) -> TryInsertResult<Result<InsertResult<A>, DbErr>>
|
||||
where
|
||||
C: ConnectionTrait,
|
||||
A: 'a,
|
||||
{
|
||||
if self.insert_struct.columns.is_empty() {
|
||||
TryInsertResult::Empty
|
||||
} else {
|
||||
TryInsertResult::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 async fn exec_without_returning<'a, C>(
|
||||
self,
|
||||
db: &'a C,
|
||||
) -> TryInsertResult<Result<u64, DbErr>>
|
||||
where
|
||||
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
|
||||
C: ConnectionTrait,
|
||||
A: 'a,
|
||||
{
|
||||
if self.insert_struct.columns.is_empty() {
|
||||
TryInsertResult::Empty
|
||||
} else {
|
||||
TryInsertResult::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 async fn exec_with_returning<'a, C>(
|
||||
self,
|
||||
db: &'a C,
|
||||
) -> TryInsertResult<Result<<A::Entity as EntityTrait>::Model, DbErr>>
|
||||
where
|
||||
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
|
||||
C: ConnectionTrait,
|
||||
A: 'a,
|
||||
{
|
||||
if self.insert_struct.columns.is_empty() {
|
||||
TryInsertResult::Empty
|
||||
} else {
|
||||
TryInsertResult::Inserted(self.insert_struct.exec_with_returning(db).await)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> Insert<A>
|
||||
where
|
||||
A: ActiveModelTrait,
|
||||
|
@ -72,7 +72,7 @@ where
|
||||
/// r#"INSERT INTO "cake" ("name") VALUES ('Apple Pie')"#,
|
||||
/// );
|
||||
/// ```
|
||||
pub fn one<M>(m: M) -> Insert<A>
|
||||
pub fn one<M>(m: M) -> Self
|
||||
where
|
||||
M: IntoActiveModel<A>,
|
||||
{
|
||||
@ -208,6 +208,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) -> TryInsert<A>
|
||||
where
|
||||
A: ActiveModelTrait,
|
||||
{
|
||||
TryInsert::from_insert(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> QueryTrait for Insert<A>
|
||||
@ -229,11 +238,108 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs INSERT operations on a ActiveModel, will do nothing if input is empty.
|
||||
///
|
||||
/// All functions works the same as if it is Insert<A>. Please refer to Insert<A> page for more information
|
||||
#[derive(Debug)]
|
||||
pub struct TryInsert<A>
|
||||
where
|
||||
A: ActiveModelTrait,
|
||||
{
|
||||
pub(crate) insert_struct: Insert<A>,
|
||||
}
|
||||
|
||||
impl<A> Default for TryInsert<A>
|
||||
where
|
||||
A: ActiveModelTrait,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
impl<A> TryInsert<A>
|
||||
where
|
||||
A: ActiveModelTrait,
|
||||
{
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
insert_struct: Insert::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn one<M>(m: M) -> Self
|
||||
where
|
||||
M: IntoActiveModel<A>,
|
||||
{
|
||||
Self::new().add(m)
|
||||
}
|
||||
|
||||
pub fn many<M, I>(models: I) -> Self
|
||||
where
|
||||
M: IntoActiveModel<A>,
|
||||
I: IntoIterator<Item = M>,
|
||||
{
|
||||
Self::new().add_many(models)
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn add<M>(mut self, m: M) -> Self
|
||||
where
|
||||
M: IntoActiveModel<A>,
|
||||
{
|
||||
self.insert_struct = self.insert_struct.add(m);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_many<M, I>(mut self, models: I) -> Self
|
||||
where
|
||||
M: IntoActiveModel<A>,
|
||||
I: IntoIterator<Item = M>,
|
||||
{
|
||||
for model in models.into_iter() {
|
||||
self.insert_struct = self.insert_struct.add(model);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
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<A>
|
||||
pub fn from_insert(insert: Insert<A>) -> Self {
|
||||
Self {
|
||||
insert_struct: insert,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> QueryTrait for TryInsert<A>
|
||||
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::tests_cfg::cake::{self, ActiveModel};
|
||||
use crate::{ActiveValue, DbBackend, DbErr, EntityTrait, Insert, IntoActiveModel, QueryTrait};
|
||||
|
||||
#[test]
|
||||
|
54
tests/empty_insert_tests.rs
Normal file
54
tests/empty_insert_tests.rs
Normal file
@ -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, TryInsertResult};
|
||||
|
||||
#[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, TryInsertResult::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, TryInsertResult::Empty));
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user