(
self,
mut g: G,
- ) -> Map<'q, DB, impl FnMut(DB::Row) -> Result + Send, A>
+ ) -> Map<'q, 'a, DB, impl FnMut(DB::Row) -> Result
+ Send>
where
G: FnMut(O) -> P + Send,
P: Unpin,
@@ -356,7 +309,7 @@ where
pub fn try_map(
self,
mut g: G,
- ) -> Map<'q, DB, impl FnMut(DB::Row) -> Result + Send, A>
+ ) -> Map<'q, 'a, DB, impl FnMut(DB::Row) -> Result
+ Send>
where
G: FnMut(O) -> Result
+ Send,
P: Unpin,
@@ -497,9 +450,9 @@ where
}
/// Execute a single SQL query as a prepared statement (explicitly created).
-pub fn query_statement<'q, DB>(
+pub fn query_statement<'q, 'a, DB>(
statement: &'q DB::Statement<'q>,
-) -> Query<'q, DB, ::Arguments<'_>>
+) -> Query<'q, 'a, DB>
where
DB: Database,
{
@@ -512,17 +465,17 @@ where
}
/// Execute a single SQL query as a prepared statement (explicitly created), with the given arguments.
-pub fn query_statement_with<'q, DB, A>(
+pub fn query_statement_with<'q, 'a, DB, A>(
statement: &'q DB::Statement<'q>,
arguments: A,
-) -> Query<'q, DB, A>
+) -> Query<'q, 'a, DB>
where
DB: Database,
A: IntoArguments<'q, DB>,
{
Query {
database: PhantomData,
- arguments: Some(Ok(arguments)),
+ arguments: Some(Ok(arguments.into_arguments())),
statement: Either::Right(statement),
persistent: true,
}
@@ -652,14 +605,15 @@ where
///
/// 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(sql: &str) -> Query<'_, DB, ::Arguments<'_>>
+pub fn query<'a, DB, SQL>(sql: SQL) -> Query<'static, 'a, DB>
where
DB: Database,
+ SQL: SqlSafeStr,
{
Query {
database: PhantomData,
arguments: Some(Ok(Default::default())),
- statement: Either::Left(sql),
+ statement: Either::Left(sql.into_sql_str()),
persistent: true,
}
}
@@ -667,27 +621,27 @@ where
/// 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>
+pub fn query_with<'a, DB, SQL, A>(sql: SQL, arguments: A) -> Query<'static, 'a, DB>
where
DB: Database,
- A: IntoArguments<'q, DB>,
+ A: IntoArguments<'a, DB>,
{
query_with_result(sql, Ok(arguments))
}
/// Same as [`query_with`] but is initialized with a Result of arguments instead
-pub fn query_with_result<'q, DB, A>(
- sql: &'q str,
- arguments: Result,
-) -> Query<'q, DB, A>
+pub fn query_with_result<'a, DB, SQL>(
+ sql: SQL,
+ arguments: Result, BoxDynError>,
+) -> Query<'static, 'a, DB>
where
DB: Database,
- A: IntoArguments<'q, DB>,
+ SQL: SqlSafeStr,
{
Query {
database: PhantomData,
arguments: Some(arguments),
- statement: Either::Left(sql),
+ statement: Either::Left(sql.into_sql_str()),
persistent: true,
}
}
diff --git a/sqlx-core/src/query_as.rs b/sqlx-core/src/query_as.rs
index fbc7fab55b..a2984c3f27 100644
--- a/sqlx-core/src/query_as.rs
+++ b/sqlx-core/src/query_as.rs
@@ -11,6 +11,7 @@ use crate::error::{BoxDynError, Error};
use crate::executor::{Execute, Executor};
use crate::from_row::FromRow;
use crate::query::{query, query_statement, query_statement_with, query_with_result, Query};
+use crate::sql_str::SqlSafeStr;
use crate::types::Type;
/// A single SQL query as a prepared statement, mapping results using [`FromRow`].
@@ -339,7 +340,7 @@ where
///
/// ```
#[inline]
-pub fn query_as<'q, DB, O>(sql: &'q str) -> QueryAs<'q, DB, O, ::Arguments<'q>>
+pub fn query_as<'q, DB, SQL, O>(sql: SQL) -> QueryAs<'q, DB, O, ::Arguments<'q>>
where
DB: Database,
O: for<'r> FromRow<'r, DB::Row>,
@@ -357,9 +358,10 @@ where
///
/// For details about type mapping from [`FromRow`], see [`query_as()`].
#[inline]
-pub fn query_as_with<'q, DB, O, A>(sql: &'q str, arguments: A) -> QueryAs<'q, DB, O, A>
+pub fn query_as_with<'q, DB, SQL, O, A>(sql: SQL, arguments: A) -> QueryAs<'q, DB, O, A>
where
DB: Database,
+ SQL: SqlSafeStr<'q>,
A: IntoArguments<'q, DB>,
O: for<'r> FromRow<'r, DB::Row>,
{
diff --git a/sqlx-core/src/query_builder.rs b/sqlx-core/src/query_builder.rs
index b071ff8a47..28f06c3787 100644
--- a/sqlx-core/src/query_builder.rs
+++ b/sqlx-core/src/query_builder.rs
@@ -3,7 +3,7 @@
use std::fmt::Display;
use std::fmt::Write;
use std::marker::PhantomData;
-
+use std::sync::Arc;
use crate::arguments::{Arguments, IntoArguments};
use crate::database::Database;
use crate::encode::Encode;
@@ -13,6 +13,7 @@ use crate::query_as::QueryAs;
use crate::query_scalar::QueryScalar;
use crate::types::Type;
use crate::Either;
+use crate::sql_str::AssertSqlSafe;
/// A builder type for constructing queries at runtime.
///
@@ -25,7 +26,9 @@ pub struct QueryBuilder<'args, DB>
where
DB: Database,
{
- query: String,
+ // Using `Arc` allows us to share the query string allocation with the database driver.
+ // It's only copied if the driver retains ownership after execution.
+ query: Arc,
init_len: usize,
arguments: Option<::Arguments<'args>>,
}
@@ -85,6 +88,16 @@ where
"QueryBuilder must be reset before reuse after `.build()`"
);
}
+
+ fn query_mut(&mut self) -> &mut String {
+ assert!(
+ self.arguments.is_some(),
+ "QueryBuilder must be reset before reuse after `.build()`"
+ );
+
+ Arc::get_mut(&mut self.query)
+ .expect("BUG: query must not be shared at this point in time")
+ }
/// Append a SQL fragment to the query.
///
@@ -116,7 +129,7 @@ where
pub fn push(&mut self, sql: impl Display) -> &mut Self {
self.sanity_check();
- write!(self.query, "{sql}").expect("error formatting `sql`");
+ write!(self.query_mut(), "{sql}").expect("error formatting `sql`");
self
}
@@ -158,7 +171,7 @@ where
arguments.add(value).expect("Failed to add argument");
arguments
- .format_placeholder(&mut self.query)
+ .format_placeholder(self.query_mut())
.expect("error in format_placeholder");
self
@@ -448,12 +461,10 @@ where
pub fn build(&mut self) -> Query<'_, DB, ::Arguments<'args>> {
self.sanity_check();
- Query {
- statement: Either::Left(&self.query),
- arguments: self.arguments.take().map(Ok),
- database: PhantomData,
- persistent: true,
- }
+ crate::query::query_with(
+ AssertSqlSafe(&self.query),
+ self.arguments.take().expect("BUG: just ran sanity_check")
+ )
}
/// Produce an executable query from this builder.
diff --git a/sqlx-core/src/query_scalar.rs b/sqlx-core/src/query_scalar.rs
index c131adcca3..c097b9b18b 100644
--- a/sqlx-core/src/query_scalar.rs
+++ b/sqlx-core/src/query_scalar.rs
@@ -4,6 +4,7 @@ use futures_util::{StreamExt, TryFutureExt, TryStreamExt};
use crate::arguments::IntoArguments;
use crate::database::{Database, HasStatementCache};
+use crate::decode::Decode;
use crate::encode::Encode;
use crate::error::{BoxDynError, Error};
use crate::executor::{Execute, Executor};
@@ -11,6 +12,7 @@ use crate::from_row::FromRow;
use crate::query_as::{
query_as, query_as_with_result, query_statement_as, query_statement_as_with, QueryAs,
};
+use crate::sql_str::SqlSafeStr;
use crate::types::Type;
/// A single SQL query as a prepared statement which extracts only the first column of each row.
@@ -318,12 +320,13 @@ where
/// # }
/// ```
#[inline]
-pub fn query_scalar<'q, DB, O>(
- sql: &'q str,
+pub fn query_scalar<'q, DB, SQL, O>(
+ sql: SQL,
) -> QueryScalar<'q, DB, O, ::Arguments<'q>>
where
DB: Database,
- (O,): for<'r> FromRow<'r, DB::Row>,
+ SQL: SqlSafeStr<'q>,
+ O: Type + for<'r> Decode<'r, DB>,
{
QueryScalar {
inner: query_as(sql),
@@ -337,11 +340,12 @@ where
///
/// For details about prepared statements and allowed SQL syntax, see [`query()`][crate::query::query].
#[inline]
-pub fn query_scalar_with<'q, DB, O, A>(sql: &'q str, arguments: A) -> QueryScalar<'q, DB, O, A>
+pub fn query_scalar_with<'q, DB, SQL, O, A>(sql: SQL, arguments: A) -> QueryScalar<'q, DB, O, A>
where
DB: Database,
+ SQL: SqlSafeStr<'q>,
A: IntoArguments<'q, DB>,
- (O,): for<'r> FromRow<'r, DB::Row>,
+ O: Type + for<'r> Decode<'r, DB>,
{
query_scalar_with_result(sql, Ok(arguments))
}
@@ -368,7 +372,7 @@ pub fn query_statement_scalar<'q, DB, O>(
) -> QueryScalar<'q, DB, O, ::Arguments<'_>>
where
DB: Database,
- (O,): for<'r> FromRow<'r, DB::Row>,
+ O: Type + for<'r> Decode<'r, DB>,
{
QueryScalar {
inner: query_statement_as(statement),
@@ -383,7 +387,7 @@ pub fn query_statement_scalar_with<'q, DB, O, A>(
where
DB: Database,
A: IntoArguments<'q, DB>,
- (O,): for<'r> FromRow<'r, DB::Row>,
+ O: Type + for<'r> Decode<'r, DB>,
{
QueryScalar {
inner: query_statement_as_with(statement, arguments),
diff --git a/sqlx-core/src/sql_str.rs b/sqlx-core/src/sql_str.rs
new file mode 100644
index 0000000000..58ed23d6d3
--- /dev/null
+++ b/sqlx-core/src/sql_str.rs
@@ -0,0 +1,182 @@
+use std::borrow::Borrow;
+use std::hash::{Hash, Hasher};
+use std::sync::Arc;
+
+/// A SQL string that is safe to execute on a database connection.
+///
+/// A "safe" SQL string is one that is unlikely to contain a [SQL injection vulnerability][injection].
+///
+/// In practice, this means a string type that is unlikely to contain dynamic data or user input.
+///
+/// `&'static str` is the only string type that satisfies the requirements of this trait
+/// (ignoring [`String::leak()`] which has niche use-cases) and so is the only string type that
+/// natively implements this trait by default.
+///
+/// For other string types, use [`AssertSqlSafe`] to assert this property.
+/// This is the only intended way to pass an owned `String` to [`query()`] and its related functions
+/// as well as [`raw_sql()`].
+///
+/// The maintainers of SQLx take no responsibility for any data leaks or loss resulting from misuse
+/// of this API.
+///
+/// ### Motivation
+/// This is designed to act as a speed bump against naively using `format!()` to add dynamic data
+/// or user input to a query, which is a classic vector for SQL injection as SQLx does not
+/// provide any sort of escaping or sanitization (which would have to be specially implemented
+/// for each database flavor/locale).
+///
+/// The recommended way to incorporate dynamic data or user input in a query is to use
+/// bind parameters, which requires the query to execute as a prepared statement.
+/// See [`query()`] for details.
+///
+/// This trait and [`AssertSqlSafe`] are intentionally analogous to
+/// [`std::panic::UnwindSafe`] and [`std::panic::AssertUnwindSafe`], respectively.
+///
+/// [injection]: https://en.wikipedia.org/wiki/SQL_injection
+/// [`query()`]: crate::query::query
+/// [`raw_sql()`]: crate::raw_sql::raw_sql
+pub trait SqlSafeStr {
+ /// Convert `self` to a [`SqlStr`].
+ fn into_sql_str(self) -> SqlStr;
+}
+
+impl SqlSafeStr for &'static str {
+ #[inline]
+
+ fn into_sql_str(self) -> SqlStr {
+ SqlStr(Repr::Static(self))
+ }
+}
+
+/// Assert that a query string is safe to execute on a database connection.
+///
+/// Using this API means that **you** have made sure that the string contents do not contain a
+/// [SQL injection vulnerability][injection]. It means that, if the string was constructed
+/// dynamically, and/or from user input, you have taken care to sanitize the input yourself.
+/// SQLx does not provide any sort of sanitization; the design of SQLx prefers the use
+/// of prepared statements for dynamic input.
+///
+/// The maintainers of SQLx take no responsibility for any data leaks or loss resulting from misuse
+/// of this API. **Use at your own risk.**
+///
+/// Note that `&'static str` implements [`SqlSafeStr`] directly and so does not need to be wrapped
+/// with this type.
+///
+/// [injection]: https://en.wikipedia.org/wiki/SQL_injection
+pub struct AssertSqlSafe(pub T);
+
+/// Note: copies the string.
+///
+/// It is recommended to pass one of the supported owned string types instead.
+impl<'a> SqlSafeStr for AssertSqlSafe<&'a str> {
+ #[inline]
+ fn into_sql_str(self) -> SqlStr {
+ SqlStr(Repr::Arced(self.0.into()))
+ }
+}
+impl SqlSafeStr for AssertSqlSafe {
+ #[inline]
+ fn into_sql_str(self) -> SqlStr {
+ SqlStr(Repr::Owned(self.0))
+ }
+}
+
+impl SqlSafeStr for AssertSqlSafe> {
+ #[inline]
+ fn into_sql_str(self) -> SqlStr {
+ SqlStr(Repr::Boxed(self.0))
+ }
+}
+
+// Note: this is not implemented for `Rc` because it would make `QueryString: !Send`.
+impl SqlSafeStr for AssertSqlSafe> {
+ #[inline]
+ fn into_sql_str(self) -> SqlStr {
+ SqlStr(Repr::Arced(self.into()))
+ }
+}
+
+/// A SQL string that is ready to execute on a database connection.
+///
+/// This is essentially `Cow<'static, str>` but which can be constructed from additional types
+/// without copying.
+///
+/// See [`SqlSafeStr`] for details.
+#[derive(Debug)]
+pub struct SqlStr(Repr);
+
+#[derive(Debug)]
+enum Repr {
+ /// We need a variant to memoize when we already have a static string, so we don't copy it.
+ Static(&'static str),
+ /// Thanks to the new niche in `String`, this doesn't increase the size beyond 3 words.
+ /// We essentially get all these variants for free.
+ Owned(String),
+ Boxed(Box),
+ Arced(Arc),
+ /// Allows for dynamic shared ownership with `query_builder`.
+ ArcString(Arc),
+}
+
+impl Clone for SqlStr {
+ fn clone(&self) -> Self {
+ Self(match &self.0 {
+ Repr::Static(s) => Repr::Static(s),
+ Repr::Arced(s) => Repr::Arced(s.clone()),
+ _ => Repr::Arced(self.as_str().into()),
+ })
+ }
+}
+
+impl SqlSafeStr for SqlStr {
+ #[inline]
+ fn into_sql_str(self) -> SqlStr {
+ self
+ }
+}
+
+impl SqlStr {
+ pub(crate) fn from_arc_string(arc: Arc) -> Self {
+ SqlStr(Repr::ArcString(arc))
+ }
+
+ /// Borrow the inner query string.
+ #[inline]
+ pub fn as_str(&self) -> &str {
+ match &self.0 {
+ Repr::Static(s) => s,
+ Repr::Owned(s) => s,
+ Repr::Boxed(s) => s,
+ Repr::Arced(s) => s,
+ Repr::ArcString(s) => s,
+ }
+ }
+}
+
+impl AsRef for SqlStr {
+ #[inline]
+ fn as_ref(&self) -> &str {
+ self.as_str()
+ }
+}
+
+impl Borrow for SqlStr {
+ #[inline]
+ fn borrow(&self) -> &str {
+ self.as_str()
+ }
+}
+
+impl PartialEq for SqlStr where T: AsRef {
+ fn eq(&self, other: &T) -> bool {
+ self.as_str() == other.as_ref()
+ }
+}
+
+impl Eq for SqlStr {}
+
+impl Hash for SqlStr {
+ fn hash(&self, state: &mut H) {
+ self.as_str().hash(state)
+ }
+}