add push_tuples for QueryBuilder (#1954)

* add `push_tuples` for QueryBuilder

* update docs

* fix docs
This commit is contained in:
Vladimir 2022-07-15 21:11:21 +02:00 committed by GitHub
parent 9534de3476
commit 28e22e1259
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -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<MySql> = 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<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();
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