Skip to content

Commit

Permalink
feat: add raw_sql API
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
abonander committed Jan 23, 2024
1 parent a7862ae commit c6ab5a0
Show file tree
Hide file tree
Showing 4 changed files with 426 additions and 11 deletions.
2 changes: 2 additions & 0 deletions sqlx-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ pub mod net;
pub mod query_as;
pub mod query_builder;
pub mod query_scalar;

pub mod raw_sql;
pub mod row;
pub mod rt;
pub mod sync;
Expand Down
170 changes: 159 additions & 11 deletions sqlx-core/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::executor::{Execute, Executor};
use crate::statement::Statement;
use crate::types::Type;

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

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

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

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

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

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

/// Execute the query and returns at most one row.
/// Execute the query, returning the first row or `None` otherwise.
///
/// ### Note: for best performance, ensure the query returns at most one row.
/// Depending on the driver implementation, if your query can return more than one row,
/// it may lead to wasted CPU time and bandwidth on the database server.
///
/// Even when the driver implementation takes this into account, ensuring the query returns at most one row
/// can result in a more optimal query plan.
///
/// If your query has a `WHERE` clause filtering a unique column by a single value, you're good.
///
/// Otherwise, you might want to add `LIMIT 1` to your query.
#[inline]
pub async fn fetch_optional<'e, 'c: 'e, E>(self, executor: E) -> Result<Option<DB::Row>, Error>
where
Expand Down Expand Up @@ -394,7 +431,7 @@ where
}
}

// Make a SQL query from a statement.
/// Execute a single SQL query as a prepared statement (explicitly created).
pub fn query_statement<'q, DB>(
statement: &'q <DB as HasStatement<'q>>::Statement,
) -> Query<'q, DB, <DB as HasArguments<'_>>::Arguments>
Expand All @@ -409,7 +446,7 @@ where
}
}

// Make a SQL query from a statement, with the given arguments.
/// Execute a single SQL query as a prepared statement (explicitly created), with the given arguments.
pub fn query_statement_with<'q, DB, A>(
statement: &'q <DB as HasStatement<'q>>::Statement,
arguments: A,
Expand All @@ -426,7 +463,116 @@ where
}
}

/// Make a SQL query.
/// Execute a single SQL query as a prepared statement (transparently cached).
///
/// The query string may only contain a single DML statement: `SELECT`, `INSERT`, `UPDATE`, `DELETE` and variants.
/// The SQLite driver does not currently follow this restriction, but that behavior is deprecated.
///
/// The connection will transparently prepare and cache the statement, which means it only needs to be parsed once
/// in the connection's lifetime, and any generated query plans can be retained.
/// Thus, the overhead of executing the statement is amortized.
///
/// Some third-party databases that speak a supported protocol, e.g. CockroachDB or PGBouncer that speak Postgres,
/// may have issues with the transparent caching of prepared statements. If you are having trouble,
/// try setting [`.persistent(false)`][Query::persistent].
///
/// See the [`Query`] type for the methods you may call.
///
/// ### Dynamic Input: Use Query Parameters (Prevents SQL Injection)
/// At some point, you'll likely want to include some form of dynamic input in your query, possibly from the user.
///
/// Your first instinct might be to do something like this:
/// ```rust,no_run
/// # async fn example() -> sqlx::Result<()> {
/// # let mut conn: sqlx::PgConnection = unimplemented!();
/// // Imagine this is input from the user, e.g. a search form on a website.
/// let user_input = "possibly untrustworthy input!";
///
/// // DO NOT DO THIS unless you're ABSOLUTELY CERTAIN it's what you need!
/// let query = format!("SELECT * FROM articles WHERE content LIKE '%{user_input}%'");
/// // where `conn` is `PgConnection` or `MySqlConnection`
/// // or some other type that implements `Executor`.
/// let results = sqlx::query(&query).fetch_all(&mut conn).await?;
/// # }
/// ```
///
/// The example above showcases a **SQL injection vulnerability**, because it's trivial for a malicious user to craft
/// an input that can "break out" of the string literal.
///
/// For example, if they send the input `foo'; DELETE FROM articles; --`
/// then your application would send the following to the database server (line breaks added for clarity):
///
/// ```sql
/// SELECT * FROM articles WHERE content LIKE '%foo';
/// DELETE FROM articles;
/// --%'
/// ```
///
/// In this case, because this interface *always* uses prepared statements, you would likely be fine because prepared
/// statements _generally_ (see above) are only allowed to contain a single query. This would simply return an error.
///
/// However, it would also break on legitimate user input.
/// What if someone wanted to search for the string `Alice's Apples`? It would also return an error because
/// the database would receive a query with a broken string literal (line breaks added for clarity):
///
/// ```sql
/// SELECT * FROM articles WHERE content LIKE '%Alice'
/// s Apples%'
/// ```
///
/// Of course, it's possible to make this syntactically valid by escaping the apostrophe, but there's a better way.
///
/// ##### You should always prefer query parameters for dynamic input.
///
/// When using query parameters, you add placeholders to your query where a value
/// should be substituted at execution time, then call [`.bind()`][Query::bind] with that value.
///
/// The syntax for placeholders is unfortunately not standardized and depends on the database:
///
/// * Postgres and SQLite: use `$1`, `$2`, `$3`, etc.
/// * The number is the Nth bound value, starting from one.
/// * The same placeholder can be used arbitrarily many times to refer to the same bound value.
/// * SQLite technically supports MySQL's syntax as well as others, but we recommend using this syntax
/// as SQLx's SQLite driver is written with it in mind.
/// * MySQL and MariaDB: use `?`.
/// * Placeholders are purely positional, like `format!("{}, {}", foo, bar)`.
/// * The order of bindings must match the order of placeholders in the query.
/// * To use a value in multiple places, you must bind it multiple times.
///
/// In both cases, the placeholder syntax acts as a variable expression representing the bound value:
///
/// ```rust,no_run
/// # async fn example2() -> sqlx::Result<()> {
/// # let mut conn: sqlx::PgConnection = unimplemented!();
/// let user_input = "Alice's Apples";
///
/// // Postgres and SQLite
/// let results = sqlx::query("SELECT * FROM articles WHERE content LIKE '%' || $1 || '%'")
/// .bind(user_input)
/// .fetch_all(&mut conn)
/// .await?;
///
/// // MySQL and MariaDB
/// let results = sqlx::query("SELECT * FROM articles WHERE content LIKE CONCAT('%', ?, '%')")
/// .bind(user_input)
/// .fetch_all(&mut conn)
/// .await?;
/// ```

Check failure on line 560 in sqlx-core/src/query.rs

View workflow job for this annotation

GitHub Actions / Unit Test (async-std, native-tls)

this file contains an unclosed delimiter

Check failure on line 560 in sqlx-core/src/query.rs

View workflow job for this annotation

GitHub Actions / Unit Test (async-std, none)

this file contains an unclosed delimiter
/// ##### The bound value to a query parameter is entirely separate from the query and does not affect its syntax.
/// Thus, SQL injection is impossible (barring shenanigans like calling a SQL function that lets you execute a string
/// as a statement) and *all* strings are valid.
///
/// This also means you cannot use query parameters to add conditional SQL fragments.
///
/// **SQLx does not substitute placeholders on the client side**. It is done by the database server itself.
///
/// ##### SQLx supports many different types for bind parameters, not just strings.
/// Any type that implements [`Encode<DB>`][Encode] and [`Type<DB>`] can be bound as a parameter.
///
/// See [the `types` module][crate::types] (links to `sqlx_core::types` but you should use `sqlx::types`) for details.
///
/// As an additional benefit, query parameters are usually sent in a compact binary encoding instead of a human-readable
/// text encoding, which saves bandwidth.
pub fn query<DB>(sql: &str) -> Query<'_, DB, <DB as HasArguments<'_>>::Arguments>
where
DB: Database,
Expand All @@ -439,7 +585,9 @@ where
}
}

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

0 comments on commit c6ab5a0

Please sign in to comment.