diff --git a/sqlx-core/src/query_builder.rs b/sqlx-core/src/query_builder.rs index 761903f0e8..adb7b6b2fa 100644 --- a/sqlx-core/src/query_builder.rs +++ b/sqlx-core/src/query_builder.rs @@ -272,6 +272,113 @@ where separated.query_builder } + /// Creates `((a, b), (..)` statements, from `tuples`. + /// + /// This can be used to construct a bulk `SELECT` statement like this: + /// ```sql + /// SELECT * FROM users WHERE (id, username) IN ((1, "test_user_1"), (2, "test_user_2")) + /// ``` + /// + /// Although keep in mind that all + /// databases have some practical limit on the number of bind arguments in a single query. + /// See [`.push_bind()`][Self::push_bind] for details. + /// + /// To be safe, you can do `tuples.into_iter().take(N)` where `N` is the limit for your database + /// divided by the number of fields in each tuple; since integer division always rounds down, + /// this will ensure that you don't exceed the limit. + /// + /// ### Notes + /// + /// If `tuples` is empty, this will likely produce a syntactically invalid query + /// + /// ### Example (MySQL) + /// + /// ```rust + /// # #[cfg(feature = "mysql")] + /// # { + /// use sqlx::{Execute, MySql, QueryBuilder}; + /// + /// struct User { + /// id: i32, + /// username: String, + /// email: String, + /// password: String, + /// } + /// + /// // The number of parameters in MySQL must fit in a `u16`. + /// const BIND_LIMIT: usize = 65535; + /// + /// // This would normally produce values forever! + /// let users = (0..).map(|i| User { + /// id: i, + /// username: format!("test_user_{}", i), + /// email: format!("test-user-{}@example.com", i), + /// password: format!("Test!User@Password#{}", i), + /// }); + /// + /// let mut query_builder: QueryBuilder = QueryBuilder::new( + /// // Note the trailing space; most calls to `QueryBuilder` don't automatically insert + /// // spaces as that might interfere with identifiers or quoted strings where exact + /// // values may matter. + /// "SELECT * FROM users WHERE (id, username, email, password) in" + /// ); + /// + /// // Note that `.into_iter()` wasn't needed here since `users` is already an iterator. + /// query_builder.push_tuples(users.take(BIND_LIMIT / 4), |mut b, user| { + /// // If you wanted to bind these by-reference instead of by-value, + /// // you'd need an iterator that yields references that live as long as `query_builder`, + /// // e.g. collect it to a `Vec` first. + /// b.push_bind(user.id) + /// .push_bind(user.username) + /// .push_bind(user.email) + /// .push_bind(user.password); + /// }); + /// + /// let mut query = query_builder.build(); + /// + /// // You can then call `query.execute()`, `.fetch_one()`, `.fetch_all()`, etc. + /// // For the sake of demonstration though, we're just going to assert the contents + /// // of the query. + /// + /// // These are methods of the `Execute` trait, not normally meant to be called in user code. + /// let sql = query.sql(); + /// let arguments = query.take_arguments().unwrap(); + /// + /// assert!(sql.starts_with( + /// "SELECT * FROM users WHERE (id, username, email, password) in ((?, ?, ?, ?), (?, ?, ?, ?), " + /// )); + /// + /// assert!(sql.ends_with("(?, ?, ?, ?)) ")); + /// + /// // Not a normally exposed function, only used for this doctest. + /// // 65535 / 4 = 16383 (rounded down) + /// // 16383 * 4 = 65532 + /// assert_eq!(arguments.len(), 65532); + /// } + pub fn push_tuples(&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(); + + self.push(" ("); + + let mut separated = self.separated(", "); + + for tuple in tuples { + separated.push("("); + + // use a `Separated` with a separate (hah) internal state + push_tuple(separated.query_builder.separated(", "), tuple); + + separated.push_unseparated(")"); + } + separated.push_unseparated(") "); + + separated.query_builder + } + /// Produce an executable query from this builder. /// /// ### Note: Query is not Checked