Dynamic filtering of a query in a dynamic schema

The idea of returning a BoxableExpression is actually pretty good!

I implemented an expression() method:

fn expression(&self, table: &DynTable) -> Option<DynExpr> {
    use diesel::BoolExpressionMethods;

    let mut or_expr = None::<DynExpr>;
    for and_filter in &self.inner {
        if let Some(new_expr) = Self::and_expression(table, &and_filter) {
            or_expr = Some(if let Some(cur_expr) = or_expr {
                Box::new(cur_expr.or(new_expr))
            } else {
                new_expr
            });
        }
    }

    or_expr
}

fn and_expression(table: &DynTable, list: &[FilterClause]) -> Option<DynExpr> {
    use diesel::BoolExpressionMethods;

    let mut and_expr = None::<DynExpr>;

    for clause in list {
        if let Ok(new_expr) = clause.expression(table) {
            and_expr = Some(if let Some(cur_expr) = and_expr {
                Box::new(cur_expr.and(new_expr))
            } else {
                new_expr
            });
        }
    }

    and_expr
}

For that, I implemented an expression() method in FilterClause, that generates an expression depending on the type of the column. In order to no repeate code, this function will call to expression_ty() a typed version of the function:

fn expression_ty<T, E, V>(&self, table: &DynTable) -> Result<DynExpr, Error>
where
    T: FromStr<Err = E> + diesel::expression::AsExpression<V> + std::fmt::Display,
    E: std::error::Error + Send + Sync + 'static,
    V: diesel::sql_types::SingleValue,
    <T as diesel::expression::AsExpression<V>>::Expression:
        diesel::expression::NonAggregate + QueryFragment<Pg> + SelectableExpression<DynTable>,
{
    
    use diesel::{ExpressionMethods, PgTextExpressionMethods};

    let field = table.column::<V, _>(self.field);

    let res: DynExpr = match o {
        Operator::Contains => Box::new(field.ilike(format!("%{}%", value))),
        Operator::DoesNotContain => {
            Box::new(field.not_ilike(format!("%{}%", value)))
        }
        Operator::StartsWith => Box::new(field.ilike(format!("{}%", value))),
        Operator::EndsWith => Box::new(field.ilike(format!("%{}", value))),
    };

    Ok(res)
}

But I’m getting some errors in that last function:

error[E0599]: no method named `ilike` found for struct `diesel_dynamic_schema::column::Column<diesel_dynamic_schema::table::Table<std::string::String>, std::string::String, V>` in the current scope
   --> api_server/src/db.rs:853:66
    |
853 | ...                   Operator::Contains => Box::new(field.ilike(format!("%{}%", value))),
    |                                                            ^^^^^ method not found in `diesel_dynamic_schema::column::Column<diesel_dynamic_schema::table::Table<std::string::String>, std::string::String, V>`
    |
    = note: the method `ilike` exists but the following trait bounds were not satisfied:
            `&diesel_dynamic_schema::column::Column<diesel_dynamic_schema::table::Table<std::string::String>, std::string::String, V> : diesel::PgTextExpressionMethods`
            `&mut diesel_dynamic_schema::column::Column<diesel_dynamic_schema::table::Table<std::string::String>, std::string::String, V> : diesel::PgTextExpressionMethods`
            `diesel_dynamic_schema::column::Column<diesel_dynamic_schema::table::Table<std::string::String>, std::string::String, V> : diesel::PgTextExpressionMethods`

So, it seems that the table.column() method is not creating a PgTextExpressionMethods. Probably I’m missing some bounds in the header of the function, do you have an idea of what could it be?

Also, do you think this looks performant enough? It seems like it’s boxing a bunch of stuff.