MySQL insert on conflict do nothing (#2244)

* MySQL insert on conflict do nothing

* on_conflict_do_nothing

* clippy
This commit is contained in:
Billy Chan 2024-06-19 20:09:30 +08:00 committed by GitHub
parent ad9f3c4112
commit b4506c0647
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 37 additions and 5 deletions

View File

@ -1,6 +1,7 @@
use crate::{
error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, Insert, IntoActiveModel,
Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, SelectModel, SelectorRaw, TryFromU64, TryInsert,
error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, DbBackend, EntityTrait, Insert,
IntoActiveModel, Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, SelectModel, SelectorRaw,
TryFromU64, TryInsert,
};
use sea_query::{FromValueTuple, Iden, InsertStatement, Query, ValueTuple};
use std::{future::Future, marker::PhantomData};
@ -245,6 +246,14 @@ where
return Err(DbErr::RecordNotInserted);
}
let last_insert_id = res.last_insert_id();
// For MySQL, the affected-rows number:
// - The affected-rows value per row is `1` if the row is inserted as a new row,
// - `2` if an existing row is updated,
// - and `0` if an existing row is set to its current values.
// Reference: https://dev.mysql.com/doc/refman/8.4/en/insert-on-duplicate.html
if db_backend == DbBackend::MySql && last_insert_id == 0 {
return Err(DbErr::RecordNotInserted);
}
ValueTypeOf::<A>::try_from_u64(last_insert_id).map_err(|_| DbErr::UnpackInsertId)?
}
};

View File

@ -225,6 +225,21 @@ where
{
TryInsert::from_insert(self)
}
/// On conflict do nothing
pub fn on_conflict_do_nothing(mut self) -> TryInsert<A>
where
A: ActiveModelTrait,
{
let primary_keys = <A::Entity as EntityTrait>::PrimaryKey::iter();
self.query.on_conflict(
OnConflict::columns(primary_keys.clone())
.do_nothing_on(primary_keys)
.to_owned(),
);
TryInsert::from_insert(self)
}
}
impl<A> QueryTrait for Insert<A>

View File

@ -34,12 +34,19 @@ pub async fn test(db: &DbConn) {
assert!(matches!(res, Ok(TryInsertResult::Inserted(_))));
let _double_seaside_bakery = bakery::ActiveModel {
let double_seaside_bakery = bakery::ActiveModel {
name: Set("SeaSide Bakery".to_owned()),
profit_margin: Set(10.4),
id: Set(1),
};
let conflict_insert = Bakery::insert_many([double_seaside_bakery])
.on_conflict_do_nothing()
.exec(db)
.await;
assert!(matches!(conflict_insert, Ok(TryInsertResult::Conflicted)));
let empty_insert = Bakery::insert_many(std::iter::empty::<bakery::ActiveModel>())
.on_empty_do_nothing()
.exec(db)

View File

@ -9,7 +9,6 @@ use sea_orm::TryInsertResult;
use sea_orm::{sea_query::OnConflict, Set};
#[sea_orm_macros::test]
#[cfg(feature = "sqlx-postgres")]
async fn main() -> Result<(), DbErr> {
let ctx = TestContext::new("upsert_tests").await;
create_tables(&ctx.db).await?;
@ -22,7 +21,9 @@ async fn main() -> Result<(), DbErr> {
pub async fn create_insert_default(db: &DatabaseConnection) -> Result<(), DbErr> {
use insert_default::*;
let on_conflict = OnConflict::column(Column::Id).do_nothing().to_owned();
let on_conflict = OnConflict::column(Column::Id)
.do_nothing_on([Column::Id])
.to_owned();
let res = Entity::insert_many([
ActiveModel { id: Set(1) },