refactor: introduce SqlSafeStr API

This commit is contained in:
Austin Bonander 2024-04-23 03:49:18 -07:00
parent eaad7b2c9a
commit 230cc55302
7 changed files with 284 additions and 203 deletions

View file

@ -182,76 +182,3 @@ pub trait Executor<'c>: Send + Debug + Sized {
where
'c: 'e;
}
/// A type that may be executed against a database connection.
///
/// Implemented for the following:
///
/// * [`&str`](std::str)
/// * [`Query`](super::query::Query)
///
pub trait Execute<'q, DB: Database>: Send + Sized {
/// Gets the SQL that will be executed.
fn sql(&self) -> &'q str;
/// Gets the previously cached statement, if available.
fn statement(&self) -> Option<&DB::Statement<'q>>;
/// Returns the arguments to be bound against the query string.
///
/// Returning `Ok(None)` for `Arguments` indicates to use a "simple" query protocol and to not
/// prepare the query. Returning `Ok(Some(Default::default()))` is an empty arguments object that
/// will be prepared (and cached) before execution.
///
/// Returns `Err` if encoding any of the arguments failed.
fn take_arguments(&mut self) -> Result<Option<<DB as Database>::Arguments<'q>>, BoxDynError>;
/// Returns `true` if the statement should be cached.
fn persistent(&self) -> bool;
}
// NOTE: `Execute` is explicitly not implemented for String and &String to make it slightly more
// involved to write `conn.execute(format!("SELECT {val}"))`
impl<'q, DB: Database> Execute<'q, DB> for &'q str {
#[inline]
fn sql(&self) -> &'q str {
self
}
#[inline]
fn statement(&self) -> Option<&DB::Statement<'q>> {
None
}
#[inline]
fn take_arguments(&mut self) -> Result<Option<<DB as Database>::Arguments<'q>>, BoxDynError> {
Ok(None)
}
#[inline]
fn persistent(&self) -> bool {
true
}
}
impl<'q, DB: Database> Execute<'q, DB> for (&'q str, Option<<DB as Database>::Arguments<'q>>) {
#[inline]
fn sql(&self) -> &'q str {
self.0
}
#[inline]
fn statement(&self) -> Option<&DB::Statement<'q>> {
None
}
#[inline]
fn take_arguments(&mut self) -> Result<Option<<DB as Database>::Arguments<'q>>, BoxDynError> {
Ok(self.1.take())
}
#[inline]
fn persistent(&self) -> bool {
true
}
}

View file

@ -74,6 +74,7 @@ pub mod net;
pub mod query_as;
pub mod query_builder;
pub mod query_scalar;
pub mod sql_str;
pub mod raw_sql;
pub mod row;

View file

@ -8,15 +8,16 @@ use crate::arguments::{Arguments, IntoArguments};
use crate::database::{Database, HasStatementCache};
use crate::encode::Encode;
use crate::error::{BoxDynError, Error};
use crate::executor::{Execute, Executor};
use crate::executor::{Executor};
use crate::sql_str::{SqlSafeStr, SqlStr};
use crate::statement::Statement;
use crate::types::Type;
/// 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::Statement<'q>>,
pub(crate) arguments: Option<Result<A, BoxDynError>>,
pub struct Query<'q, 'a, DB: Database> {
pub(crate) statement: Either<SqlStr, &'q DB::Statement<'q>>,
pub(crate) arguments: Option<Result<DB::Arguments<'a>, BoxDynError>>,
pub(crate) database: PhantomData<DB>,
pub(crate) persistent: bool,
}
@ -33,46 +34,32 @@ pub struct Query<'q, DB: Database, A> {
/// before `.try_map()`. This is also to prevent adding superfluous binds to the result of
/// `query!()` et al.
#[must_use = "query must be executed to affect database"]
pub struct Map<'q, DB: Database, F, A> {
inner: Query<'q, DB, A>,
pub struct Map<'q, 'a, DB: Database, F> {
inner: Query<'q, 'a, DB>,
mapper: F,
}
impl<'q, DB, A> Execute<'q, DB> for Query<'q, DB, A>
impl<'q, 'a, DB> Query<'q, 'a, DB>
where
DB: Database,
A: Send + IntoArguments<'q, DB>,
DB: Database + HasStatementCache,
{
#[inline]
fn sql(&self) -> &'q str {
match self.statement {
Either::Right(statement) => statement.sql(),
Either::Left(sql) => sql,
}
}
fn statement(&self) -> Option<&DB::Statement<'q>> {
match self.statement {
Either::Right(statement) => Some(statement),
Either::Left(_) => None,
}
}
#[inline]
fn take_arguments(&mut self) -> Result<Option<<DB as Database>::Arguments<'q>>, BoxDynError> {
self.arguments
.take()
.transpose()
.map(|option| option.map(IntoArguments::into_arguments))
}
#[inline]
fn persistent(&self) -> bool {
self.persistent
/// If `true`, the statement will get prepared once and cached to the
/// connection's statement cache.
///
/// If queried once with the flag set to `true`, all subsequent queries
/// 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;
self
}
}
impl<'q, DB: Database> Query<'q, DB, <DB as Database>::Arguments<'q>> {
impl<'q, 'a, DB: Database> Query<'q, 'a, DB> {
/// Bind a value for use with this SQL query.
///
/// If the number of times this is called does not match the number of bind parameters that
@ -120,31 +107,10 @@ impl<'q, DB: Database> Query<'q, DB, <DB as Database>::Arguments<'q>> {
}
}
impl<'q, DB, A> Query<'q, DB, A>
where
DB: Database + HasStatementCache,
{
/// If `true`, the statement will get prepared once and cached to the
/// connection's statement cache.
///
/// If queried once with the flag set to `true`, all subsequent queries
/// 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;
self
}
}
impl<'q, DB, A: Send> Query<'q, DB, A>
where
DB: Database,
A: 'q + IntoArguments<'q, DB>,
{
impl<'q, 'a, DB> Query<'q, 'a, DB>
where
DB: Database,
{
/// Map each row in the result to another type.
///
/// See [`try_map`](Query::try_map) for a fallible version of this method.
@ -155,7 +121,7 @@ where
pub fn map<F, O>(
self,
mut f: F,
) -> Map<'q, DB, impl FnMut(DB::Row) -> Result<O, Error> + Send, A>
) -> Map<'q, 'a, DB, impl FnMut(DB::Row) -> Result<O, Error> + Send>
where
F: FnMut(DB::Row) -> O + Send,
O: Unpin,
@ -168,7 +134,7 @@ where
/// The [`query_as`](super::query_as::query_as) method will construct a mapped query using
/// a [`FromRow`](super::from_row::FromRow) implementation.
#[inline]
pub fn try_map<F, O>(self, f: F) -> Map<'q, DB, F, A>
pub fn try_map<F, O>(self, f: F) -> Map<'q, 'a, DB, F>
where
F: FnMut(DB::Row) -> Result<O, Error> + Send,
O: Unpin,
@ -184,33 +150,17 @@ where
pub async fn execute<'e, 'c: 'e, E>(self, executor: E) -> Result<DB::QueryResult, Error>
where
'q: 'e,
A: 'e,
'a: 'e,
E: Executor<'c, Database = DB>,
{
executor.execute(self).await
}
/// 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 prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."]
pub async fn execute_many<'e, 'c: 'e, E>(
self,
executor: E,
) -> BoxStream<'e, Result<DB::QueryResult, Error>>
where
'q: 'e,
A: 'e,
E: Executor<'c, Database = DB>,
{
executor.execute_many(self)
}
/// Execute the query and return the generated results as a stream.
#[inline]
pub fn fetch<'e, 'c: 'e, E>(self, executor: E) -> BoxStream<'e, Result<DB::Row, Error>>
where
'q: 'e,
A: 'e,
E: Executor<'c, Database = DB>,
{
executor.fetch(self)
@ -229,7 +179,6 @@ where
) -> BoxStream<'e, Result<Either<DB::QueryResult, DB::Row>, Error>>
where
'q: 'e,
A: 'e,
E: Executor<'c, Database = DB>,
{
executor.fetch_many(self)
@ -246,7 +195,6 @@ where
pub async fn fetch_all<'e, 'c: 'e, E>(self, executor: E) -> Result<Vec<DB::Row>, Error>
where
'q: 'e,
A: 'e,
E: Executor<'c, Database = DB>,
{
executor.fetch_all(self).await
@ -268,7 +216,6 @@ where
pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> Result<DB::Row, Error>
where
'q: 'e,
A: 'e,
E: Executor<'c, Database = DB>,
{
executor.fetch_one(self).await
@ -290,45 +237,51 @@ where
pub async fn fetch_optional<'e, 'c: 'e, E>(self, executor: E) -> Result<Option<DB::Row>, Error>
where
'q: 'e,
A: 'e,
E: Executor<'c, Database = DB>,
{
executor.fetch_optional(self).await
}
}
impl<'q, DB, F: Send, A: Send> Execute<'q, DB> for Map<'q, DB, F, A>
#[doc(hidden)]
impl<'q, 'a, DB> Query<'q, 'a, DB>
where
DB: Database,
A: IntoArguments<'q, DB>,
{
#[inline]
fn sql(&self) -> &'q str {
self.inner.sql()
match &self.statement {
Either::Right(statement) => statement.sql(),
Either::Left(sql) => sql.as_str(),
}
}
#[inline]
fn statement(&self) -> Option<&DB::Statement<'q>> {
self.inner.statement()
match self.statement {
Either::Right(statement) => Some(statement),
Either::Left(_) => None,
}
}
#[inline]
fn take_arguments(&mut self) -> Result<Option<<DB as Database>::Arguments<'q>>, BoxDynError> {
self.inner.take_arguments()
self.arguments
.take()
.transpose()
.map(|option| option.map(IntoArguments::into_arguments))
}
#[inline]
fn persistent(&self) -> bool {
self.inner.arguments.is_some()
fn is_persistent(&self) -> bool {
self.persistent
}
}
impl<'q, DB, F, O, A> Map<'q, DB, F, A>
impl<'q, 'a, DB, F, O> Map<'q, 'a, DB, F>
where
DB: Database,
F: FnMut(DB::Row) -> Result<O, Error> + Send,
O: Send + Unpin,
A: 'q + Send + IntoArguments<'q, DB>,
{
/// Map each row in the result to another type.
///
@ -340,7 +293,7 @@ where
pub fn map<G, P>(
self,
mut g: G,
) -> Map<'q, DB, impl FnMut(DB::Row) -> Result<P, Error> + Send, A>
) -> Map<'q, 'a, DB, impl FnMut(DB::Row) -> Result<P, Error> + Send>
where
G: FnMut(O) -> P + Send,
P: Unpin,
@ -356,7 +309,7 @@ where
pub fn try_map<G, P>(
self,
mut g: G,
) -> Map<'q, DB, impl FnMut(DB::Row) -> Result<P, Error> + Send, A>
) -> Map<'q, 'a, DB, impl FnMut(DB::Row) -> Result<P, Error> + Send>
where
G: FnMut(O) -> Result<P, Error> + 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, <DB as Database>::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<DB>(sql: &str) -> Query<'_, DB, <DB as Database>::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<A, BoxDynError>,
) -> Query<'q, DB, A>
pub fn query_with_result<'a, DB, SQL>(
sql: SQL,
arguments: Result<DB::Arguments<'a>, 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,
}
}

View file

@ -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, <DB as Database>::Arguments<'q>>
pub fn query_as<'q, DB, SQL, O>(sql: SQL) -> QueryAs<'q, DB, O, <DB as Database>::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>,
{

View file

@ -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<String>,
init_len: usize,
arguments: Option<<DB as Database>::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, <DB as Database>::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.

View file

@ -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, <DB as Database>::Arguments<'q>>
where
DB: Database,
(O,): for<'r> FromRow<'r, DB::Row>,
SQL: SqlSafeStr<'q>,
O: Type<DB> + 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<DB> + 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, <DB as Database>::Arguments<'_>>
where
DB: Database,
(O,): for<'r> FromRow<'r, DB::Row>,
O: Type<DB> + 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<DB> + for<'r> Decode<'r, DB>,
{
QueryScalar {
inner: query_statement_as_with(statement, arguments),

182
sqlx-core/src/sql_str.rs Normal file
View file

@ -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<T>(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<String> {
#[inline]
fn into_sql_str(self) -> SqlStr {
SqlStr(Repr::Owned(self.0))
}
}
impl SqlSafeStr for AssertSqlSafe<Box<str>> {
#[inline]
fn into_sql_str(self) -> SqlStr {
SqlStr(Repr::Boxed(self.0))
}
}
// Note: this is not implemented for `Rc<str>` because it would make `QueryString: !Send`.
impl SqlSafeStr for AssertSqlSafe<Arc<str>> {
#[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<str>),
Arced(Arc<str>),
/// Allows for dynamic shared ownership with `query_builder`.
ArcString(Arc<String>),
}
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<String>) -> 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<str> for SqlStr {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Borrow<str> for SqlStr {
#[inline]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl<T> PartialEq<T> for SqlStr where T: AsRef<str> {
fn eq(&self, other: &T) -> bool {
self.as_str() == other.as_ref()
}
}
impl Eq for SqlStr {}
impl Hash for SqlStr {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}