@@ -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