Skip to content

Commit 28e22e1

Browse files
authored
add push_tuples for QueryBuilder (launchbadge#1954)
* add `push_tuples` for QueryBuilder * update docs * fix docs
1 parent 9534de3 commit 28e22e1

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed

sqlx-core/src/query_builder.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,113 @@ where
272272
separated.query_builder
273273
}
274274

275+
/// Creates `((a, b), (..)` statements, from `tuples`.
276+
///
277+
/// This can be used to construct a bulk `SELECT` statement like this:
278+
/// ```sql
279+
/// SELECT * FROM users WHERE (id, username) IN ((1, "test_user_1"), (2, "test_user_2"))
280+
/// ```
281+
///
282+
/// Although keep in mind that all
283+
/// databases have some practical limit on the number of bind arguments in a single query.
284+
/// See [`.push_bind()`][Self::push_bind] for details.
285+
///
286+
/// To be safe, you can do `tuples.into_iter().take(N)` where `N` is the limit for your database
287+
/// divided by the number of fields in each tuple; since integer division always rounds down,
288+
/// this will ensure that you don't exceed the limit.
289+
///
290+
/// ### Notes
291+
///
292+
/// If `tuples` is empty, this will likely produce a syntactically invalid query
293+
///
294+
/// ### Example (MySQL)
295+
///
296+
/// ```rust
297+
/// # #[cfg(feature = "mysql")]
298+
/// # {
299+
/// use sqlx::{Execute, MySql, QueryBuilder};
300+
///
301+
/// struct User {
302+
/// id: i32,
303+
/// username: String,
304+
/// email: String,
305+
/// password: String,
306+
/// }
307+
///
308+
/// // The number of parameters in MySQL must fit in a `u16`.
309+
/// const BIND_LIMIT: usize = 65535;
310+
///
311+
/// // This would normally produce values forever!
312+
/// let users = (0..).map(|i| User {
313+
/// id: i,
314+
/// username: format!("test_user_{}", i),
315+
/// email: format!("test-user-{}@example.com", i),
316+
/// password: format!("Test!User@Password#{}", i),
317+
/// });
318+
///
319+
/// let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new(
320+
/// // Note the trailing space; most calls to `QueryBuilder` don't automatically insert
321+
/// // spaces as that might interfere with identifiers or quoted strings where exact
322+
/// // values may matter.
323+
/// "SELECT * FROM users WHERE (id, username, email, password) in"
324+
/// );
325+
///
326+
/// // Note that `.into_iter()` wasn't needed here since `users` is already an iterator.
327+
/// query_builder.push_tuples(users.take(BIND_LIMIT / 4), |mut b, user| {
328+
/// // If you wanted to bind these by-reference instead of by-value,
329+
/// // you'd need an iterator that yields references that live as long as `query_builder`,
330+
/// // e.g. collect it to a `Vec` first.
331+
/// b.push_bind(user.id)
332+
/// .push_bind(user.username)
333+
/// .push_bind(user.email)
334+
/// .push_bind(user.password);
335+
/// });
336+
///
337+
/// let mut query = query_builder.build();
338+
///
339+
/// // You can then call `query.execute()`, `.fetch_one()`, `.fetch_all()`, etc.
340+
/// // For the sake of demonstration though, we're just going to assert the contents
341+
/// // of the query.
342+
///
343+
/// // These are methods of the `Execute` trait, not normally meant to be called in user code.
344+
/// let sql = query.sql();
345+
/// let arguments = query.take_arguments().unwrap();
346+
///
347+
/// assert!(sql.starts_with(
348+
/// "SELECT * FROM users WHERE (id, username, email, password) in ((?, ?, ?, ?), (?, ?, ?, ?), "
349+
/// ));
350+
///
351+
/// assert!(sql.ends_with("(?, ?, ?, ?)) "));
352+
///
353+
/// // Not a normally exposed function, only used for this doctest.
354+
/// // 65535 / 4 = 16383 (rounded down)
355+
/// // 16383 * 4 = 65532
356+
/// assert_eq!(arguments.len(), 65532);
357+
/// }
358+
pub fn push_tuples<I, F>(&mut self, tuples: I, mut push_tuple: F) -> &mut Self
359+
where
360+
I: IntoIterator,
361+
F: FnMut(Separated<'_, 'args, DB, &'static str>, I::Item),
362+
{
363+
self.sanity_check();
364+
365+
self.push(" (");
366+
367+
let mut separated = self.separated(", ");
368+
369+
for tuple in tuples {
370+
separated.push("(");
371+
372+
// use a `Separated` with a separate (hah) internal state
373+
push_tuple(separated.query_builder.separated(", "), tuple);
374+
375+
separated.push_unseparated(")");
376+
}
377+
separated.push_unseparated(") ");
378+
379+
separated.query_builder
380+
}
381+
275382
/// Produce an executable query from this builder.
276383
///
277384
/// ### Note: Query is not Checked

0 commit comments

Comments
 (0)