From 08bb864f2e0d789a63496ae633cfd58d3ea0ae42 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 3 Jun 2024 14:13:57 +0800 Subject: [PATCH] MySQL insert on conflict do nothing --- src/executor/insert.rs | 13 +++++++++++-- src/query/insert.rs | 9 ++++++++- tests/empty_insert_tests.rs | 9 ++++++++- tests/upsert_tests.rs | 5 +++-- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 2b7168388..665fa5bee 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, 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::::try_from_u64(last_insert_id).map_err(|_| DbErr::UnpackInsertId)? } }; diff --git a/src/query/insert.rs b/src/query/insert.rs index 3e26e0110..6c5e55b2c 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -318,7 +318,14 @@ where } // helper function for do_nothing in Insert - pub fn from_insert(insert: Insert) -> Self { + pub fn from_insert(mut insert: Insert) -> Self { + let primary_keys = ::PrimaryKey::iter(); + insert.query.on_conflict( + OnConflict::columns(primary_keys.clone()) + .do_nothing_on(primary_keys) + .to_owned(), + ); + Self { insert_struct: insert, } diff --git a/tests/empty_insert_tests.rs b/tests/empty_insert_tests.rs index 46642fa2e..b4123d54f 100644 --- a/tests/empty_insert_tests.rs +++ b/tests/empty_insert_tests.rs @@ -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::()) .on_empty_do_nothing() .exec(db) diff --git a/tests/upsert_tests.rs b/tests/upsert_tests.rs index 4879e4788..84e8b88d3 100644 --- a/tests/upsert_tests.rs +++ b/tests/upsert_tests.rs @@ -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) },