Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MySQL insert on conflict do nothing #2244

Merged
merged 3 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
9 changes: 8 additions & 1 deletion src/query/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,14 @@ where
}

// helper function for do_nothing in Insert<A>
pub fn from_insert(insert: Insert<A>) -> Self {
pub fn from_insert(mut insert: Insert<A>) -> Self {
let primary_keys = <A::Entity as EntityTrait>::PrimaryKey::iter();
insert.query.on_conflict(
OnConflict::columns(primary_keys.clone())
.do_nothing_on(primary_keys)
.to_owned(),
);

Copy link
Member

@tyt2y3 tyt2y3 Jun 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this code belongs here. on_empty_do_nothing does not mean on_conflict_do_nothing. on_empty_do_nothing simply allows us to insert empty array.

we can create a method for on_conflict_do_nothing that specifically means on PK conflict

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! 27ce67e

Self {
insert_struct: insert,
}
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_empty_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
Loading