如何从辅助方法动态返回 Diesel 等式表达式?

Posted

技术标签:

【中文标题】如何从辅助方法动态返回 Diesel 等式表达式?【英文标题】:How can I dynamically return a Diesel equality expression from a helper method? 【发布时间】:2020-08-19 01:48:18 【问题描述】:

我有一个枚举:

enum Role 
    Administrator,
    Sponsor,
    Bot,

我的数据库中有一个名为“roles”的表,如下所示:

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

如何在使用 Diesel 而不是原始 SQL 查询时对 Role 执行匹配并获取数据库中的相应列?

我可以从与角色匹配的方法中返回 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<***_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<***_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<***_quest
ions::schema::roles::table>` for `diesel::expression::operators::Eq<std::boxed::Box<dyn diesel::expression::Boxabl
eExpression<***_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<***_quest
ions::schema::roles::table>` for `(diesel::expression::operators::Eq<***_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<***_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<***_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<***_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<***_questions::schema::roles::table, diesel::mysql::backend::Mysql>` for `diesel::insertable::ColumnInsertValue<std::boxed::Box<dyn diesel::expression::BoxableExpression<***_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<***_questions::schema::roles::table, diesel::mysql::backend::Mysql>` for `(diesel::insertable::ColumnInsertValue<***_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<***_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<***_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<***_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>>), ***_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<***_questions::schema::roles::table, diesel::query_builder::insert_statement::ValuesClause<(diesel::insertable::ColumnInsertValue<***_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<***_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>>), ***_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<***_questions::schema::roles::table, diesel::query_builder::insert_statement::ValuesClause<(diesel::insertable::ColumnInsertValue<***_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<***_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>>), ***_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 `***-questions`.

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

我为这个问题创建了一个可重现的示例here。

【问题讨论】:

很难回答您的问题,因为它不包含minimal reproducible example。我们无法分辨代码中存在哪些 crate(及其版本)、类型、特征、字段等。如果您尝试在一个全新的 Cargo 项目中重现您的错误,我们会更轻松地为您提供帮助,然后 edit 您的问题将包含附加信息。您可以使用 Rust-specific 和 Diesel-specific MRE 提示来减少您在此处发布的原始代码。谢谢! 请edit 您的问题并粘贴您收到的确切和全部错误 - 这将帮助我们了解问题所在,以便我们提供最佳帮助。有时试图解释错误消息很棘手,实际上错误消息的不同部分很重要。请使用直接运行编译器的消息,而不是 IDE 生成的消息,它可能会尝试为您解释错误。 更新:我已经附加了一个带有Cargo.toml 文件的 GitHub 存储库链接,并在我的原始问题中添加了一个最小的可重现示例。感谢您的帮助。 【参考方案1】:

在我看来,使用BoxableExpression 是错误的方式。为您的枚举手动实现 Insertable&lt;roles::table&gt; 然后直接从那里插入值非常容易。

使用这个Insertable impl

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)?;

【讨论】:

但是,如果我只想更新一个字段,而其他字段保持不变,这仍然有效吗? 在这种情况下,Insertable 的 impl 需要稍作更改,否则可以。 (基本上您需要将values 函数返回的元组更改为(Option&lt;Eq&lt;…&gt;&gt;, Option&lt;Eq&lt;…&gt;&gt;, Option&lt;Eq&lt;…&gt;&gt;) 并返回None 值而不是.eq(false)。)

以上是关于如何从辅助方法动态返回 Diesel 等式表达式?的主要内容,如果未能解决你的问题,请参考以下文章

如何根据 Diesel 的动态参数有条件地按列排序?

如何在 Rust Diesel 中使用时间戳和间隔进行算术运算

如何从仅返回 1 或 0 条记录的 Diesel 查询中获取 Option<T> 而不是 Option<Vec<T>>?

使用动态参数查询 Diesel 表

返回使用 Rocket 和 Diesel (Rust) 在 PostgreSQL 中创建的单个记录

逻辑表达式的基本恒等式和从真值表导出逻辑表达式?