Skip to content

Commit fa2b56a

Browse files
abonanderkukabi
authored andcommitted
feat: add raw_sql API (launchbadge#3007)
This is meant to be much easier to discover than the current approach of directly invoking `Executor` methods. In addition, I'm improving documentation for the `query*()` functions across the board.
1 parent 0d3fec6 commit fa2b56a

File tree

9 files changed

+839
-37
lines changed

9 files changed

+839
-37
lines changed

sqlx-core/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ pub mod net;
7474
pub mod query_as;
7575
pub mod query_builder;
7676
pub mod query_scalar;
77+
78+
pub mod raw_sql;
7779
pub mod row;
7880
pub mod rt;
7981
pub mod sync;

sqlx-core/src/query.rs

Lines changed: 207 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::executor::{Execute, Executor};
1212
use crate::statement::Statement;
1313
use crate::types::Type;
1414

15-
/// Raw SQL query with bind parameters. Returned by [`query`][crate::query::query].
15+
/// A single SQL query as a prepared statement. Returned by [`query()`].
1616
#[must_use = "query must be executed to affect database"]
1717
pub struct Query<'q, DB: Database, A> {
1818
pub(crate) statement: Either<&'q str, &'q <DB as HasStatement<'q>>::Statement>,
@@ -21,7 +21,9 @@ pub struct Query<'q, DB: Database, A> {
2121
pub(crate) persistent: bool,
2222
}
2323

24-
/// SQL query that will map its results to owned Rust types.
24+
/// A single SQL query that will map its results to an owned Rust type.
25+
///
26+
/// Executes as a prepared statement.
2527
///
2628
/// Returned by [`Query::try_map`], `query!()`, etc. Has most of the same methods as [`Query`] but
2729
/// the return types are changed to reflect the mapping. However, there is no equivalent of
@@ -96,6 +98,8 @@ where
9698
/// matching the one with the flag will use the cached statement until the
9799
/// cache is cleared.
98100
///
101+
/// If `false`, the prepared statement will be closed after execution.
102+
///
99103
/// Default: `true`.
100104
pub fn persistent(mut self, value: bool) -> Self {
101105
self.persistent = value;
@@ -155,6 +159,7 @@ where
155159

156160
/// Execute multiple queries and return the rows affected from each query, in a stream.
157161
#[inline]
162+
#[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead."]
158163
pub async fn execute_many<'e, 'c: 'e, E>(
159164
self,
160165
executor: E,
@@ -178,9 +183,13 @@ where
178183
executor.fetch(self)
179184
}
180185

181-
/// Execute multiple queries and return the generated results as a stream
182-
/// from each query, in a stream.
186+
/// Execute multiple queries and return the generated results as a stream.
187+
///
188+
/// For each query in the stream, any generated rows are returned first,
189+
/// then the `QueryResult` with the number of rows affected.
183190
#[inline]
191+
#[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead."]
192+
// TODO: we'll probably still want a way to get the `DB::QueryResult` at the end of a `fetch()` stream.
184193
pub fn fetch_many<'e, 'c: 'e, E>(
185194
self,
186195
executor: E,
@@ -193,7 +202,13 @@ where
193202
executor.fetch_many(self)
194203
}
195204

196-
/// Execute the query and return all the generated results, collected into a [`Vec`].
205+
/// Execute the query and return all the resulting rows collected into a [`Vec`].
206+
///
207+
/// ### Note: beware result set size.
208+
/// This will attempt to collect the full result set of the query into memory.
209+
///
210+
/// To avoid exhausting available memory, ensure the result set has a known upper bound,
211+
/// e.g. using `LIMIT`.
197212
#[inline]
198213
pub async fn fetch_all<'e, 'c: 'e, E>(self, executor: E) -> Result<Vec<DB::Row>, Error>
199214
where
@@ -204,7 +219,18 @@ where
204219
executor.fetch_all(self).await
205220
}
206221

207-
/// Execute the query and returns exactly one row.
222+
/// Execute the query, returning the first row or [`Error::RowNotFound`] otherwise.
223+
///
224+
/// ### Note: for best performance, ensure the query returns at most one row.
225+
/// Depending on the driver implementation, if your query can return more than one row,
226+
/// it may lead to wasted CPU time and bandwidth on the database server.
227+
///
228+
/// Even when the driver implementation takes this into account, ensuring the query returns at most one row
229+
/// can result in a more optimal query plan.
230+
///
231+
/// If your query has a `WHERE` clause filtering a unique column by a single value, you're good.
232+
///
233+
/// Otherwise, you might want to add `LIMIT 1` to your query.
208234
#[inline]
209235
pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> Result<DB::Row, Error>
210236
where
@@ -215,7 +241,18 @@ where
215241
executor.fetch_one(self).await
216242
}
217243

218-
/// Execute the query and returns at most one row.
244+
/// Execute the query, returning the first row or `None` otherwise.
245+
///
246+
/// ### Note: for best performance, ensure the query returns at most one row.
247+
/// Depending on the driver implementation, if your query can return more than one row,
248+
/// it may lead to wasted CPU time and bandwidth on the database server.
249+
///
250+
/// Even when the driver implementation takes this into account, ensuring the query returns at most one row
251+
/// can result in a more optimal query plan.
252+
///
253+
/// If your query has a `WHERE` clause filtering a unique column by a single value, you're good.
254+
///
255+
/// Otherwise, you might want to add `LIMIT 1` to your query.
219256
#[inline]
220257
pub async fn fetch_optional<'e, 'c: 'e, E>(self, executor: E) -> Result<Option<DB::Row>, Error>
221258
where
@@ -307,6 +344,9 @@ where
307344
F: 'e,
308345
O: 'e,
309346
{
347+
// FIXME: this should have used `executor.fetch()` but that's a breaking change
348+
// because this technically allows multiple statements in one query string.
349+
#[allow(deprecated)]
310350
self.fetch_many(executor)
311351
.try_filter_map(|step| async move {
312352
Ok(match step {
@@ -319,6 +359,7 @@ where
319359

320360
/// Execute multiple queries and return the generated results as a stream
321361
/// from each query, in a stream.
362+
#[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead."]
322363
pub fn fetch_many<'e, 'c: 'e, E>(
323364
mut self,
324365
executor: E,
@@ -346,7 +387,13 @@ where
346387
})
347388
}
348389

349-
/// Execute the query and return all the generated results, collected into a [`Vec`].
390+
/// Execute the query and return all the resulting rows collected into a [`Vec`].
391+
///
392+
/// ### Note: beware result set size.
393+
/// This will attempt to collect the full result set of the query into memory.
394+
///
395+
/// To avoid exhausting available memory, ensure the result set has a known upper bound,
396+
/// e.g. using `LIMIT`.
350397
pub async fn fetch_all<'e, 'c: 'e, E>(self, executor: E) -> Result<Vec<O>, Error>
351398
where
352399
'q: 'e,
@@ -358,7 +405,18 @@ where
358405
self.fetch(executor).try_collect().await
359406
}
360407

361-
/// Execute the query and returns exactly one row.
408+
/// Execute the query, returning the first row or [`Error::RowNotFound`] otherwise.
409+
///
410+
/// ### Note: for best performance, ensure the query returns at most one row.
411+
/// Depending on the driver implementation, if your query can return more than one row,
412+
/// it may lead to wasted CPU time and bandwidth on the database server.
413+
///
414+
/// Even when the driver implementation takes this into account, ensuring the query returns at most one row
415+
/// can result in a more optimal query plan.
416+
///
417+
/// If your query has a `WHERE` clause filtering a unique column by a single value, you're good.
418+
///
419+
/// Otherwise, you might want to add `LIMIT 1` to your query.
362420
pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> Result<O, Error>
363421
where
364422
'q: 'e,
@@ -375,7 +433,18 @@ where
375433
.await
376434
}
377435

378-
/// Execute the query and returns at most one row.
436+
/// Execute the query, returning the first row or `None` otherwise.
437+
///
438+
/// ### Note: for best performance, ensure the query returns at most one row.
439+
/// Depending on the driver implementation, if your query can return more than one row,
440+
/// it may lead to wasted CPU time and bandwidth on the database server.
441+
///
442+
/// Even when the driver implementation takes this into account, ensuring the query returns at most one row
443+
/// can result in a more optimal query plan.
444+
///
445+
/// If your query has a `WHERE` clause filtering a unique column by a single value, you're good.
446+
///
447+
/// Otherwise, you might want to add `LIMIT 1` to your query.
379448
pub async fn fetch_optional<'e, 'c: 'e, E>(mut self, executor: E) -> Result<Option<O>, Error>
380449
where
381450
'q: 'e,
@@ -394,7 +463,7 @@ where
394463
}
395464
}
396465

397-
// Make a SQL query from a statement.
466+
/// Execute a single SQL query as a prepared statement (explicitly created).
398467
pub fn query_statement<'q, DB>(
399468
statement: &'q <DB as HasStatement<'q>>::Statement,
400469
) -> Query<'q, DB, <DB as HasArguments<'_>>::Arguments>
@@ -409,7 +478,7 @@ where
409478
}
410479
}
411480

412-
// Make a SQL query from a statement, with the given arguments.
481+
/// Execute a single SQL query as a prepared statement (explicitly created), with the given arguments.
413482
pub fn query_statement_with<'q, DB, A>(
414483
statement: &'q <DB as HasStatement<'q>>::Statement,
415484
arguments: A,
@@ -426,7 +495,129 @@ where
426495
}
427496
}
428497

429-
/// Make a SQL query.
498+
/// Execute a single SQL query as a prepared statement (transparently cached).
499+
///
500+
/// The query string may only contain a single DML statement: `SELECT`, `INSERT`, `UPDATE`, `DELETE` and variants.
501+
/// The SQLite driver does not currently follow this restriction, but that behavior is deprecated.
502+
///
503+
/// The connection will transparently prepare and cache the statement, which means it only needs to be parsed once
504+
/// in the connection's lifetime, and any generated query plans can be retained.
505+
/// Thus, the overhead of executing the statement is amortized.
506+
///
507+
/// Some third-party databases that speak a supported protocol, e.g. CockroachDB or PGBouncer that speak Postgres,
508+
/// may have issues with the transparent caching of prepared statements. If you are having trouble,
509+
/// try setting [`.persistent(false)`][Query::persistent].
510+
///
511+
/// See the [`Query`] type for the methods you may call.
512+
///
513+
/// ### Dynamic Input: Use Query Parameters (Prevents SQL Injection)
514+
/// At some point, you'll likely want to include some form of dynamic input in your query, possibly from the user.
515+
///
516+
/// Your first instinct might be to do something like this:
517+
/// ```rust,no_run
518+
/// # async fn example() -> sqlx::Result<()> {
519+
/// # let mut conn: sqlx::PgConnection = unimplemented!();
520+
/// // Imagine this is input from the user, e.g. a search form on a website.
521+
/// let user_input = "possibly untrustworthy input!";
522+
///
523+
/// // DO NOT DO THIS unless you're ABSOLUTELY CERTAIN it's what you need!
524+
/// let query = format!("SELECT * FROM articles WHERE content LIKE '%{user_input}%'");
525+
/// // where `conn` is `PgConnection` or `MySqlConnection`
526+
/// // or some other type that implements `Executor`.
527+
/// let results = sqlx::query(&query).fetch_all(&mut conn).await?;
528+
/// # }
529+
/// ```
530+
///
531+
/// The example above showcases a **SQL injection vulnerability**, because it's trivial for a malicious user to craft
532+
/// an input that can "break out" of the string literal.
533+
///
534+
/// For example, if they send the input `foo'; DELETE FROM articles; --`
535+
/// then your application would send the following to the database server (line breaks added for clarity):
536+
///
537+
/// ```sql
538+
/// SELECT * FROM articles WHERE content LIKE '%foo';
539+
/// DELETE FROM articles;
540+
/// --%'
541+
/// ```
542+
///
543+
/// In this case, because this interface *always* uses prepared statements, you would likely be fine because prepared
544+
/// statements _generally_ (see above) are only allowed to contain a single query. This would simply return an error.
545+
///
546+
/// However, it would also break on legitimate user input.
547+
/// What if someone wanted to search for the string `Alice's Apples`? It would also return an error because
548+
/// the database would receive a query with a broken string literal (line breaks added for clarity):
549+
///
550+
/// ```sql
551+
/// SELECT * FROM articles WHERE content LIKE '%Alice'
552+
/// s Apples%'
553+
/// ```
554+
///
555+
/// Of course, it's possible to make this syntactically valid by escaping the apostrophe, but there's a better way.
556+
///
557+
/// ##### You should always prefer query parameters for dynamic input.
558+
///
559+
/// When using query parameters, you add placeholders to your query where a value
560+
/// should be substituted at execution time, then call [`.bind()`][Query::bind] with that value.
561+
///
562+
/// The syntax for placeholders is unfortunately not standardized and depends on the database:
563+
///
564+
/// * Postgres and SQLite: use `$1`, `$2`, `$3`, etc.
565+
/// * The number is the Nth bound value, starting from one.
566+
/// * The same placeholder can be used arbitrarily many times to refer to the same bound value.
567+
/// * SQLite technically supports MySQL's syntax as well as others, but we recommend using this syntax
568+
/// as SQLx's SQLite driver is written with it in mind.
569+
/// * MySQL and MariaDB: use `?`.
570+
/// * Placeholders are purely positional, similar to `println!("{}, {}", foo, bar)`.
571+
/// * The order of bindings must match the order of placeholders in the query.
572+
/// * To use a value in multiple places, you must bind it multiple times.
573+
///
574+
/// In both cases, the placeholder syntax acts as a variable expression representing the bound value:
575+
///
576+
/// ```rust,no_run
577+
/// # async fn example2() -> sqlx::Result<()> {
578+
/// # let mut conn: sqlx::PgConnection = unimplemented!();
579+
/// let user_input = "Alice's Apples";
580+
///
581+
/// // Postgres and SQLite
582+
/// let results = sqlx::query(
583+
/// // Notice how we only have to bind the argument once and we can use it multiple times:
584+
/// "SELECT * FROM articles
585+
/// WHERE title LIKE '%' || $1 || '%'
586+
/// OR content LIKE '%' || $1 || '%'"
587+
/// )
588+
/// .bind(user_input)
589+
/// .fetch_all(&mut conn)
590+
/// .await?;
591+
///
592+
/// // MySQL and MariaDB
593+
/// let results = sqlx::query(
594+
/// "SELECT * FROM articles
595+
/// WHERE title LIKE CONCAT('%', ?, '%')
596+
/// OR content LIKE CONCAT('%', ?, '%')"
597+
/// )
598+
/// // If we want to reference the same value multiple times, we have to bind it multiple times:
599+
/// .bind(user_input)
600+
/// .bind(user_input)
601+
/// .fetch_all(&mut conn)
602+
/// .await?;
603+
/// # Ok(())
604+
/// # }
605+
/// ```
606+
/// ##### The value bound to a query parameter is entirely separate from the query and does not affect its syntax.
607+
/// Thus, SQL injection is impossible (barring shenanigans like calling a SQL function that lets you execute a string
608+
/// as a statement) and *all* strings are valid.
609+
///
610+
/// This also means you cannot use query parameters to add conditional SQL fragments.
611+
///
612+
/// **SQLx does not substitute placeholders on the client side**. It is done by the database server itself.
613+
///
614+
/// ##### SQLx supports many different types for parameter binding, not just strings.
615+
/// Any type that implements [`Encode<DB>`][Encode] and [`Type<DB>`] can be bound as a parameter.
616+
///
617+
/// See [the `types` module][crate::types] (links to `sqlx_core::types` but you should use `sqlx::types`) for details.
618+
///
619+
/// As an additional benefit, query parameters are usually sent in a compact binary encoding instead of a human-readable
620+
/// text encoding, which saves bandwidth.
430621
pub fn query<DB>(sql: &str) -> Query<'_, DB, <DB as HasArguments<'_>>::Arguments>
431622
where
432623
DB: Database,
@@ -439,7 +630,9 @@ where
439630
}
440631
}
441632

442-
/// Make a SQL query, with the given arguments.
633+
/// Execute a SQL query as a prepared statement (transparently cached), with the given arguments.
634+
///
635+
/// See [`query()`][query] for details, such as supported syntax.
443636
pub fn query_with<'q, DB, A>(sql: &'q str, arguments: A) -> Query<'q, DB, A>
444637
where
445638
DB: Database,

0 commit comments

Comments
 (0)