Search code examples
rustrust-diesel

How can a filter expression be built up with `into_boxed()` to operate against a query with a join?


In diesel, I want to build up the argument for .filter() in a programmatic way, but rust complains that into_boxed() cannot be called due to unsatisfied trait bounds.

#[macro_use]
extern crate diesel;
mod schema;
use diesel::{QueryDsl, ExpressionMethods, JoinOnDsl, NullableExpressionMethods};

fn main() {
    use crate::schema::Foo::dsl as foo_dsl;
    use crate::schema::Bar::dsl as bar_dsl;

    let mut query = foo_dsl::Foo
        .inner_join(bar_dsl::Bar
            .on(bar_dsl::foo_id.eq(foo_dsl::id.nullable())))
        .select((foo_dsl::id,)).into_boxed();

    let mut filter =  foo_dsl::id.eq(42).into_boxed();
    filter = filter.and(bar_dsl::id.eq(17));

    query = query.filter(filter);

}

Complains

Error[E0599]: the method `into_boxed` exists for struct `diesel::expression::operators::Eq<Foo::columns::id, diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>>`, but its trait bounds were not satisfied
   --> src/main.rs:14:42
    |
14  |     let mut filter =  foo_dsl::id.eq(42).into_boxed();
    |                                          ^^^^^^^^^^ method cannot be called on `diesel::expression::operators::Eq<Foo::columns::id, diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>>` due to unsatisfied trait bounds
    |
   ::: /home/ross/.cargo/registry/src/github.com-1ecc6299db9ec823/diesel-1.4.8/src/expression/operators.rs:343:1
    |
343 | diesel_infix_operator!(Eq, " = ");
    | ---------------------------------
    | |
    | doesn't satisfy `_: QueryDsl`
    | doesn't satisfy `_: Table`
    |
    = note: the following trait bounds were not satisfied:
            `diesel::expression::operators::Eq<Foo::columns::id, diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>>: Table`
            which is required by `diesel::expression::operators::Eq<Foo::columns::id, diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>>: QueryDsl`
            `&diesel::expression::operators::Eq<Foo::columns::id, diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>>: Table`
            which is required by `&diesel::expression::operators::Eq<Foo::columns::id, diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>>: QueryDsl`
            `&mut diesel::expression::operators::Eq<Foo::columns::id, diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>>: Table`
            which is required by `&mut diesel::expression::operators::Eq<Foo::columns::id, diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>>: QueryDsl`

For more information about this error, try `rustc --explain E0599`.
error: could not compile `and_or` due to previous error

How can I build up a filter against a query that has a join? BoxableExpression exists, but my combined filter

filter = filter.and(bar_dsl::id.eq(17));

is not for one single table.

This example could clearly be done without the filter variable and done directly in the .filter() call on the query. It is an S.S.C.C.E.

Can a boxed filter be built up somehow? With BoxableExpression or using into_boxed()?


schema.rs looks like:

table! {
    use diesel::sql_types::*;

    Bar (id) {
        id -> Nullable<Integer>,
        foo_id -> Nullable<Integer>,
    }
}

table! {
    use diesel::sql_types::*;

    Foo (id) {
        id -> Integer,
        label -> Text,
    }
}

joinable!(Bar -> Foo (foo_id));

allow_tables_to_appear_in_same_query!(
    Bar,
    Foo,
);

p.s.

With hints from this answer by @weiznich,

It looks like I can figure out a valid BoxableExpression as:

    pub type QueryType = diesel::dsl::InnerJoin<foo_dsl::Foo, bar_dsl::Bar>;
    let mut filter: Box<dyn BoxableExpression<QueryType, DB, SqlType = Bool>>
        = Box::new(foo_dsl::id.eq(42));
    filter = Box::new(filter.and(bar_dsl::id.eq(17)));

Now, when I use that filter variable like

    query = query.filter(filter);

I get

error[E0277]: the trait bound `dyn BoxableExpression<diesel::query_builder::SelectStatement<JoinOn<diesel::query_source::joins::Join<Foo::table, Bar::table, Inner>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<Bar::columns::foo_id>, diesel::expression::nullable::Nullable<Foo::columns::id>>>>, Sqlite, SqlType = diesel::sql_types::Bool>: AppearsOnTable<JoinOn<diesel::query_source::joins::Join<Foo::table, Bar::table, Inner>, diesel::expression::operators::Eq<Bar::columns::foo_id, diesel::expression::nullable::Nullable<Foo::columns::id>>>>` is not satisfied
  --> src/main.rs:27:19
   |
27 |     query = query.filter(filter);
   |                   ^^^^^^ the trait `AppearsOnTable<JoinOn<diesel::query_source::joins::Join<Foo::table, Bar::table, Inner>, diesel::expression::operators::Eq<Bar::columns::foo_id, diesel::expression::nullable::Nullable<Foo::columns::id>>>>` is not implemented for `dyn BoxableExpression<diesel::query_builder::SelectStatement<JoinOn<diesel::query_source::joins::Join<Foo::table, Bar::table, Inner>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<Bar::columns::foo_id>, diesel::expression::nullable::Nullable<Foo::columns::id>>>>, Sqlite, SqlType = diesel::sql_types::Bool>`
   |
   = note: required because of the requirements on the impl of `AppearsOnTable<JoinOn<diesel::query_source::joins::Join<Foo::table, Bar::table, Inner>, diesel::expression::operators::Eq<Bar::columns::foo_id, diesel::expression::nullable::Nullable<Foo::columns::id>>>>` for `Box<dyn BoxableExpression<diesel::query_builder::SelectStatement<JoinOn<diesel::query_source::joins::Join<Foo::table, Bar::table, Inner>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<Bar::columns::foo_id>, diesel::expression::nullable::Nullable<Foo::columns::id>>>>, Sqlite, SqlType = diesel::sql_types::Bool>>`
   = note: required because of the requirements on the impl of `FilterDsl<Box<dyn BoxableExpression<diesel::query_builder::SelectStatement<JoinOn<diesel::query_source::joins::Join<Foo::table, Bar::table, Inner>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<Bar::columns::foo_id>, diesel::expression::nullable::Nullable<Foo::columns::id>>>>, Sqlite, SqlType = diesel::sql_types::Bool>>>` for `diesel::query_builder::BoxedSelectStatement<'_, (diesel::sql_types::Integer,), JoinOn<diesel::query_source::joins::Join<Foo::table, Bar::table, Inner>, diesel::expression::operators::Eq<Bar::columns::foo_id, diesel::expression::nullable::Nullable<Foo::columns::id>>>, Sqlite>`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `and_or` due to previous error

p.p.s.

Interesting. I can get things to compile with the BoxableExpression as long as I don't let the query be into_boxed as well. The following compiles:

    pub type QueryType = diesel::dsl::InnerJoin<foo_dsl::Foo, bar_dsl::Bar>;
    let mut filter: Box<dyn BoxableExpression<QueryType, DB, SqlType = Bool>>
        = Box::new(foo_dsl::id.eq(42));
    filter = Box::new(filter.and(bar_dsl::id.eq(17)));

           
    let mut query = foo_dsl::Foo
        .inner_join(bar_dsl::Bar.on(bar_dsl::foo_id.eq(foo_dsl::id.nullable())))
        .filter(filter);   

Solution

  • The following filter buildup with BoxableExpression works and is usable with a query that is then itself boxed with .into_boxed():

    #[macro_use]
    extern crate diesel;
    mod schema;
    use diesel::{QueryDsl, ExpressionMethods, JoinOnDsl, NullableExpressionMethods};
    use diesel::sql_types::*;
    use diesel::expression::BoxableExpression;
    use diesel::BoolExpressionMethods;
    
    use std::boxed::Box;
    type DB = diesel::sqlite::Sqlite;
    
    fn main() {
        use crate::schema::Foo::dsl as foo_dsl;
        use crate::schema::Bar::dsl as bar_dsl;
    
        let mut filter: Box<dyn BoxableExpression<_, DB, SqlType = Bool>>
            = Box::new(foo_dsl::id.eq(42));
        filter = Box::new(filter.and(bar_dsl::id.eq(17)));
    
    
        let mut query = foo_dsl::Foo
            .inner_join(bar_dsl::Bar.on(bar_dsl::foo_id.eq(foo_dsl::id.nullable())))
            .into_boxed();
    
        let query = query.filter(filter);
    
    }