QueryBuilder::push_values() automatically adds the VALUES keyword every time it is called.
This works well for a single call, but becomes problematic when push_values() needs to be called multiple times while constructing one bulk INSERT query, especially when the data comes from nested collections.
I would like to propose adding a way to push generated value tuples without automatically adding the VALUES keyword.
Description
Consider the following use case:
for (index, varient) in varients.iter().enumerate() {
if let Some(product_varient) = product.product_varient.get(index) {
qb2.push_values(&product_varient.varient_images, |mut b, single_varient| {
b.push_bind(&single_varient.image_url)
.push_bind(single_varient.display_order)
.push_bind(varient.id);
});
}
}
When push_values() is called multiple times, each invocation adds the VALUES keyword.
For example, the generated SQL can become:
VALUES ($1, $2, $3), ($4, $5, $6)
VALUES ($7, $8, $9), ($10, $11, $12)
This is invalid SQL.
The desired output is:
VALUES
($1, $2, $3),
($4, $5, $6),
($7, $8, $9),
($10, $11, $12)
The underlying problem is that push_values() currently combines two responsibilities:
- Adding the
VALUES keyword.
- Generating comma-separated parenthesized value tuples.
When building queries dynamically, these two operations sometimes need to be controlled independently.
Prefered solution
Add a push_args() method that performs the tuple-generation part of push_values() without adding the VALUES keyword.
For example:
pub fn push_args<I, F>(&mut self, tuples: I, mut push_tuple: F) -> &mut Self
where
I: IntoIterator,
F: FnMut(Separated<'_, 'args, DB, &'static str>, I::Item),
{
self.sanity_check();
let mut separated = self.separated(", ");
for tuple in tuples {
separated.push("(");
push_tuple(separated.query_builder.separated(", "), tuple);
separated.push_unseparated(")");
}
debug_assert!(
separated.push_separator,
"No value being pushed. QueryBuilder may not build correct sql query!"
);
separated.query_builder
}
Then the caller can explicitly control the VALUES keyword:
let mut qb = QueryBuilder::new(
"INSERT INTO product_variant_images \
(image_url, display_order, variant_id) VALUES "
);
let mut first = true;
for (index, varient) in varients.iter().enumerate() {
if let Some(product_varient) = product.product_varient.get(index) {
if !first {
qb.push(", ");
}
first = false;
qb.push_args(&product_varient.varient_images, |mut b, single_varient| {
b.push_bind(&single_varient.image_url)
.push_bind(single_varient.display_order)
.push_bind(varient.id);
});
}
}
This produces a single VALUES clause with all tuples.
The existing push_values() API can remain unchanged. Its implementation could potentially reuse push_args() internally:
pub fn push_values<I, F>(&mut self, tuples: I, push_tuple: F) -> &mut Self
where
I: IntoIterator,
F: FnMut(Separated<'_, 'args, DB, &'static str>, I::Item),
{
self.push("VALUES ");
self.push_args(tuples, push_tuple)
}
I'm open to feedback on the method name or API design if there is a preferred approach for QueryBuilder.
If the maintainers agree that this API/use case is appropriate, I would be happy to implement this change and submit the PR, including the necessary tests.
If possible, could you please confirm whether I can take this issue up for implementation? I would like to work on the implementation and tests myself rather than having multiple contributors work on the same change.
I'm also happy to adjust the proposed API or implementation based on the maintainers' feedback before starting the PR.
Is this a breaking change? Why or why not?
No, this should not be a breaking change.
The proposed push_args() method is a new API and does not change the existing behavior or signature of push_values().
Existing code using push_values() will continue to work as before.
If push_values() is internally refactored to use push_args(), the externally observable behavior of push_values() remains unchanged.
- SQLX VERSION : sqlx = { version = "0.8" }
- DATABASE DRIVER : Postgresql
- DATABASE VERSION : 17
QueryBuilder::push_values()automatically adds theVALUESkeyword every time it is called.This works well for a single call, but becomes problematic when
push_values()needs to be called multiple times while constructing one bulkINSERTquery, especially when the data comes from nested collections.I would like to propose adding a way to push generated value tuples without automatically adding the VALUES keyword.
Description
Consider the following use case:
When
push_values()is called multiple times, each invocation adds theVALUESkeyword.For example, the generated SQL can become:
This is invalid SQL.
The desired output is:
The underlying problem is that
push_values()currently combines two responsibilities:VALUESkeyword.When building queries dynamically, these two operations sometimes need to be controlled independently.
Prefered solution
Add a
push_args()method that performs the tuple-generation part ofpush_values()without adding theVALUESkeyword.For example:
Then the caller can explicitly control the
VALUESkeyword:This produces a single
VALUESclause with all tuples.The existing
push_values()API can remain unchanged. Its implementation could potentially reusepush_args()internally:I'm open to feedback on the method name or API design if there is a preferred approach for
QueryBuilder.If the maintainers agree that this API/use case is appropriate, I would be happy to implement this change and submit the PR, including the necessary tests.
If possible, could you please confirm whether I can take this issue up for implementation? I would like to work on the implementation and tests myself rather than having multiple contributors work on the same change.
I'm also happy to adjust the proposed API or implementation based on the maintainers' feedback before starting the PR.
Is this a breaking change? Why or why not?
No, this should not be a breaking change.
The proposed
push_args()method is a new API and does not change the existing behavior or signature ofpush_values().Existing code using
push_values()will continue to work as before.If
push_values()is internally refactored to usepush_args(), the externally observable behavior ofpush_values()remains unchanged.