Как я могу динамически возвращать выражение равенства Дизеля из вспомогательного метода?

У меня есть перечисление:

enum Role {
    Administrator,
    Sponsor,
    Bot,
}

и у меня есть таблица в моей базе данных под названием «роли», которая выглядит так:

| user_id | administrator | sponsor | bot   |
|---------|---------------|---------|-------|
| 0       | true          | false   | false |
| 1       | false         | false   | false |
| 2       | false         | true    | true  |

Как выполнить сопоставление Role и получить соответствующий столбец в моей базе данных, используя Diesel, а не необработанный SQL запрос?

Я могу вернуть выражение Diesel из метода, который соответствует роли, но он не работает с моим оператором вставки:

#[macro_use]
extern crate diesel;

use diesel::{
    mysql::{Mysql, MysqlConnection},
    sql_types::{Bool, Nullable},
    BoxableExpression, Connection, ExpressionMethods, RunQueryDsl,
};
use schema::{roles, users};
use models::{NewUser};

use std::error::Error;

mod schema {
    table! {
        roles (user_id) {
            id -> Unsigned<Bigint>,
            user_id -> Unsigned<Bigint>,
            administrator -> Nullable<Bool>,
            sponsor -> Nullable<Bool>,
            bot -> Nullable<Bool>,
        }
    }

    table! {
        users (id) {
            id -> Unsigned<Bigint>,
            username -> Nullable<Varchar>,
        }
    }

    allow_tables_to_appear_in_same_query!(
        roles,
        users,
    );
}

mod models {
    use super::schema::users;

    #[derive(Insertable, PartialEq, Debug, Default)]
    #[table_name = "users"]
    pub struct NewUser<'a> {
        username: &'a str,
    }

    impl<'a> NewUser<'a> {
        pub fn new(username: &'a str) -> Self {
            Self {
                username
            }
        }
    }
}

enum Role {
    Administrator,
    Sponsor,
    Bot,
}

impl From<&Role> for Box<dyn BoxableExpression<roles::table, Mysql, SqlType = Nullable<Bool>>> {
    fn from(r: &Role) -> Self {
        match r {
            Role::Administrator => Box::new(roles::dsl::administrator),
            Role::Sponsor => Box::new(roles::dsl::sponsor),
            Role::Bot => Box::new(roles::dsl::bot),
        }
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let conn = MysqlConnection::establish("mysql://localhost/stquestion")?;

    diesel::replace_into(users::table)
        .values(&NewUser::new("test_account"))
        .execute(&conn)?;
    diesel::replace_into(roles::table)
        .values((
            roles::dsl::user_id.eq(1),
            <&Role as Into<
                Box<dyn BoxableExpression<roles::table, Mysql, SqlType = Nullable<Bool>>>,
            >>::into(&Role::Administrator)
            .eq(true),
        ))
        .execute(&conn)?;

    Ok(())
}

Приведенный выше код оставляет меня со следующей ошибкой компиляции:

error[E0277]: the trait bound `std::boxed::Box<dyn diesel::expression::BoxableExpression<stackoverflow_questions::
schema::roles::table, diesel::mysql::backend::Mysql, SqlType = diesel::sql_types::Nullable<diesel::sql_types::Bool
>>>: diesel::query_source::Column` is not satisfied
  --> src/main.rs:36:17
   |
36 |           .values((
   |  _________________^
37 | |             roles::dsl::user_id.eq(1),
38 | |             <&Role as Into<
39 | |                 Box<dyn BoxableExpression<roles::table, Mysql, SqlType = Nullable<Bool>>>,
40 | |             >>::into(&Role::Administrator)
41 | |             .eq(true),
42 | |         ))
   | |_________^ the trait `diesel::query_source::Column` is not implemented for `std::boxed::Box<dyn diesel::expr
ession::BoxableExpression<stackoverflow_questions::schema::roles::table, diesel::mysql::backend::Mysql, SqlType =
diesel::sql_types::Nullable<diesel::sql_types::Bool>>>`
   |
   = note: required because of the requirements on the impl of `diesel::insertable::Insertable<stackoverflow_quest
ions::schema::roles::table>` for `diesel::expression::operators::Eq<std::boxed::Box<dyn diesel::expression::Boxabl
eExpression<stackoverflow_questions::schema::roles::table, diesel::mysql::backend::Mysql, SqlType = diesel::sql_ty
pes::Nullable<diesel::sql_types::Bool>>>, diesel::expression::bound::Bound<diesel::sql_types::Nullable<diesel::sql
_types::Bool>, bool>>`
   = note: required because of the requirements on the impl of `diesel::insertable::Insertable<stackoverflow_quest
ions::schema::roles::table>` for `(diesel::expression::operators::Eq<stackoverflow_questions::schema::roles::colum
ns::user_id, diesel::expression::bound::Bound<diesel::mysql::types::Unsigned<diesel::sql_types::BigInt>, u64>>, di
esel::expression::operators::Eq<std::boxed::Box<dyn diesel::expression::BoxableExpression<stackoverflow_questions:
:schema::roles::table, diesel::mysql::backend::Mysql, SqlType = diesel::sql_types::Nullable<diesel::sql_types::Boo
l>>>, diesel::expression::bound::Bound<diesel::sql_types::Nullable<diesel::sql_types::Bool>, bool>>)`
>error[E0277]: the trait bound `std::boxed::Box<dyn diesel::expression::BoxableExpression<stackoverflow_questions::schema::roles::table, diesel::mysql::backend::Mysql, SqlType = diesel::sql_types::Nullable<diesel::sql_types::Bool>>>: diesel::query_source::Column` is not satisfied
  --> src/main.rs:43:10
   |
43 |         .execute(&conn)?;
   |          ^^^^^^^ the trait `diesel::query_source::Column` is not implemented for `std::boxed::Box<dyn diesel::expression::BoxableExpression<stackoverflow_questions::schema::roles::table, diesel::mysql::backend::Mysql, SqlType = diesel::sql_types::Nullable<diesel::sql_types::Bool>>>`
   |
   = note: required because of the requirements on the impl of `diesel::insertable::InsertValues<stackoverflow_questions::schema::roles::table, diesel::mysql::backend::Mysql>` for `diesel::insertable::ColumnInsertValue<std::boxed::Box<dyn diesel::expression::BoxableExpression<stackoverflow_questions::schema::roles::table, diesel::mysql::backend::Mysql, SqlType = diesel::sql_types::Nullable<diesel::sql_types::Bool>>>, diesel::expression::bound::Bound<diesel::sql_types::Nullable<diesel::sql_types::Bool>, bool>>`
   = note: required because of the requirements on the impl of `diesel::insertable::InsertValues<stackoverflow_questions::schema::roles::table, diesel::mysql::backend::Mysql>` for `(diesel::insertable::ColumnInsertValue<stackoverflow_questions::schema::roles::columns::user_id, diesel::expression::bound::Bound<diesel::mysql::types::Unsigned<diesel::sql_types::BigInt>, u64>>, diesel::insertable::ColumnInsertValue<std::boxed::Box<dyn diesel::expression::BoxableExpression<stackoverflow_questions::schema::roles::table, diesel::mysql::backend::Mysql, SqlType = diesel::sql_types::Nullable<diesel::sql_types::Bool>>>, diesel::expression::bound::Bound<diesel::sql_types::Nullable<diesel::sql_types::Bool>, bool>>)`
   = note: required because of the requirements on the impl of `diesel::query_builder::QueryFragment<diesel::mysql::backend::Mysql>` for `diesel::query_builder::insert_statement::ValuesClause<(diesel::insertable::ColumnInsertValue<stackoverflow_questions::schema::roles::columns::user_id, diesel::expression::bound::Bound<diesel::mysql::types::Unsigned<diesel::sql_types::BigInt>, u64>>, diesel::insertable::ColumnInsertValue<std::boxed::Box<dyn diesel::expression::BoxableExpression<stackoverflow_questions::schema::roles::table, diesel::mysql::backend::Mysql, SqlType = diesel::sql_types::Nullable<diesel::sql_types::Bool>>>, diesel::expression::bound::Bound<diesel::sql_types::Nullable<diesel::sql_types::Bool>, bool>>), stackoverflow_questions::schema::roles::table>`
   = note: required because of the requirements on the impl of `diesel::query_builder::QueryFragment<diesel::mysql::backend::Mysql>` for `diesel::query_builder::insert_statement::InsertStatement<stackoverflow_questions::schema::roles::table, diesel::query_builder::insert_statement::ValuesClause<(diesel::insertable::ColumnInsertValue<stackoverflow_questions::schema::roles::columns::user_id, diesel::expression::bound::Bound<diesel::mysql::types::Unsigned<diesel::sql_types::BigInt>, u64>>, diesel::insertable::ColumnInsertValue<std::boxed::Box<dyn diesel::expression::BoxableExpression<stackoverflow_questions::schema::roles::table, diesel::mysql::backend::Mysql, SqlType = diesel::sql_types::Nullable<diesel::sql_types::Bool>>>, diesel::expression::bound::Bound<diesel::sql_types::Nullable<diesel::sql_types::Bool>, bool>>), stackoverflow_questions::schema::roles::table>, diesel::query_builder::insert_statement::Replace>`
   = note: required because of the requirements on the impl of `diesel::query_dsl::load_dsl::ExecuteDsl<_, diesel::mysql::backend::Mysql>` for `diesel::query_builder::insert_statement::InsertStatement<stackoverflow_questions::schema::roles::table, diesel::query_builder::insert_statement::ValuesClause<(diesel::insertable::ColumnInsertValue<stackoverflow_questions::schema::roles::columns::user_id, diesel::expression::bound::Bound<diesel::mysql::types::Unsigned<diesel::sql_types::BigInt>, u64>>, diesel::insertable::ColumnInsertValue<std::boxed::Box<dyn diesel::expression::BoxableExpression<stackoverflow_questions::schema::roles::table, diesel::mysql::backend::Mysql, SqlType = diesel::sql_types::Nullable<diesel::sql_types::Bool>>>, diesel::expression::bound::Bound<diesel::sql_types::Nullable<diesel::sql_types::Bool>, bool>>), stackoverflow_questions::schema::roles::table>, diesel::query_builder::insert_statement::Replace>`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0277`.
error: could not compile `stackoverflow-questions`.

To learn more, run the command again with --verbose.

Я создал воспроизводимый пример для этой проблемы здесь.


person Dowland Aiello    schedule 04.05.2020    source источник
comment
Трудно ответить на ваш вопрос, потому что он не включает минимально воспроизводимый пример. Мы не можем сказать, какие крейты (и их версии), типы, трейты, поля и т. д. присутствуют в коде. Нам было бы легче помочь вам, если вы попытаетесь воспроизвести свою ошибку в совершенно новом проекте Cargo, а затем изменить ваш вопрос, чтобы включить дополнительную информацию. Существуют специфичные для Rust и Diesel- конкретные советы по MRE, которые вы можете использовать, чтобы уменьшить исходный код для размещения здесь. Спасибо!   -  person Shepmaster    schedule 04.05.2020
comment
Пожалуйста, отредактируйте свой вопрос и вставьте точную и полную ошибку, которую вы получаете - это поможет нам понять, в чем проблема. так что мы можем помочь лучше всего. Иногда интерпретировать сообщение об ошибке сложно, и на самом деле важна другая часть сообщения об ошибке. Пожалуйста, используйте сообщение от непосредственного запуска компилятора, а не сообщение, созданное IDE, которая может пытаться интерпретировать ошибку для вас.   -  person Shepmaster    schedule 04.05.2020
comment
Обновление: я прикрепил ссылку на репозиторий GitHub с файлом Cargo.toml и добавил минимальный воспроизводимый пример к своему исходному вопросу. Спасибо за помощь.   -  person Dowland Aiello    schedule 04.05.2020


Ответы (1)


На мой взгляд, использование BoxableExpression здесь неправильно. Гораздо проще просто реализовать Insertable<roles::table> вручную для вашего перечисления, а затем напрямую вставить оттуда значения.

Используя этот Insertable импл

impl Insertable<roles::table> for Role {
    type Values = <(
        diesel::dsl::Eq<roles::administrator, bool>,
        diesel::dsl::Eq<roles::sponsor, bool>,
        diesel::dsl::Eq<roles::bot, bool>,
    ) as Insertable<roles::table>>::Values;

    fn values(self) -> Self::Values {
        use crate::schema::roles;

        match self {
            Role::Administrator => (
                roles::administrator.eq(true),
                roles::sponsor.eq(false),
                roles::bot.eq(false),
            ),
            Role::Sponsor => (
                roles::administrator.eq(false),
                roles::sponsor.eq(true),
                roles::bot.eq(true),
            ),
            Role::Bot => (
                roles::administrator.eq(false),
                roles::sponsor.eq(false),
                roles::bot.eq(true),
            ),
        }
        .values()
    }
}

запрос на вставку можно написать следующим образом:

    diesel::replace_into(roles::table)
        .values((roles::dsl::user_id.eq(1), Role::Administrator))
        .execute(&conn)?;
person weiznich    schedule 05.05.2020
comment
Но будет ли это работать, если я хочу обновить только одно поле, оставив остальные нетронутыми? - person Dowland Aiello; 06.05.2020
comment
В таком случае реализацию Insertable нужно будет немного изменить, но в остальном да. (В основном вам нужно изменить кортеж, возвращаемый функцией values, на (Option<Eq<…>>, Option<Eq<…>>, Option<Eq<…>>) и возвращать значения None вместо .eq(false).) - person weiznich; 06.05.2020