diesel-rs/diesel

table with more than one primary_key can not use on_conflict().do_update()

istudyatuni opened this issue · 1 comments

This is similar to #3872

Setup

Code that doesn't work

diesel::table! {
    categories (id, user_id) {
        id -> BigInt,
        created_at -> BigInt,
        sort_key -> Integer,
        title -> Text,
        order -> Text,
        user_id -> Integer,
        track -> Bool,
        show_in_lib -> Bool,
        deleted_at -> BigInt,
    }
}

#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset)]
#[diesel(
    table_name = crate::db::schema::categories,
    check_for_backend(diesel::mysql::Mysql),
    primary_key(id, user_id)
)]
pub struct Category {
    pub id: i64,
    pub created_at: Time,
    pub sort_key: i32,
    pub title: String,
    pub order: String,
    pub user_id: UserID,
    pub track: bool,
    pub show_in_lib: bool,
    pub deleted_at: Time,
}

diesel::insert_into(categories)
    .values(&category)
    .on_conflict(diesel::dsl::DuplicatedKeys)
    .do_update()
    .set(&category)
    .execute(conn)?;

// this is also doesn't work
diesel::insert_into(categories)
    .values(&category)
    .on_conflict_do_nothing()
    .execute(conn)?;

// and this
diesel::insert_into(categories)
    .values(&category)
    .on_conflict(diesel::dsl::DuplicatedKeys)
    .do_nothing()
    .execute(conn)?;

// and this
diesel::insert_into(categories)
    .values(&category)
    .on_conflict((id, user_id))
    .do_nothing()
    .execute(conn)?;

First 3 cases compiles when I define only one-column primary key, except 4th:

diesel::insert_into(categories)
    .values(&category)
    .on_conflict(id)
    .do_nothing()
    .execute(conn)?;

Schema is in src/db/schema.rs, models in src/models/db.rs, code, that work with db, in src/db/conn.rs::add_category()

Versions

  • Rust: 1.77.2
  • Diesel: 2.1.6
  • Database: MySQL
  • Operating System Linux

Feature Flags

  • diesel: r2d2, mysql

Problem Description

What are you trying to accomplish?

Insert value, and if it exists, update values, except primary keys. I use insert_into + on_conflict because replace_into doesn't work in MySQL (it works in SQLite)

What is the expected output?

Compiles

What is the actual output?

Log

For first case

error[E0277]: the trait bound `query_builder::upsert::on_conflict_clause::OnConflictValues<query_builder::insert_statement::ValuesClause<(DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::id, expression::bound::Bound<diesel::sql_types::BigInt, &i64>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::created_at, expression::bound::Bound<diesel::sql_types::BigInt, &i64>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::sort_key, expression::bound::Bound<diesel::sql_types::Integer, &i32>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::title, expression::bound::Bound<diesel::sql_types::Text, &std::string::String>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::order, expression::bound::Bound<diesel::sql_types::Text, &std::string::String>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::user_id, expression::bound::Bound<diesel::sql_types::Integer, &i32>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::track, expression::bound::Bound<diesel::sql_types::Bool, &bool>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::show_in_lib, expression::bound::Bound<diesel::sql_types::Bool, &bool>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::deleted_at, expression::bound::Bound<diesel::sql_types::BigInt, &i64>>>), categories::table>, query_builder::upsert::on_conflict_target::ConflictTarget<DuplicatedKeys>, query_builder::upsert::on_conflict_actions::DoUpdate<(query_builder::update_statement::changeset::Assign<query_builder::update_statement::changeset::ColumnWrapperForUpdate<categories::columns::created_at>, expression::bound::Bound<diesel::sql_types::BigInt, &i64>>, query_builder::update_statement::changeset::Assign<query_builder::update_statement::changeset::ColumnWrapperForUpdate<categories::columns::sort_key>, expression::bound::Bound<diesel::sql_types::Integer, &i32>>, query_builder::update_statement::changeset::Assign<query_builder::update_statement::changeset::ColumnWrapperForUpdate<categories::columns::title>, expression::bound::Bound<diesel::sql_types::Text, &std::string::String>>, query_builder::update_statement::changeset::Assign<query_builder::update_statement::changeset::ColumnWrapperForUpdate<categories::columns::order>, expression::bound::Bound<diesel::sql_types::Text, &std::string::String>>, query_builder::update_statement::changeset::Assign<query_builder::update_statement::changeset::ColumnWrapperForUpdate<categories::columns::track>, expression::bound::Bound<diesel::sql_types::Bool, &bool>>, query_builder::update_statement::changeset::Assign<query_builder::update_statement::changeset::ColumnWrapperForUpdate<categories::columns::show_in_lib>, expression::bound::Bound<diesel::sql_types::Bool, &bool>>, query_builder::update_statement::changeset::Assign<query_builder::update_statement::changeset::ColumnWrapperForUpdate<categories::columns::deleted_at>, expression::bound::Bound<diesel::sql_types::BigInt, &i64>>), categories::table>>: QueryFragment<Mysql, mysql::backend::MysqlOnConflictClause>` is not satisfied
    --> src/db/conn.rs:217:22
     |
217  |             .execute(conn)?;
     |              ------- ^^^^ the trait `QueryFragment<Mysql, mysql::backend::MysqlOnConflictClause>` is not implemented for `OnConflictValues<ValuesClause<(DefaultableColumnInsertValue<...>, ..., ..., ..., ..., ..., ..., ..., ...), ...>, ..., ...>`, which is required by `InsertStatement<categories::table, query_builder::upsert::on_conflict_clause::OnConflictValues<query_builder::insert_statement::ValuesClause<(DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::id, expression::bound::Bound<diesel::sql_types::BigInt, &i64>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::created_at, expression::bound::Bound<diesel::sql_types::BigInt, &i64>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::sort_key, expression::bound::Bound<diesel::sql_types::Integer, &i32>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::title, expression::bound::Bound<diesel::sql_types::Text, &std::string::String>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::order, expression::bound::Bound<diesel::sql_types::Text, &std::string::String>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::user_id, expression::bound::Bound<diesel::sql_types::Integer, &i32>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::track, expression::bound::Bound<diesel::sql_types::Bool, &bool>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::show_in_lib, expression::bound::Bound<diesel::sql_types::Bool, &bool>>>, DefaultableColumnInsertValue<ColumnInsertValue<categories::columns::deleted_at, expression::bound::Bound<diesel::sql_types::BigInt, &i64>>>), categories::table>, query_builder::upsert::on_conflict_target::ConflictTarget<DuplicatedKeys>, query_builder::upsert::on_conflict_actions::DoUpdate<(query_builder::update_statement::changeset::Assign<query_builder::update_statement::changeset::ColumnWrapperForUpdate<categories::columns::created_at>, expression::bound::Bound<diesel::sql_types::BigInt, &i64>>, query_builder::update_statement::changeset::Assign<query_builder::update_statement::changeset::ColumnWrapperForUpdate<categories::columns::sort_key>, expression::bound::Bound<diesel::sql_types::Integer, &i32>>, query_builder::update_statement::changeset::Assign<query_builder::update_statement::changeset::ColumnWrapperForUpdate<categories::columns::title>, expression::bound::Bound<diesel::sql_types::Text, &std::string::String>>, query_builder::update_statement::changeset::Assign<query_builder::update_statement::changeset::ColumnWrapperForUpdate<categories::columns::order>, expression::bound::Bound<diesel::sql_types::Text, &std::string::String>>, query_builder::update_statement::changeset::Assign<query_builder::update_statement::changeset::ColumnWrapperForUpdate<categories::columns::track>, expression::bound::Bound<diesel::sql_types::Bool, &bool>>, query_builder::update_statement::changeset::Assign<query_builder::update_statement::changeset::ColumnWrapperForUpdate<categories::columns::show_in_lib>, expression::bound::Bound<diesel::sql_types::Bool, &bool>>, query_builder::update_statement::changeset::Assign<query_builder::update_statement::changeset::ColumnWrapperForUpdate<categories::columns::deleted_at>, expression::bound::Bound<diesel::sql_types::BigInt, &i64>>), categories::table>>>: ExecuteDsl<_, _>`
     |              |
     |              required by a bound introduced by this call
     |
     = help: the following other types implement trait `QueryFragment<DB, SP>`:
               <query_builder::upsert::on_conflict_clause::OnConflictValues<Values, Target, Action> as QueryFragment<Mysql, mysql::backend::MysqlOnConflictClause>>
               <query_builder::upsert::on_conflict_clause::OnConflictValues<Values, Target, Action> as QueryFragment<DB>>
               <query_builder::upsert::on_conflict_clause::OnConflictValues<Values, Target, Action> as QueryFragment<DB, SD>>
               <query_builder::upsert::on_conflict_clause::OnConflictValues<Values, Target, Action, query_builder::where_clause::WhereClause<Expr>> as QueryFragment<DB>>
     = note: required for `OnConflictValues<ValuesClause<(DefaultableColumnInsertValue<...>, ..., ..., ..., ..., ..., ..., ..., ...), ...>, ..., ...>` to implement `QueryFragment<Mysql>`
     = note: the full type name has been written to '/[..]/kotync/target/debug/deps/kotync-dba6149db5684457.long-type-16164201235515559885.txt'
     = note: 1 redundant requirement hidden
     = note: required for `InsertStatement<table, OnConflictValues<ValuesClause<(..., ..., ..., ..., ..., ..., ..., ..., ...), ...>, ..., ...>>` to implement `QueryFragment<Mysql>`
     = note: the full type name has been written to '/[..]/kotync/target/debug/deps/kotync-dba6149db5684457.long-type-16831793125519393571.txt'
     = note: required for `InsertStatement<table, OnConflictValues<ValuesClause<(..., ..., ..., ..., ..., ..., ..., ..., ...), ...>, ..., ...>>` to implement `ExecuteDsl<PooledConnection<ConnectionManager<MysqlConnection>>, Mysql>`
     = note: the full type name has been written to '/[..]/kotync/target/debug/deps/kotync-dba6149db5684457.long-type-16831793125519393571.txt'
note: required by a bound in `diesel::RunQueryDsl::execute`
    --> /[..]/src/index.crates.io-6f17d22bba15001f/diesel-2.1.6/src/query_dsl/mod.rs:1431:15
     |
1428 |     fn execute(self, conn: &mut Conn) -> QueryResult<usize>
     |        ------- required by a bound in this associated function
...
1431 |         Self: methods::ExecuteDsl<Conn>,
     |               ^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RunQueryDsl::execute`

For more information about this error, try `rustc --explain E0277`.

Are you seeing any additional errors?

No

Steps to reproduce

git clone -b mysql https://github.com/istudyatuni/kotync.git
cd kotync
cargo c

Checklist

  • This issue can be reproduced on Rust's stable channel. (Your issue will be
    closed if this is not the case)
  • This issue can be reproduced without requiring a third party crate (I'm not sure, but I don't use third-party crate for DB, except diesel_migrations)

Thanks for opening this bug report. This is literally a duplicate of #3872 as this error does not happen with the master branch anymore. It seems like nobody cared enough about that bug to get the fix backported into some recent patch release, so it will be part of the next feature release by default.

As a site note: It's expected that this will not work:

diesel::insert_into(categories)
    .values(&category)
    .on_conflict((id, user_id))
    .do_nothing()
    .execute(conn)?;

as mysql just does not allow to list explicit keys as conflict target.