Skip to content

Commit

Permalink
MySQL insert on conflict do nothing (#2244)
Browse files Browse the repository at this point in the history
* MySQL insert on conflict do nothing

* on_conflict_do_nothing

* clippy
  • Loading branch information
billy1624 authored Jun 19, 2024
1 parent ad9f3c4 commit b4506c0
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 5 deletions.
13 changes: 11 additions & 2 deletions src/executor/insert.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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)?
}
};
Expand Down
15 changes: 15 additions & 0 deletions src/query/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
9 changes: 8 additions & 1 deletion tests/empty_insert_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions tests/upsert_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand All @@ -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) },
Expand Down

0 comments on commit b4506c0

Please sign in to comment.