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.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.