blob: 4ecdf8fabe8eff20972380b28a4e0f082b18f9c6 [file] [log] [blame]
use std::{fmt, mem};
use crate::backend::Backend;
use crate::query_builder::{BindCollector, QueryBuilder};
use crate::result::QueryResult;
use crate::serialize::ToSql;
use crate::sql_types::HasSqlType;
#[allow(missing_debug_implementations)]
/// The primary type used when walking a Diesel AST during query execution.
///
/// Executing a query is generally done in multiple passes. This list includes,
/// but is not limited to:
///
/// - Generating the SQL
/// - Collecting and serializing bound values (sent separately from the SQL)
/// - Determining if a query is safe to store in the prepared statement cache
///
/// When adding a new type that is used in a Diesel AST, you don't need to care
/// about which specific passes are being performed, nor is there any way for
/// you to find out what the current pass is. You should simply call the
/// relevant methods and trust that they will be a no-op if they're not relevant
/// to the current pass.
pub struct AstPass<'a, DB>
where
DB: Backend,
DB::QueryBuilder: 'a,
DB::BindCollector: 'a,
DB::MetadataLookup: 'a,
{
internals: AstPassInternals<'a, DB>,
}
impl<'a, DB> AstPass<'a, DB>
where
DB: Backend,
{
#[doc(hidden)]
#[allow(clippy::wrong_self_convention)]
pub fn to_sql(query_builder: &'a mut DB::QueryBuilder) -> Self {
AstPass {
internals: AstPassInternals::ToSql(query_builder),
}
}
#[doc(hidden)]
pub fn collect_binds(
collector: &'a mut DB::BindCollector,
metadata_lookup: &'a DB::MetadataLookup,
) -> Self {
AstPass {
internals: AstPassInternals::CollectBinds {
collector,
metadata_lookup,
},
}
}
#[doc(hidden)]
pub fn is_safe_to_cache_prepared(result: &'a mut bool) -> Self {
AstPass {
internals: AstPassInternals::IsSafeToCachePrepared(result),
}
}
#[doc(hidden)]
pub fn debug_binds(formatter: &'a mut fmt::DebugList<'a, 'a>) -> Self {
AstPass {
internals: AstPassInternals::DebugBinds(formatter),
}
}
/// Does running this AST pass have any effect?
///
/// The result will be set to `false` if any method that generates SQL
/// is called.
pub(crate) fn is_noop(result: &'a mut bool) -> Self {
AstPass {
internals: AstPassInternals::IsNoop(result),
}
}
/// Call this method whenever you pass an instance of `AstPass` by value.
///
/// Effectively copies `self`, with a narrower lifetime. When passing a
/// reference or a mutable reference, this is normally done by rust
/// implicitly. This is why you can pass `&mut Foo` to multiple functions,
/// even though mutable references are not `Copy`. However, this is only
/// done implicitly for references. For structs with lifetimes it must be
/// done explicitly. This method matches the semantics of what Rust would do
/// implicitly if you were passing a mutable reference
// Clippy is wrong, this cannot be expressed with pointer casting
#[allow(clippy::transmute_ptr_to_ptr)]
pub fn reborrow(&mut self) -> AstPass<DB> {
use self::AstPassInternals::*;
let internals = match self.internals {
ToSql(ref mut builder) => ToSql(&mut **builder),
CollectBinds {
ref mut collector,
metadata_lookup,
} => CollectBinds {
collector: &mut **collector,
metadata_lookup: &*metadata_lookup,
},
IsSafeToCachePrepared(ref mut result) => IsSafeToCachePrepared(&mut **result),
DebugBinds(ref mut f) => {
// Safe because the lifetime is always being shortened.
let f_with_shorter_lifetime = unsafe { mem::transmute(&mut **f) };
DebugBinds(f_with_shorter_lifetime)
}
IsNoop(ref mut result) => IsNoop(&mut **result),
};
AstPass { internals }
}
/// Mark the current query being constructed as unsafe to store in the
/// prepared statement cache.
///
/// Diesel caches prepared statements as much as possible. However, it is
/// important to ensure that this doesn't result in unbounded memory usage
/// on the database server. To ensure this is the case, any logical query
/// which could generate a potentially unbounded number of prepared
/// statements *must* call this method. Examples of AST nodes which do this
/// are:
///
/// - `SqlLiteral`. We have no way of knowing if the SQL string was
/// constructed dynamically or not, so we must assume it was dynamic.
/// - `EqAny` when passed a Rust `Vec`. The `IN` operator requires one bind
/// parameter per element, meaning that the query could generate up to
/// `usize` unique prepared statements.
/// - `InsertStatement`. Unbounded due to the variable number of records
/// being inserted generating unique SQL.
/// - `UpdateStatement`. The number of potential queries is actually
/// technically bounded, but the upper bound is the number of columns on
/// the table factorial which is too large to be safe.
pub fn unsafe_to_cache_prepared(&mut self) {
if let AstPassInternals::IsSafeToCachePrepared(ref mut result) = self.internals {
**result = false
}
}
/// Push the given SQL string on the end of the query being constructed.
///
/// # Example
///
/// ```rust
/// # extern crate diesel;
/// # use diesel::query_builder::{QueryFragment, AstPass};
/// # use diesel::backend::Backend;
/// # use diesel::QueryResult;
/// # struct And<Left, Right> { left: Left, right: Right }
/// impl<Left, Right, DB> QueryFragment<DB> for And<Left, Right>
/// where
/// DB: Backend,
/// Left: QueryFragment<DB>,
/// Right: QueryFragment<DB>,
/// {
/// fn walk_ast(&self, mut out: AstPass<DB>) -> QueryResult<()> {
/// self.left.walk_ast(out.reborrow())?;
/// out.push_sql(" AND ");
/// self.right.walk_ast(out.reborrow())?;
/// Ok(())
/// }
/// }
/// # fn main() {}
/// ```
pub fn push_sql(&mut self, sql: &str) {
match self.internals {
AstPassInternals::ToSql(ref mut builder) => builder.push_sql(sql),
AstPassInternals::IsNoop(ref mut result) => **result = false,
_ => {}
}
}
/// Push the given SQL identifier on the end of the query being constructed.
///
/// The identifier will be quoted using the rules specific to the backend
/// the query is being constructed for.
pub fn push_identifier(&mut self, identifier: &str) -> QueryResult<()> {
match self.internals {
AstPassInternals::ToSql(ref mut builder) => builder.push_identifier(identifier)?,
AstPassInternals::IsNoop(ref mut result) => **result = false,
_ => {}
}
Ok(())
}
/// Push a value onto the given query to be sent separate from the SQL
///
/// This method affects multiple AST passes. It should be called at the
/// point in the query where you'd want the parameter placeholder (`$1` on
/// PG, `?` on other backends) to be inserted.
pub fn push_bind_param<T, U>(&mut self, bind: &U) -> QueryResult<()>
where
DB: HasSqlType<T>,
U: ToSql<T, DB>,
{
use self::AstPassInternals::*;
match self.internals {
ToSql(ref mut out) => out.push_bind_param(),
CollectBinds {
ref mut collector,
metadata_lookup,
} => collector.push_bound_value(bind, metadata_lookup)?,
DebugBinds(ref mut f) => {
f.entry(bind);
}
IsNoop(ref mut result) => **result = false,
_ => {}
}
Ok(())
}
#[doc(hidden)]
pub fn push_bind_param_value_only<T, U>(&mut self, bind: &U) -> QueryResult<()>
where
DB: HasSqlType<T>,
U: ToSql<T, DB>,
{
use self::AstPassInternals::*;
match self.internals {
CollectBinds { .. } | DebugBinds(..) => self.push_bind_param(bind)?,
_ => {}
}
Ok(())
}
}
#[allow(missing_debug_implementations)]
/// This is separate from the struct to cause the enum to be opaque, forcing
/// usage of the methods provided rather than matching on the enum directly.
/// This essentially mimics the capabilities that would be available if
/// `AstPass` were a trait.
enum AstPassInternals<'a, DB>
where
DB: Backend,
DB::QueryBuilder: 'a,
DB::BindCollector: 'a,
DB::MetadataLookup: 'a,
{
ToSql(&'a mut DB::QueryBuilder),
CollectBinds {
collector: &'a mut DB::BindCollector,
metadata_lookup: &'a DB::MetadataLookup,
},
IsSafeToCachePrepared(&'a mut bool),
DebugBinds(&'a mut fmt::DebugList<'a, 'a>),
IsNoop(&'a mut bool),
}