# Diesel conditional query builder?

**URL:** <https://users.rust-lang.org/t/diesel-conditional-query-builder/15815>\
**Category:** help\
**Created:** [February 23, 2018, 2:42pm UTC](https://users.rust-lang.org/t/diesel-conditional-query-builder/15815 "2018-02-23T14:42:04Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![svartalf](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/svartalf/32/2740_2.png) [@svartalf](https://users.rust-lang.org/u/svartalf)\
**Post date:** [February 23, 2018, 2:42pm UTC](https://users.rust-lang.org/t/diesel-conditional-query-builder/15815/1 "2018-02-23T14:42:04Z")

</div>

I'm trying to create a [conditional query](https://github.com/diesel-rs/diesel/issues/455) builder for diesel query and having a hard time with a generics here, since `diesel::query_builder::BoxedSelectStatement<ST, QS, DB>` is generic over a few parameters.

While I can inline it all into a web controller function, resulting code will bloat drastically, so I want to wrap all that logic into a some kind of builder. Problem is: diesel query methods returns new `BoxedSelectStatement` type objects with different `ST` and `QS` types, and I don't know them at compilation time, since they can vary based on the already joined tables and selected fields.

#### Example table definitions

```
table! {
    table1 {
        id -> Integer,
        title -> String,
    }
}

table! {
    table2 {
        id -> Integer,
        table1_id -> Integer,
        tag -> String,
    }
}

table! {
    table3 {
        id -> Integer,
        table1_id -> Integer,
        tag -> String,
    }
}

table! {
    table4 {
        id -> Integer,
        table1_id -> Integer,
        tag -> String,
    }
}

allow_tables_to_appear_in_same_query!(table1, table2, table3, table4);

```

Following code is not compiling (obviously), but I hope, it will explain my thoughts.

#### Builder

```
use std::boxed::Box;

use diesel::*;
use diesel::mysql::Mysql;
use diesel::query_dsl::*;
use diesel::query_builder::BoxedSelectStatement;

use schema::{table1, table2, table3, table4};

struct Builder<'b, ST, QS>(Box<BoxedSelectStatement<'b, ST, QS, Mysql>>);

impl<'b, ST, QS> Builder<'b, ST, QS> {
    pub fn new() -> Builder<'b, ST, QS> {
        let inner = table1::table.into_boxed();

        Builder(Box::new(inner))
    }

    pub fn with_table2(self, tag: String) -> Builder<'b, ST, QS> {
        let inner = self.0.join(table2::table.on(
            table1::id.eq(table2::table1_id)
                .and(table2::tag.eq(tag))
        ));

        Builder(Box::new(inner))
    }

    pub fn with_table3(self, tag: String) -> Builder<'b, ST, QS> {
        let inner = self.0.join(table3::table.on(
            table1::id.eq(table3::table1_id)
                .and(table3::tag.eq(tag))
        ));

        Builder(Box::new(inner))
    }

    pub fn with_table4(self, tag: String) -> Builder<'b, ST, QS> {
        let inner = self.0.join(table4::table.on(
            table1::id.eq(table4::table1_id)
                .and(table4::tag.eq(tag))
        ));

        Builder(Box::new(inner))
    }

    pub fn finish(self) -> BoxedSelectStatement<'b, ST, QS, Mysql> {
        self.0
    }
}

```

#### Usage example

```
fn main() {
    let query = QueryParams::from_string("?foo=tag2&baz=tag4");
    let builder = Builder::new();

    if query.contains("foo") {
        let builder = builder.with_table2(query.get("foo"));
    }
    if query.contains("bar") {
        let builder = builder.with_table3(query.get("bar"));
    }
    if query.contains("baz") {
        let builder = builder.with_table4(query.get("baz"));
    }

    let sql_query = builder.finish();
    sql_query.load(&connection).unwrap();
}

```

For example, `new()` method will following type:

```
diesel::query_builder::BoxedSelectStatement<'_, (diesel::sql_types::Integer, std::string::String), db::table1::table, Mysql>

```

, and if I call `with_table2()` method later, I'll get

```
diesel::query_builder::BoxedSelectStatement<'_, (diesel::sql_types::Integer, std::string::String), diesel::query_source::joins::JoinOn<diesel::query_source::joins::Join<db::table1::table, _, _>, _>, diesel::mysql::Mysql>

```

So, since `BoxedSelectStatement` is new each time, is there is some way to either ignore generic types at the returned type or move that checks into runtime?  
One option was to hardcore these types into a builder method signatures, but I don't know them until runtime, since tables can be joined in a random order.

---

<div class="post-metadata">

**Author:** ![vitalyd](https://avatars.discourse-cdn.com/v4/letter/v/edb3f5/32.png) [@vitalyd](https://users.rust-lang.org/u/vitalyd)\
**Post date:** [February 23, 2018, 6:10pm UTC](https://users.rust-lang.org/t/diesel-conditional-query-builder/15815/2 "2018-02-23T18:10:47Z")

</div>

Not sure if these diesel types will facilitate this, but you'll need to employ type erasure (i.e. trait objects). The issue you're having is essentially equivalent to using the various `Iterator` or `Future` combinators that build up different concrete types; trait objects are used there to add dynamic behavior.

@sgrif or @killercup will know better.

---

<div class="post-metadata">

**Author:** ![svartalf](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/svartalf/32/2740_2.png) [@svartalf](https://users.rust-lang.org/u/svartalf)\
**Post date:** [February 24, 2018, 12:41pm UTC](https://users.rust-lang.org/t/diesel-conditional-query-builder/15815/3 "2018-02-24T12:41:33Z")

</div>

Probably I failed to explain that properly, but `BoxedSelectStatement` is [a struct](https://github.com/diesel-rs/diesel/blob/master/diesel/src/query_builder/select_statement/boxed.rs#L23), not a trait, so I'm not sure that type erasure is even applicable here.

---

<div class="post-metadata">

**Author:** ![vitalyd](https://avatars.discourse-cdn.com/v4/letter/v/edb3f5/32.png) [@vitalyd](https://users.rust-lang.org/u/vitalyd)\
**Post date:** [February 24, 2018, 3:43pm UTC](https://users.rust-lang.org/t/diesel-conditional-query-builder/15815/4 "2018-02-24T15:43:25Z")

</div>

Yeah, I understood that part. The types returned in the Iterator and Future combinators are also (generic) structs.

What I meant was you may need to find a way to abstract over those concrete generic types using type erasure, and working with trait objects only (ala `Box<Iterator<Item=...>>` or `Box<Future<Item=...,Error=...>>`). If the query execution ultimately needs some trait, and there’s an impl of that trait for a boxed version, it may work. But, I don’t know diesel’s API well enough to say for sure.

---

<div class="post-metadata">

**Author:** ![steffahn](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/steffahn/32/47569_2.png) [@steffahn](https://users.rust-lang.org/u/steffahn)\
**Post date:** [January 12, 2023, 8:46am UTC](https://users.rust-lang.org/t/diesel-conditional-query-builder/15815/5 "2023-01-12T08:46:44Z")

</div>


