代码之家  ›  专栏  ›  技术社区  ›  Ismaïl Mourtada

尝试使用具有不完整特征边界的Diesel“first”方法创建泛型查询

  •  0
  • Ismaïl Mourtada  · 技术社区  · 1 年前

    我试图为我的柴油机应用提供通用的过滤器功能。用户将指定一个要过滤的列,该列的适当值(str、float、int等),然后执行以下操作之一:

    pub enum FilterOp {
        NONE, 
        EQUAL,
        LESS_THAN,
        LESS_THAN_EQUAL,
        GREATER_THAN, 
        GREATER_THAN_EQUAL, 
        CONTAINS,
        NOT_EQUAL, 
        STRING_LIKE, 
    }
    

    如果我的桌子有:

    diesel::table! {
        use diesel::sql_types::*;
        use postgis_diesel::sql_types::*;
    
        entities (uid) {
            uid -> Uuid,
            source -> Text,
            source_id -> Text,
            ...
    

    因此,对于“source_id”列,我最终得到的代码如下:

        let mut query: schema::entities::BoxedQuery<diesel::pg::Pg> = schema::entities::table.into_boxed();
    
            if "source_id".eq( fitler_column.as_str() )  {
                    ...
                    match op {
                        FilterOp::EQUAL => {
                            query = query.filter( source_id.eq( filter_value.string_value() ) );
                        },
                        FilterOp::NOT_EQUAL => {
                            query = query.filter( source_id.ne( filter_value.string_value() ) );
                        },
                        FilterOp::LESS_THAN => {
                            query = query.filter( source_id.lt( filter_value.string_value() ) );
                        },
    

    这同样适用于其他列类型(uuid字符串将被转换为uuid实例)。但是(每一列)都有很多重复的代码。

    所以我想提取匹配项,比如:(对于“源”列):

                    query_filter(op, 
                                 &mut query, 
                                 &schema::entities::dsl::source, 
                                 filter_value.string_value() );
    

    但我很难正确定义query_filter。它需要看起来像:

    fn query_filter<T, V> ( op: FilterOp, 
                            query: &mut schema::entities::BoxedQuery<diesel::pg::Pg>, 
                            column: &T, 
                            filter_value: &V ) 
    {
        use crate::schema::entities::dsl::*;
    
        match op {
            FilterOp::EQUAL => {
                *query = query.filter( column.eq( filter_value ) );
            },
        ...
    

    因此,这将列及其SQL类型(T)和相应的原生Rust类型(V)参数化。我对T和V类型应该声明为什么感到困惑。

    这需要是宏观的吗?

    谢谢!

    0 回复  |  直到 2 年前
        1
  •  1
  •   weiznich    2 年前

    由于你的帖子并没有真正包含一个最小的示例,我构建了以下示例:

    
    table! {
        users {
            id -> Integer,
            name -> Text,
        }
    }
    
    fn apply_filters<C, V>(
        mut query: users::BoxedQuery<diesel::sqlite::Sqlite>,
        column: C,
        value: V,
    ) -> users::BoxedQuery<diesel::sqlite::Sqlite>
    {
        // just use a single method here for now as other methods
        // can be easily added later
        query = query.filter(column.eq(value));
        
        query
    }
    
    fn main() {
        let q = users::table.into_boxed();
    
        let q = apply_filters(q, users::id, 42);
        apply_filters(q, users::name, "Foo");
    
        println!("Hello, world!");
    }
    
    

    现在,在我们讨论“解决方案”之前,首先要提醒一下:为diesel编写抽象代码是可能的,但需要处理大量的特性边界。因此,在开始处理这类代码之前,请确保您对rusts-trait系统的工作原理有很好的了解。

    现在需要回答的第一件事是如何限制类型 C V 。我们可以在这里查看柴油机文件,发现有一个名为 Column ,这似乎适合我们的用例 C 。否则,我们也可以看看 ExpressionMethods::eq 方法,看看它是 implemented for all types 该工具 Expression . 专栏 暗示 表达式 所以我们在那里很好。 For 五、 我们再看一遍 eq 方法,并查看参数是否受约束 T: AsExpression<Self::SqlType>, 这表明我们需要限制 五、 同样的特质。

    这给我们留下了以下起始特征界限: C: Column V: AsExpression<C::SqlType> .

    如果我们尝试编译器抱怨“trait” diesel::sql_types::SqlType 未实现 <C as diesel::Expression>::SqlType “,因此我们添加了建议的trait bound: C::SqlType: SqlType

    如果我们尝试编译器再次抱怨“方法 eq 类型参数存在 C ,但其特征界限未得到满足”。现在,它给出了无意义的限制建议 C Iterator 。帮助还包含以下块:

       = note: the following trait bounds were not satisfied:
               `<C as diesel::Expression>::SqlType: SingleValue`
               which is required by `C: diesel::ExpressionMethods`
               `<C as diesel::Expression>::SqlType: SingleValue`
               which is required by `&C: diesel::ExpressionMethods`
               `&mut C: diesel::Expression`
               which is required by `&mut C: diesel::ExpressionMethods`
               `C: Iterator`
               which is required by `&mut C: Iterator`
    

    所以,让我们试试 C: SingleValue 相反,这是一种柴油机的特性。

    如果我们尝试让编译器再次抱怨“特性绑定” C: ValidGrouping<()> 不满意”,并建议添加 C: 有效分组<()> .

    如果我们尝试让编译器再次抱怨“特性绑定” <V as AsExpression<<C as diesel::Expression>::SqlType>>::Expression: ValidGrouping<()> 不满意”,并建议添加该约束。现在可以更简短地写为 diesel::dsl::AsExpr<V, C>: ValidGrouping<()>

    如果我们尝试让编译器再次抱怨“特性绑定” <C as ValidGrouping<()>>::IsAggregate: MixedAggregates<<<V as AsExpression<<C as diesel::Expression>::SqlType>>::Expression as ValidGrouping<()>>::IsAggregate> 不满意”,并建议添加另一个特征界限。建议的界限可以通过使用更容易地编写 <C as ValidGrouping<()>>::IsAggregate:MixedAggregates<<dsl::AsExpr<V, C> as ValidGrouping<()>>::IsAggregate>

    如果我们再次尝试,编译器将再次抱怨“特性绑定” <<C as ValidGrouping<()>>::IsAggregate as MixedAggregates<<<V as AsExpression<<C as diesel::Expression>::SqlType>>::Expression as ValidGrouping<()>>::IsAggregate>>::Output: MixedAggregates<diesel::expression::is_aggregate::No> 不满意”,并建议另一个特征界限。不幸的是,这个建议是一个陷阱。相反,我们需要将最后一个界限修改为 <C as ValidGrouping<()>>::IsAggregate:MixedAggregates<<dsl::AsExpr<V, C> as ValidGrouping<()>>::IsAggregate, Output = is_aggregate::No>

    如果我们这样做,编译器会再次抱怨“特性绑定” <<C as diesel::Expression>::SqlType as diesel::sql_types::SqlType>::IsNull: OneIsNullable<<<C as diesel::Expression>::SqlType as diesel::sql_types::SqlType>::IsNull> 不满意”,并建议另一个长特征界限。这可以通过使用“所需的 expression::operators::Eq<C, <V as AsExpression<<C as diesel::Expression>::SqlType>>::Expression> 实施 diesel::Expression “改为行。这给了我们以下界限: dsl::Eq<C, V>: Expression

    如果我们这样做,编译器会再次抱怨“特性绑定” <expression::grouped::Grouped<expression::operators::Eq<C, <V as AsExpression<<C as diesel::Expression>::SqlType>>::Expression>> as diesel::Expression>::SqlType: BoolOrNullableBool 不满意”,并建议另一个特征界限。

    如果我们尝试让编译器再次抱怨“特性绑定” C: AppearsOnTable<users::table> 不满意”,并建议添加另一个特征边界。该边界有点帮助,但最好在以下注释行中基于特征边界之一添加一个边界:“必需 表达式::运算符::Eq<C、 <V作为AsExpression<&书信电报;C作为柴油::表达式>:SqlType>>::表达式> 实施 AppearsOnTable<users::table> “.这导致了界限 dsl::Eq<C, V>: AppearsOnTable<users::table>

    如果我们尝试让编译器再次抱怨“特性绑定” C: QueryFragment<Sqlite> 不满意”。再次,最好添加绑定到 dsl::Eq 基于所需的原因行。

    如果我们尝试编译器再次抱怨“ C 无法在线程之间安全地发送”。同样,最好添加绑定到的trait dsl::方程式

    如果我们尝试让编译器抱怨“关联的类型” <V as AsExpression<<C as diesel::Expression>::SqlType>>::Expression 可能活得不够长”,并表明在 BoxedQuery 类型。所以我们添加一个命名的生命周期 'a 并限制相关类型的人至少能活多久 。对发出相同的错误消息 C 所以我们也在那里添加了界限。

    添加这些边界后,代码最终会编译。这给出了以下代码:

    fn apply_filters<'a, C, V>(
        mut query: users::BoxedQuery<'a, diesel::sqlite::Sqlite>,
        column: C,
        value: V,
    ) -> users::BoxedQuery<diesel::sqlite::Sqlite>
    where
        C: Column + ValidGrouping<()> + 'a,
        V: AsExpression<C::SqlType>,
        C::SqlType: SqlType + SingleValue,
        dsl::AsExpr<V, C>: ValidGrouping<()> + 'a,
        <C as ValidGrouping<()>>::IsAggregate: MixedAggregates<
            <dsl::AsExpr<V, C> as ValidGrouping<()>>::IsAggregate,
            Output = is_aggregate::No,
        >,
        dsl::Eq<C, V>:
            Expression + AppearsOnTable<users::table> + QueryFragment<diesel::sqlite::Sqlite> + Send,
        <dsl::Eq<C, V> as Expression>::SqlType: BoolOrNullableBool,
    {
        query = query.filter(column.eq(value));
    
        query
    }
    
    

    前5个边界是所有运算符都需要的一般边界。最后两个边界(两行均包含 dsl::方程式 )是您在此函数中使用的运算符特定的边界。如果使用多个运算符,则需要为其他运算符复制和调整这些行。例如,支持 .ne() 需要以下附加边界:

        dsl::NotEq<C, V>:
            Expression + AppearsOnTable<users::table> + QueryFragment<diesel::sqlite::Sqlite> + Send,
        <dsl::NotEq<C, V> as Expression>::SqlType: BoolOrNullableBool,
    

    其总体信息是:

    • 编写这样的代码是可能的
    • 你通常需要一些耐心,你需要仔细检查编译器输出,看看接下来要添加什么
    • 您通常不想在应用程序中编写该代码,因为它相当复杂
    推荐文章