Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add push_tuples for QueryBuilder #1954

Merged
merged 6 commits into from
Jul 15, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions sqlx-core/src/query_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down