postgresql 柴油-防 rust (Postgres)-过滤用柱型

iyfjxgzm  于 2023-11-18  发布在  PostgreSQL
关注(0)|答案(1)|浏览(122)

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

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

字符串
如果我的table有:

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类型应该声明为什么感到困惑。
这需要宏吗?
谢谢你,谢谢

rjzwgtxy

rjzwgtxy1#

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

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编写抽象代码是可能的,但需要处理大量的trait边界。因此,在开始玩弄这类代码之前,请确保您对rusts trait系统的工作原理有很好的理解。
现在需要回答的第一件事是如何限制类型CV。我们可以在这里查看diesel文档,发现有一个名为Column的trait,这似乎适合我们对C的使用情况。否则,我们也可以看看ExpressionMethods::eq方法,并看到它对所有实现Expression的类型都实现了。对于V,我们再次查看eq方法,发现参数绑定在T: AsExpression<Self::SqlType>,上。这表明我们需要在同一个trait上限制V
这就给我们留下了以下起始trait边界:C: ColumnV: AsExpression<C::SqlType>
如果我们尝试编译器抱怨“trait diesel::sql_types::SqlType没有为<C as diesel::Expression>::SqlType实现“,那么我们添加建议的trait绑定:C::SqlType: SqlType
如果我们尝试编译器再次抱怨“方法eq存在于类型参数C中,但它的trait bounds不被满足”。现在它给出了毫无意义的建议,将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建议,而不是因为这是一些柴油特质。
如果我们尝试,编译器再次抱怨“trait bound C: ValidGrouping<()> is not satisfied”,并建议添加C: ValidGrouping<()>
如果我们尝试编译器再次抱怨“trait bound <V as AsExpression<<C as diesel::Expression>::SqlType>>::Expression: ValidGrouping<()> is not satisfied”,并建议添加该bound。
如果我们尝试编译器再次抱怨“trait bound <C as ValidGrouping<()>>::IsAggregate: MixedAggregates<<<V as AsExpression<<C as diesel::Expression>::SqlType>>::Expression as ValidGrouping<()>>::IsAggregate> is not satisfied”并建议添加另一个trait bound,建议的bound可以通过使用<C as ValidGrouping<()>>::IsAggregate:MixedAggregates<<dsl::AsExpr<V, C> as ValidGrouping<()>>::IsAggregate>更容易地编写。
如果我们再次尝试,编译器会再次抱怨“trait bound <<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> is not satisfied”并建议另一个trait bound。不幸的是,这个建议是一个陷阱。相反,我们需要将最后一个绑定修改为<C as ValidGrouping<()>>::IsAggregate:MixedAggregates<<dsl::AsExpr<V, C> as ValidGrouping<()>>::IsAggregate, Output = is_aggregate::No>
如果我们这样做,编译器会再次抱怨“trait bound <<C as diesel::Expression>::SqlType as diesel::sql_types::SqlType>::IsNull: OneIsNullable<<<C as diesel::Expression>::SqlType as diesel::sql_types::SqlType>::IsNull> is not satisfied”,并建议另一个长trait bound。这可以通过使用“required for expression::operators::Eq<C, <V as AsExpression<<C as diesel::Expression>::SqlType>>::Expression> to implement diesel::Expression“行中的bound来简化。这给了我们以下的bound:dsl::Eq<C, V>: Expression
如果我们这样做,编译器会再次抱怨“trait bound <expression::grouped::Grouped<expression::operators::Eq<C, <V as AsExpression<<C as diesel::Expression>::SqlType>>::Expression>> as diesel::Expression>::SqlType: BoolOrNullableBool is not satisfied”并建议另一个trait bound。
如果我们尝试编译器再次抱怨“trait bound C: AppearsOnTable<users::table> is not satisfied”并建议添加另一个trait bound。该bound有点帮助,但最好基于以下注解行中的trait bound之一添加一个bound:“required for expression::operators::Eq<C, <V as AsExpression<<C as diesel::Expression>::SqlType>>::Expression> to implement AppearsOnTable<users::table>“。这将导致bound dsl::Eq<C, V>: AppearsOnTable<users::table>
如果我们尝试编译器再次抱怨“trait bound C: QueryFragment<Sqlite> is not satisfied”。同样,最好根据所需的because. lines将trait绑定添加到dsl::Eq
如果我们尝试编译器再次抱怨“C不能在线程之间安全地发送”,那么最好再次将trait绑定到dsl::Eq
如果我们尝试编译器抱怨“关联的类型<V as AsExpression<<C as diesel::Expression>::SqlType>>::Expression可能活得不够长”,并表示BoxedQuery类型中存在隐藏的生存期边界。因此,我们在此添加命名生存期'a,并限制相关类型的生存期至少与'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个边界是所有运算符都需要的通用边界。最后2个边界(两行都包含dsl::Eq)是您在此函数中使用的运算符特定的边界。如果您使用多个运算符,则需要复制这些行并针对其他运算符进行调整。例如,为了支持.ne(),需要以下附加边界:

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


总的信息是:

  • 写这样的代码是可能的
  • 您通常需要一些耐心,并且需要仔细检查编译器输出,以了解下一步要添加什么
  • 您通常不希望在应用程序中编写这些代码,因为它非常复杂

相关问题