sqlx icon indicating copy to clipboard operation
sqlx copied to clipboard

Add `merge` to `Arguments`

Open dgagn opened this issue 1 year ago • 2 comments

Add merge to Arguments

Currently, there is no convenient way to merge two sets of query builder arguments.

Why?

In my use case, I need to combine arguments from multiple queries, but there's no existing method to do this.

Here is one of my use case

I am building a custom QueryBuilder that would like to take Arguments as a parameter, but can't because of the limitation of the add method on the trait.

I would like something like :

macro_rules! binding {
    [$($arg:expr),* $(,)?] => {{
        let mut args = <Postgres as sqlx::Database>::Arguments::default();
        $(
            args.add($arg).expect("failed to add binding");
        )*
        args
    }};
}

CustomBuilder::new()
    .select_raw("price * ? as price_tax, ? as name", binding![1.2, "test"])
    .from("products")
    .where_raw("price > ?", binding![10])
    // ...

fn select_raw_expr_bindings<T, B>(mut self, columns: T, bindings: B) -> Self
where
    T: Into<String>,
    B: Arguments<'q>,
{
    self.ensure_bindings();
    self.bindings
        .as_mut()
        .expect("bindings should be initialized")
        .merge(bindings); // Merge here
    self.columns = ColumnExpr::Raw(columns.into());
    self
}

But, this is what I have at the moment :

pub struct SelectBuilder<'q> {
    columns: ColumnExpr<'q>,
    table: Ident<'q>,
    bindings: Option<<Postgres as Database>::Arguments<'q>>,
    where_node: Option<Node<'q>>,
    // ...
}

// ...

fn select_raw_expr<T, B>(mut self, columns: T, bindings: B) -> Self
where
    T: Into<String>,
    B: IntoIterator,
    B::Item: 'q + sqlx::Encode<'q, Postgres> + sqlx::Type<Postgres>,
{
    self.ensure_bindings();
    for binding in bindings {
        self.bindings
          .as_mut()
          .expect("bindings should be initialized")
          .add(value)
          .expect("failed to add value to bindings");
    }
    self.columns = ColumnExpr::Raw(columns.into());
    self
}

What I have has a significant limitation; it requires all bindings in the iterator to be of the same type. For example, [1, "test"] would not work because it combines different types (integer and str) in the same collection.

Introducing a merge function would allow developers to combine multiple sets of Arguments.

Here is another method for which I need to wait for merge to be accepted.

pub fn where_subquery<C, F>(mut self, column: C, operator: Operator, subquery_fn: F) -> Self
where
    C: Into<Ident<'q>>,
    F: FnOnce(SelectFromBuilder) -> SelectBuilder<'q>,
{
    let (query, bindings) = subquery_fn(SelectFromBuilder).into_parts();
    if let Some(bindings) = bindings {
        self.ensure_bindings();
        // TODO: wait for merge to be accepted for subqueries
        todo!()
    } else {
        self.where_raw_expr(
            format!("{} {} ({})", column.into().format(), operator, query),
            None::<Vec<&str>>,
        )
    }
}

Thank you

dgagn avatar Dec 22 '24 03:12 dgagn

@abonander could you review please?

dgagn avatar Apr 30 '25 02:04 dgagn

I get to PRs when I can. Pinging me only fills up my notifications.

abonander avatar Apr 30 '25 02:04 abonander

Bumping this PR—really looking forward to seeing it merged!

cody-why avatar Aug 28 '25 15:08 cody-why