Help: Combine filters to Boxable expression for joined tables without joinable!

Semi-long explanation but just giving as much info as I can - would really appreciate help

I have the following table structure

table! {
    use diesel::sql_types::*;
    items (id) {
        id -> Int4,
        name -> Text,
        // Some other irrelevant fields
    }
}

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

    weapons (item_id) {
        item_id -> Int4,
        primary_end -> Int4,
        secondary_end -> Nullable<Int4>,
        // Some other irrelevant fields
    }
}

table! {
    use diesel::sql_types::*;
    use crate::db::sql_types::*;
    weapon_ends (id) {
        id -> Int4,
        range -> Int2,
        // Some other irrelevant fields
    }
}

joinable!(weapons -> items (item_id));

So basically, a weapon belongs to/is an item and a weapon has a primary end and possibly a secondary end as well. Notice the joinable! on weapons -> items since there’s a one-to-one correspondance. However, weapons are not joinable! on weapon_ends.

I want to extract all the items that are weapons which have a weapon end (either end) with a certain criteria. For this I’ve been doing the following:

let mut results = items::table
    .select((items::name,))
    .into_boxed();
results = results.filter(items::id.eq(any(weapons::table
    .select(weapons::item_id)
    .left_join(weapon_ends::table.on(weapons::primary_end.eq(weapon_ends::id).or(weapons::secondary_end.eq(weapon_ends::id.nullable()))))
    .filter(weapon_ends::range.le(user_input_minimum_range))
)))

And this does work. But, I have a lot of these filters and I’d rather avoid doing a lot of these inner select queries, so I thought I could make a function to return those inner weapons_ends filters like so:

type WeaponEndsFilter = Box<dyn BoxableExpression<weapon_ends::table, Pg, SqlType = Bool>>;
pub fn get_weapon_details_filters(...) -> Option<WeaponEndsFilter> {
    let mut filters: Vec<WeaponEndsFilter> = Vec::new();

    filters.push(Box::new(weapon_ends::range.le(user_input_minimum_range)))
    filters.push(Box::new(weapon_ends::some_other_column.le(some_other_input)))
    // Etc...

    filters.into_iter().fold_first(|acc, f| {
        Box::new(acc.and(f))
    })
}

And this function does compile. However, now I try to plug it back in my previous solution:

if let Some(f) = get_weapon_details_filters(...) {
    results = results.filter(items::id.eq(any(weapons::table
        .select(weapons::item_id)
        .left_join(weapon_ends::table.on(weapons::primary_end.eq(weapon_ends::id).or(weapons::secondary_end.eq(weapon_ends::id.nullable()))))
        .filter(f)
    )))
}

Now the compiler gets very mad at me. Concrete error here:

    Checking SorteKanin v0.1.0 (X:\SorteKanin\Documents\GitHub\SorteKanin)
warning: unused imports: `JoinOnDsl`, `NullableExpressionMethods`
 --> src\db\helpers.rs:2:89
  |
2 | use diesel::{QueryDsl, ExpressionMethods, BoolExpressionMethods, TextExpressionMethods, NullableExpressionMethods, JoinOnDsl};
  |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

error[E0277]: the trait bound `dyn diesel::BoxableExpression<db::schema::weapon_ends::table, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>: diesel::AppearsOnTable<diesel::query_source::joins::Join<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, db::schema::items::table, diesel::query_source::joins::Inner>>` is not satisfied
   --> src\subpages\archive\views\browse.rs:269:28
    |
269 |           results = results.filter(items::id.eq(any(
    |  __________________________________^
270 | |             weapons::table
271 | |                 .select(weapons::item_id)
272 | |                 .left_join(weapon_ends::table.on(weapons::primary_end.eq(weapon_ends::id).or(weapons::secondary_end.eq(weapon_ends::id.nullable()))))
273 | |                 .filter(f)
274 | |         )))
    | |__________^ the trait `diesel::AppearsOnTable<diesel::query_source::joins::Join<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, db::schema::items::table, diesel::query_source::joins::Inner>>` is not implemented for `dyn diesel::BoxableExpression<db::schema::weapon_ends::table, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>`
    |
    = note: required because of the requirements on the impl of `diesel::AppearsOnTable<diesel::query_source::joins::Join<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, db::schema::items::table, diesel::query_source::joins::Inner>>` for `std::boxed::Box<dyn diesel::BoxableExpression<db::schema::weapon_ends::table, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>>`
    = note: required because of the requirements on the impl of `diesel::query_builder::where_clause::ValidWhereClause<diesel::query_source::joins::Join<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, db::schema::items::table, diesel::query_source::joins::Inner>>` for `diesel::query_builder::where_clause::WhereClause<std::boxed::Box<dyn diesel::BoxableExpression<db::schema::weapon_ends::table, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>>>`
    = note: required because of the requirements on the impl of `diesel::expression::subselect::ValidSubselect<db::schema::items::table>` for `diesel::query_builder::SelectStatement<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::query_builder::select_clause::SelectClause<db::schema::weapons::columns::item_id>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<std::boxed::Box<dyn diesel::BoxableExpression<db::schema::weapon_ends::table, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>>>>`
    = note: required because of the requirements on the impl of `diesel::AppearsOnTable<db::schema::items::table>` for `diesel::expression::subselect::Subselect<diesel::query_builder::SelectStatement<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::query_builder::select_clause::SelectClause<db::schema::weapons::columns::item_id>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<std::boxed::Box<dyn diesel::BoxableExpression<db::schema::weapon_ends::table, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>>>>, diesel::sql_types::Array<diesel::sql_types::Integer>>`
    = note: required because of the requirements on the impl of `diesel::AppearsOnTable<db::schema::items::table>` for `diesel::pg::expression::array_comparison::Any<diesel::expression::subselect::Subselect<diesel::query_builder::SelectStatement<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::query_builder::select_clause::SelectClause<db::schema::weapons::columns::item_id>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<std::boxed::Box<dyn diesel::BoxableExpression<db::schema::weapon_ends::table, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>>>>, diesel::sql_types::Array<diesel::sql_types::Integer>>>`
    = note: required because of the requirements on the impl of `diesel::AppearsOnTable<db::schema::items::table>` for `diesel::expression::operators::Eq<db::schema::items::columns::id, diesel::pg::expression::array_comparison::Any<diesel::expression::subselect::Subselect<diesel::query_builder::SelectStatement<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::query_builder::select_clause::SelectClause<db::schema::weapons::columns::item_id>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<std::boxed::Box<dyn diesel::BoxableExpression<db::schema::weapon_ends::table, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>>>>, diesel::sql_types::Array<diesel::sql_types::Integer>>>>`
    = note: required because of the requirements on the impl of `diesel::query_dsl::filter_dsl::FilterDsl<diesel::expression::operators::Eq<db::schema::items::columns::id, diesel::pg::expression::array_comparison::Any<diesel::expression::subselect::Subselect<diesel::query_builder::SelectStatement<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::query_builder::select_clause::SelectClause<db::schema::weapons::columns::item_id>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<std::boxed::Box<dyn diesel::BoxableExpression<db::schema::weapon_ends::table, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>>>>, diesel::sql_types::Array<diesel::sql_types::Integer>>>>>` for `diesel::query_builder::BoxedSelectStatement<'_, (diesel::sql_types::Text, diesel::sql_types::Float, diesel::sql_types::Float, db::sql_types::db_enum_impl_Slot::Item_slot, diesel::sql_types::Bool, db::sql_types::db_enum_impl_Source::Item_source), db::schema::items::table, diesel::pg::Pg>`

error: aborting due to previous error; 1 warning emitted

For more information about this error, try `rustc --explain E0277`.
error: could not compile `SorteKanin`.

To learn more, run the command again with --verbose.

So, what I read from this is that I can’t use my WeaponEndsFilter type (3 code block up) as a filter here because I am joining on weapon_ends, but not filtering on it directly. So what type should I construct that would work? Is there some simpler way to achieve what I want to do?
I can’t for the life of me figure out how I am supposed to denote the type of WeaponEndsFilter so that this works. The documentation seems very minimal when it comes to the boxable types.
Any help hugely appreciated.

Something like

type WeaponEndsFilter = Box<dyn BoxableExpression<diesel::dsl::LeftJoin<weapons::table, weapon_ends::table>, Pg, SqlType = Bool>>;

should work. The QS (QuerySource) generic parameter needs to contain the complete FROM clause of the underlying query.

When I try that, I get this error:

error[E0277]: the trait bound `db::schema::weapons::table: diesel::JoinTo<db::schema::weapon_ends::table>` is not satisfied
   --> src\db\helpers.rs:179:1
    |
179 | / pub fn _get_weapon_details_filters(query: &ItemQuery) -> Option<WeaponEndsFilter>
180 | | {
181 | |     let mut filters: Vec<WeaponEndsFilter> = Vec::new();
182 | |
...   |
233 | |     // })
234 | | }
    | |_^ the trait `diesel::JoinTo<db::schema::weapon_ends::table>` is not implemented for `db::schema::weapons::table`
    |
    = help: the following implementations were found:
              <db::schema::weapons::table as diesel::JoinTo<db::schema::items::table>>
              <db::schema::weapons::table as diesel::JoinTo<diesel::query_builder::BoxedSelectStatement<'a, QS, ST, DB>>>
              <db::schema::weapons::table as diesel::JoinTo<diesel::query_builder::SelectStatement<F, S, D, W, O, L, Of, G>>>
              <db::schema::weapons::table as diesel::JoinTo<diesel::query_source::joins::Join<Left, Right, Kind>>>
              <db::schema::weapons::table as diesel::JoinTo<diesel::query_source::joins::JoinOn<Join, On>>>
    = note: required because of the requirements on the impl of `diesel::query_dsl::JoinWithImplicitOnClause<db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>` for `db::schema::weapons::table`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `SorteKanin`.

To learn more, run the command again with --verbose.

As far as I understand, this error happens because the weapons table does not have a joinable! macro with the weapon_ends table. So it can’t implicitly know how to join the two tables together. I want it to join like this: ON weapons.primary_end = weapon_end.id OR weapons.secondary_end = weapon_end.id.

So I try doing this for example:

type WeaponsWeaponEndsJoin = Join<weapons::table, weapon_ends::table, LeftOuter>;
type WeaponsWeaponEndsJoinOn = JoinOn<WeaponsWeaponEndsJoin, Or<Eq<weapons::primary_end, weapon_ends::id>, Eq<Nullable<weapons::secondary_end>, Nullable<weapon_ends::id>>>>;

pub type WeaponEndsFilter = Box<dyn BoxableExpression<WeaponsWeaponEndsJoinOn, DB, SqlType = Bool>>;

I am not very certain I’m using those types correctly, but hopefully that represents the kind of ON clause I want. Problem is, I get this error now, in the get_weapon_details_filters function:

error[E0271]: type mismatch resolving `<db::schema::weapons::table as diesel::query_source::AppearsInFromClause<db::schema::weapon_ends::table>>::Count == diesel::query_source::Once`
   --> src\db\helpers.rs:192:16
    |
192 |         filters.push(Box::new(weapon_ends::damage_dice.ge(min_num_dice)))
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `diesel::query_source::Never`, found struct `diesel::query_source::Once`
    |
    = note: required because of the requirements on the impl of `diesel::SelectableExpression<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>>` for `db::schema::weapon_ends::columns::damage_dice`
    = note: required because of the requirements on the impl of `diesel::SelectableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<db::schema::weapons::columns::secondary_end>, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>>` for `db::schema::weapon_ends::columns::damage_dice`
    = note: required because of the requirements on the impl of `diesel::SelectableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<db::schema::weapons::columns::secondary_end>, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>>` for `diesel::expression::operators::GtEq<db::schema::weapon_ends::columns::damage_dice, diesel::expression::bound::Bound<diesel::sql_types::SmallInt, i16>>`
    = note: required because of the requirements on the impl of `diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<db::schema::weapons::columns::secondary_end>, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg>` for `diesel::expression::operators::GtEq<db::schema::weapon_ends::columns::damage_dice, diesel::expression::bound::Bound<diesel::sql_types::SmallInt, i16>>`
    = note: required for the cast to the object type `dyn diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<db::schema::weapons::columns::secondary_end>, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>`

error[E0271]: type mismatch resolving `<db::schema::weapon_ends::table as diesel::query_source::AppearsInFromClause<db::schema::weapon_ends::table>>::Count == diesel::query_source::Never`
   --> src\db\helpers.rs:192:16
    |
192 |         filters.push(Box::new(weapon_ends::damage_dice.ge(min_num_dice)))
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `diesel::query_source::Once`, found struct `diesel::query_source::Never`
    |
    = note: required because of the requirements on the impl of `diesel::SelectableExpression<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>>` for `db::schema::weapon_ends::columns::damage_dice`
    = note: required because of the requirements on the impl of `diesel::SelectableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<db::schema::weapons::columns::secondary_end>, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>>` for `db::schema::weapon_ends::columns::damage_dice`
    = note: required because of the requirements on the impl of `diesel::SelectableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<db::schema::weapons::columns::secondary_end>, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>>` for `diesel::expression::operators::GtEq<db::schema::weapon_ends::columns::damage_dice, diesel::expression::bound::Bound<diesel::sql_types::SmallInt, i16>>`
    = note: required because of the requirements on the impl of `diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<db::schema::weapons::columns::secondary_end>, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg>` for `diesel::expression::operators::GtEq<db::schema::weapon_ends::columns::damage_dice, diesel::expression::bound::Bound<diesel::sql_types::SmallInt, i16>>`
    = note: required for the cast to the object type `dyn diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<db::schema::weapons::columns::secondary_end>, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>`

error[E0271]: type mismatch resolving `<db::schema::weapons::table as diesel::query_source::AppearsInFromClause<db::schema::weapon_ends::table>>::Count == diesel::query_source::Once`
   --> src\db\helpers.rs:197:16
    |
197 |         filters.push(Box::new(weapon_ends::damage_dice.le(max_num_dice)))
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `diesel::query_source::Never`, found struct `diesel::query_source::Once`
    |
    = note: required because of the requirements on the impl of `diesel::SelectableExpression<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>>` for `db::schema::weapon_ends::columns::damage_dice`
    = note: required because of the requirements on the impl of `diesel::SelectableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<db::schema::weapons::columns::secondary_end>, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>>` for `db::schema::weapon_ends::columns::damage_dice`
    = note: required because of the requirements on the impl of `diesel::SelectableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<db::schema::weapons::columns::secondary_end>, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>>` for `diesel::expression::operators::LtEq<db::schema::weapon_ends::columns::damage_dice, diesel::expression::bound::Bound<diesel::sql_types::SmallInt, i16>>`
    = note: required because of the requirements on the impl of `diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<db::schema::weapons::columns::secondary_end>, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg>` for `diesel::expression::operators::LtEq<db::schema::weapon_ends::columns::damage_dice, diesel::expression::bound::Bound<diesel::sql_types::SmallInt, i16>>`
    = note: required for the cast to the object type `dyn diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<diesel::expression::nullable::Nullable<db::schema::weapons::columns::secondary_end>, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>

// etc etc, this goes on for as many filters as I add

It seems like every other filter I add to the vector in the get_weapon_details_filters gets “Expected Once, found Never”, while every other filter gets “Expected Never, found Once”. The documentation does not tell me very much about these traits.

Any additional help hugely appreciated.

That error message makes sense. SelectableExpression<QS> is used to check if a expression is a valid select clause for a given from clause QS. For LEFT JOINS the plain field is no valid expression as LEFT JOINS may return null values for not nullable fields. Therefore diesel requires that you call .nullable() on each field coming from a LEFT JOIN. You can potentially fix those error messages by just calling .nullable() on any field coming from the joined table.

Calling nullable on all the filters that gets pushed onto the vector makes get_weapon_details_filters compile…

type WeaponsWeaponEndsJoin = Join<weapons::table, weapon_ends::table, LeftOuter>;
type WeaponsWeaponEndsJoinOn = JoinOn<WeaponsWeaponEndsJoin, Or<Eq<weapons::primary_end, weapon_ends::id>, Eq<weapons::secondary_end, Nullable<weapon_ends::id>>>>;

pub type WeaponEndsFilter = Box<dyn BoxableExpression<WeaponsWeaponEndsJoinOn, DB, SqlType = Bool>>;

pub fn get_weapon_details_filters(query: &ItemQuery) -> Option<WeaponEndsFilter>
{
	let mut filters: Vec<WeaponEndsFilter> = Vec::new();

	if let Some(min_num_dice) = query.min_num_dice
	{
		filters.push(Box::new(weapon_ends::damage_dice.nullable().ge(min_num_dice)))
	}

	// ... push more filters with .nullable()...

	filters.into_iter().fold_first(|acc, f| {
		Box::new(acc.and(f))
	})
} // Compiles!

… but when I then apply it to my query on items

	if let Some(f) = helpers::get_weapon_details_filters(&query)
	{
		results = results.filter(items::id.eq(any(
			weapons::table
				.select(weapons::item_id)
				.left_join(weapon_ends::table.on(weapons::primary_end.eq(weapon_ends::id).or(weapons::secondary_end.eq(weapon_ends::id.nullable()))))
				.filter(f)
		)))
	}
error[E0277]: the trait bound `dyn diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>: diesel::AppearsOnTable<diesel::query_source::joins::Join<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, db::schema::items::table, diesel::query_source::joins::Inner>>` is not satisfied
   --> src\subpages\archive\views\browse.rs:424:28
    |
424 |           results = results.filter(items::id.eq(any(
    |  __________________________________^
425 | |             weapons::table
426 | |                 .select(weapons::item_id)
427 | |                 .left_join(weapon_ends::table.on(weapons::primary_end.eq(weapon_ends::id).or(weapons::secondary_end.eq(weapon_ends::id.nullable()))))
428 | |                 .filter(f)
429 | |         )))
    | |__________^ the trait `diesel::AppearsOnTable<diesel::query_source::joins::Join<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, db::schema::items::table, diesel::query_source::joins::Inner>>` is not implemented for `dyn diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>`
    |
    = note: required because of the requirements on the impl of `diesel::AppearsOnTable<diesel::query_source::joins::Join<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, db::schema::items::table, diesel::query_source::joins::Inner>>` for `std::boxed::Box<dyn diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>>`
    = note: required because of the requirements on the impl of `diesel::query_builder::where_clause::ValidWhereClause<diesel::query_source::joins::Join<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, db::schema::items::table, diesel::query_source::joins::Inner>>` for `diesel::query_builder::where_clause::WhereClause<std::boxed::Box<dyn diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>>>`
    = note: required because of the requirements on the impl of `diesel::expression::subselect::ValidSubselect<db::schema::items::table>` for `diesel::query_builder::SelectStatement<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::query_builder::select_clause::SelectClause<db::schema::weapons::columns::item_id>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<std::boxed::Box<dyn diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>>>>`
    = note: required because of the requirements on the impl of `diesel::AppearsOnTable<db::schema::items::table>` for `diesel::expression::subselect::Subselect<diesel::query_builder::SelectStatement<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::query_builder::select_clause::SelectClause<db::schema::weapons::columns::item_id>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<std::boxed::Box<dyn diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>>>>, diesel::sql_types::Array<diesel::sql_types::Integer>>`
    = note: required because of the requirements on the impl of `diesel::AppearsOnTable<db::schema::items::table>` for `diesel::pg::expression::array_comparison::Any<diesel::expression::subselect::Subselect<diesel::query_builder::SelectStatement<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::query_builder::select_clause::SelectClause<db::schema::weapons::columns::item_id>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<std::boxed::Box<dyn diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>>>>, diesel::sql_types::Array<diesel::sql_types::Integer>>>`
    = note: required because of the requirements on the impl of `diesel::AppearsOnTable<db::schema::items::table>` for `diesel::expression::operators::Eq<db::schema::items::columns::id, diesel::pg::expression::array_comparison::Any<diesel::expression::subselect::Subselect<diesel::query_builder::SelectStatement<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::query_builder::select_clause::SelectClause<db::schema::weapons::columns::item_id>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<std::boxed::Box<dyn diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>>>>, diesel::sql_types::Array<diesel::sql_types::Integer>>>>`
    = note: required because of the requirements on the impl of `diesel::query_dsl::filter_dsl::FilterDsl<diesel::expression::operators::Eq<db::schema::items::columns::id, diesel::pg::expression::array_comparison::Any<diesel::expression::subselect::Subselect<diesel::query_builder::SelectStatement<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::query_builder::select_clause::SelectClause<db::schema::weapons::columns::item_id>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<std::boxed::Box<dyn diesel::BoxableExpression<diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::schema::weapons::table, db::schema::weapon_ends::table, diesel::query_source::joins::LeftOuter>, diesel::expression::grouped::Grouped<diesel::expression::operators::Or<diesel::expression::operators::Eq<db::schema::weapons::columns::primary_end, db::schema::weapon_ends::columns::id>, diesel::expression::operators::Eq<db::schema::weapons::columns::secondary_end, diesel::expression::nullable::Nullable<db::schema::weapon_ends::columns::id>>>>>, diesel::pg::Pg, SqlType = diesel::sql_types::Bool>>>>, diesel::sql_types::Array<diesel::sql_types::Integer>>>>>` for `diesel::query_builder::BoxedSelectStatement<'_, (diesel::sql_types::Text, diesel::sql_types::Float, diesel::sql_types::Float, db::sql_types::slot::db_enum_impl_Slot::Item_slot, diesel::sql_types::Bool, db::sql_types::source::db_enum_impl_Source::Item_source), db::schema::items::table, diesel::pg::Pg>`

This looks very similar to the first error I got but I still have no clue how to solve it…
Thanks for the help so far - hope you will continue to indulge me.