Issue with diesel generics and overflow

I feel like this is a pretty common thing to want to do, but I've spent a few hours wrangling with bounds and haven't been able to get anything to work.

pub fn search<T, C>(
    table: T,
    col: C,
    query: String,
) -> T::Output
where
    T: FilterDsl<Like<C, Bound<VarChar, String>>>,
    C: Expression<SqlType = VarChar> + TextExpressionMethods,
{
    let query = format!("%{query}%");
    table.filter(col.like(query))
}

Essentially, I want to be able to search a given column of a given table for some string, in a generic way.

The code above works, but only generates the query. If I want to execute it, I need to call .load(conn) on it. So I rewrote it to look like this:

pub fn search<T, C, M>(  // extra type param for "model" type
    table: T,
    col: C,
    query: String,
    conn: SqliteConnection,
) -> Result<Vec<M>, diesel::result::Error>
where
    T: FilterDsl<Like<C, Bound<VarChar, String>>>,
    C: Expression<SqlType = VarChar> + TextExpressionMethods,
    T::Output: LoadQuery<SqliteConnection, M>,
{
    let query = format!("%{query}%");
    table.filter(col.like(query)).load(&conn)
}

This causes an overflow error:

error[E0275]: overflow evaluating the requirement `<T as FilterDsl<Like<C, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>>>::Output == _`
  --> vit-servicing-station-lib/src/db/queries/search/generic.rs:32:1
   |
32 | / pub fn search<T, C, M>(
33 | |     table: T,
34 | |     col: C,
35 | |     query: String,
...  |
40 | |     C: Expression<SqlType = VarChar> + TextExpressionMethods,
41 | |     T::Output: LoadQuery<SqliteConnection, M>,
   | |______________________________________________^

When I read the constraint T::Output: LoadQuery<SqliteConnection, M>, I think: "whatever the return type of table.filter(...) is, that type should implement LoadQuery<_, _>, which is the trait that .load() comes from, so it should work".

But this overflow seems to indicate that there's a cycle somewhere (setting #![recursion_limit = "1000000"] doesn't help), but I'm not sure where. I've also tried specifying <T as FilterDsl<_>>::Output in the bound for T, like:

T: FilterDsl<Like<C, Bound<VarChar, String>>, Output = ...>

in an attempt to break the cycle, but the error persists.

Can anyone spot what I'm doing wrong? I have a feeling I'm just missing one trait bound that will make this go away, but I can't for the life of me see it.

Thanks :grin:

The problem seems to come from this circular impl:

impl<T, Predicate> FilterDsl<Predicate> for T
where
    T: Table,
    T::Query: FilterDsl<Predicate>,
{
    type Output = Filter<T::Query, Predicate>;

    fn filter(self, predicate: Predicate) -> Self::Output {
        self.as_query().filter(predicate)
    }
}

It might work to use SelectStatement directly, but I haven't had any luck doing that.

It seems like one can actually get it to compile by adding an additional type parameter:

use diesel::{
    dsl::Like,
    prelude::*,
    query_dsl::methods::{FilterDsl, LoadQuery},
    sql_types::VarChar,
};

pub fn search<T, C, F, M>(
    table: T,
    col: C,
    query: String,
    conn: SqliteConnection,
) -> Result<Vec<M>, diesel::result::Error>
where
    T: FilterDsl<Like<C, String>, Output = F>,
    C: Expression<SqlType = VarChar> + TextExpressionMethods,
    F: LoadQuery<SqliteConnection, M>,
{
    let query = format!("%{query}%");
    table.filter(col.like(query)).load(&conn)
}

It should really work with how you wrote it; I'll see if I can reduce it to a minimal example to file an issue. Also, be careful with Like, since the public type alias already applies the AsExprOf transformation to the second parameter.