1//! Helper types for prepared statement caching
2//!
3//! A primer on prepared statement caching in Diesel
4//! ------------------------------------------------
5//!
6//! Diesel uses prepared statements for virtually all queries. This is most
7//! visible in our lack of any sort of "quoting" API. Values must always be
8//! transmitted as bind parameters, we do not support direct interpolation. The
9//! only method in the public API that doesn't require the use of prepared
10//! statements is [`SimpleConnection::batch_execute`](super::SimpleConnection::batch_execute).
11//!
12//! In order to avoid the cost of re-parsing and planning subsequent queries,
13//! by default Diesel caches the prepared statement whenever possible. This
14//! can be customized by calling
15//! [`Connection::set_cache_size`](super::Connection::set_prepared_statement_cache_size).
16//!
17//! Queries will fall into one of three buckets:
18//!
19//! - Unsafe to cache
20//! - Cached by SQL
21//! - Cached by type
22//!
23//! A query is considered unsafe to cache if it represents a potentially
24//! unbounded number of queries. This is communicated to the connection through
25//! [`QueryFragment::is_safe_to_cache_prepared`]. While this is done as a full AST
26//! pass, after monomorphisation and inlining this will usually be optimized to
27//! a constant. Only boxed queries will need to do actual work to answer this
28//! question.
29//!
30//! The majority of AST nodes are safe to cache if their components are safe to
31//! cache. There are at least 4 cases where a query is unsafe to cache:
32//!
33//! - queries containing `IN` with bind parameters
34//! - This requires 1 bind parameter per value, and is therefore unbounded
35//! - `IN` with subselects are cached (assuming the subselect is safe to
36//! cache)
37//! - `IN` statements for postgresql are cached as they use `= ANY($1)` instead
38//! which does not cause an unbound number of binds
39//! - `INSERT` statements with a variable number of rows
40//! - The SQL varies based on the number of rows being inserted.
41//! - `UPDATE` statements
42//! - Technically it's bounded on "number of optional values being passed to
43//! `SET` factorial" but that's still quite high, and not worth caching
44//! for the same reason as single row inserts
45//! - `SqlLiteral` nodes
46//! - We have no way of knowing whether the SQL was generated dynamically or
47//! not, so we must assume that it's unbounded
48//!
49//! For queries which are unsafe to cache, the statement cache will never insert
50//! them. They will be prepared and immediately released after use (or in the
51//! case of PG they will use the unnamed prepared statement).
52//!
53//! For statements which are able to be cached, we then have to determine what
54//! to use as the cache key. The standard method that virtually all ORMs or
55//! database access layers use in the wild is to store the statements in a
56//! hash map, using the SQL as the key.
57//!
58//! However, the majority of queries using Diesel that are safe to cache as
59//! prepared statements will be uniquely identified by their type. For these
60//! queries, we can bypass the query builder entirely. Since our AST is
61//! generally optimized away by the compiler, for these queries the cost of
62//! fetching a prepared statement from the cache is the cost of [`HashMap<u32,
63//! _>::get`](std::collections::HashMap::get), where the key we're fetching by is a compile time constant. For
64//! these types, the AST pass to gather the bind parameters will also be
65//! optimized to accessing each parameter individually.
66//!
67//! Determining if a query can be cached by type is the responsibility of the
68//! [`QueryId`] trait. This trait is quite similar to `Any`, but with a few
69//! differences:
70//!
71//! - No `'static` bound
72//! - Something being a reference never changes the SQL that is generated,
73//! so `&T` has the same query id as `T`.
74//! - `Option<TypeId>` instead of `TypeId`
75//! - We need to be able to constrain on this trait being implemented, but
76//! not all types will actually have a static query id. Hopefully once
77//! specialization is stable we can remove the `QueryId` bound and
78//! specialize on it instead (or provide a blanket impl for all `T`)
79//! - Implementors give a more broad type than `Self`
80//! - This really only affects bind parameters. There are 6 different Rust
81//! types which can be used for a parameter of type `timestamp`. The same
82//! statement can be used regardless of the Rust type, so [`Bound<ST, T>`](crate::expression::bound::Bound)
83//! defines its [`QueryId`] as [`Bound<ST, ()>`](crate::expression::bound::Bound).
84//!
85//! A type returning `Some(id)` or `None` for its query ID is based on whether
86//! the SQL it generates can change without the type changing. At the moment,
87//! the only type which is safe to cache as a prepared statement but does not
88//! have a static query ID is something which has been boxed.
89//!
90//! One potential optimization that we don't perform is storing the queries
91//! which are cached by type ID in a separate map. Since a type ID is a u64,
92//! this would allow us to use a specialized map which knows that there will
93//! never be hashing collisions (also known as a perfect hashing function),
94//! which would mean lookups are always constant time. However, this would save
95//! nanoseconds on an operation that will take microseconds or even
96//! milliseconds.
9798use crate::util::std_compat::Entry;
99use alloc::borrow::Cow;
100use alloc::boxed::Box;
101use alloc::string::String;
102use alloc::vec::Vec;
103use core::any::TypeId;
104use core::hash::Hash;
105use core::ops::{Deref, DerefMut};
106107use strategy::{
108LookupStatementResult, StatementCacheStrategy, WithCacheStrategy, WithoutCacheStrategy,
109};
110111use crate::backend::Backend;
112use crate::connection::InstrumentationEvent;
113use crate::query_builder::*;
114use crate::result::QueryResult;
115116use super::{CacheSize, Instrumentation};
117118/// Various interfaces and implementations to control connection statement caching.
119#[allow(unreachable_pub)]
120pub mod strategy;
121122/// A prepared statement cache
123#[allow(missing_debug_implementations, unreachable_pub)]
124#[cfg_attr(
125 diesel_docsrs,
126 doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
127)]
128pub struct StatementCache<DB: Backend, Statement> {
129 cache: Box<dyn StatementCacheStrategy<DB, Statement>>,
130// increment every time a query is cached
131 // some backends might use it to create unique prepared statement names
132cache_counter: u64,
133}
134135/// A helper type that indicates if a certain query
136/// is cached inside of the prepared statement cache or not
137///
138/// This information can be used by the connection implementation
139/// to signal this fact to the database while actually
140/// preparing the statement
141#[derive(#[automatically_derived]
#[allow(unreachable_pub)]
impl ::core::fmt::Debug for PrepareForCache {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
PrepareForCache::Yes { counter: __self_0 } =>
::core::fmt::Formatter::debug_struct_field1_finish(f, "Yes",
"counter", &__self_0),
PrepareForCache::No => ::core::fmt::Formatter::write_str(f, "No"),
}
}
}Debug, #[automatically_derived]
#[allow(unreachable_pub)]
impl ::core::clone::Clone for PrepareForCache {
#[inline]
fn clone(&self) -> PrepareForCache {
let _: ::core::clone::AssertParamIsClone<u64>;
*self
}
}Clone, #[automatically_derived]
#[allow(unreachable_pub)]
impl ::core::marker::Copy for PrepareForCache { }Copy)]
142#[cfg_attr(
143 diesel_docsrs,
144 doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
145)]
146#[allow(unreachable_pub)]
147pub enum PrepareForCache {
148/// The statement will be cached
149Yes {
150/// Counter might be used as unique identifier for prepared statement.
151#[allow(dead_code)]
152counter: u64,
153 },
154/// The statement won't be cached
155No,
156}
157158#[allow(clippy::new_without_default, unreachable_pub)]
159impl<DB, Statement> StatementCache<DB, Statement>
160where
161DB: Backend + 'static,
162 Statement: Send + 'static,
163 DB::TypeMetadata: Send + Clone,
164 DB::QueryBuilder: Default,
165StatementCacheKey<DB>: Hash + Eq,
166{
167/// Create a new prepared statement cache using [`CacheSize::Unbounded`] as caching strategy.
168#[allow(unreachable_pub)]
169pub fn new() -> Self {
170StatementCache {
171 cache: Box::new(WithCacheStrategy::default()),
172 cache_counter: 0,
173 }
174 }
175176/// Set caching strategy from predefined implementations
177pub fn set_cache_size(&mut self, size: CacheSize) {
178if self.cache.cache_size() != size {
179self.cache = match size {
180 CacheSize::Unbounded => Box::new(WithCacheStrategy::default()),
181 CacheSize::Disabled => Box::new(WithoutCacheStrategy::default()),
182 }
183 }
184 }
185186/// Removes all cached statements so subsequent queries are re-prepared,
187 /// while keeping the configured caching strategy.
188// Currently used only by the SQLite authorizer, so it is compiled only for
189 // backends that provide that caller.
190#[cfg(feature = "__sqlite-shared")]
191pub(crate) fn clear(&mut self) {
192self.cache.clear();
193 }
194195/// Setting custom caching strategy. It is used in tests, to verify caching logic
196#[allow(dead_code)]
197pub(crate) fn set_strategy<Strategy>(&mut self, s: Strategy)
198where
199Strategy: StatementCacheStrategy<DB, Statement> + 'static,
200 {
201self.cache = Box::new(s);
202 }
203204/// Prepare a query as prepared statement
205 ///
206 /// This functions returns a prepared statement corresponding to the
207 /// query passed as `source` with the bind values passed as `bind_types`.
208 /// If the query is already cached inside this prepared statement cache
209 /// the cached prepared statement will be returned, otherwise `prepare_fn`
210 /// will be called to create a new prepared statement for this query source.
211 /// The first parameter of the callback contains the query string, the second
212 /// parameter indicates if the constructed prepared statement will be cached or not.
213 /// See the [module](self) documentation for details
214 /// about which statements are cached and which are not cached.
215//
216 // Notes:
217 // This function takes explicitly a connection and a function pointer (and no generic callback)
218 // as argument to ensure that we don't leak generic query types into the prepare function
219#[allow(unreachable_pub)]
220 #[cfg(any(
221 feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
222 feature = "__sqlite-shared",
223 feature = "mysql",
224 feature = "mariadb"
225))]
226pub fn cached_statement<'a, T, R, C>(
227&'a mut self,
228 source: &T,
229 backend: &DB,
230 bind_types: &[DB::TypeMetadata],
231 conn: C,
232 prepare_fn: fn(C, &str, PrepareForCache, &[DB::TypeMetadata]) -> R,
233 instrumentation: &mut dyn Instrumentation,
234 ) -> R::Return<'a>
235where
236T: QueryFragment<DB> + QueryId,
237 R: StatementCallbackReturnType<Statement, C> + 'a,
238 {
239self.cached_statement_non_generic(
240 T::query_id(),
241source,
242backend,
243bind_types,
244conn,
245prepare_fn,
246instrumentation,
247 )
248 }
249250/// Prepare a query as prepared statement
251 ///
252 /// This function closely mirrors `Self::cached_statement` but
253 /// eliminates the generic query type in favour of a trait object
254 ///
255 /// This can be easier to use in situations where you already turned
256 /// the query type into a concrete SQL string
257// Notes:
258 // This function takes explicitly a connection and a function pointer (and no generic callback)
259 // as argument to ensure that we don't leak generic query types into the prepare function
260#[allow(unreachable_pub)]
261 #[allow(clippy::too_many_arguments)] // we need all of them
262pub fn cached_statement_non_generic<'a, R, C>(
263&'a mut self,
264 maybe_type_id: Option<TypeId>,
265 source: &dyn QueryFragmentForCachedStatement<DB>,
266 backend: &DB,
267 bind_types: &[DB::TypeMetadata],
268 conn: C,
269 prepare_fn: fn(C, &str, PrepareForCache, &[DB::TypeMetadata]) -> R,
270 instrumentation: &mut dyn Instrumentation,
271 ) -> R::Return<'a>
272where
273R: StatementCallbackReturnType<Statement, C> + 'a,
274 {
275Self::cached_statement_non_generic_impl(
276self.cache.as_mut(),
277maybe_type_id,
278source,
279backend,
280bind_types,
281conn,
282 |conn, sql, is_cached| {
283if is_cached {
284instrumentation.on_connection_event(InstrumentationEvent::CacheQuery { sql });
285self.cache_counter += 1;
286prepare_fn(
287conn,
288sql,
289 PrepareForCache::Yes {
290 counter: self.cache_counter,
291 },
292bind_types,
293 )
294 } else {
295prepare_fn(conn, sql, PrepareForCache::No, bind_types)
296 }
297 },
298 )
299 }
300301/// Reduce the amount of monomorphized code by factoring this via dynamic dispatch
302 /// There will be only one instance of `R` for diesel (and a different single instance for diesel-async)
303 /// There will be only a instance per connection type `C` for each connection that
304 /// uses this prepared statement impl, this closely correlates to the types `DB` and `Statement`
305 /// for the overall statement cache impl
306fn cached_statement_non_generic_impl<'a, R, C>(
307 cache: &'a mut dyn StatementCacheStrategy<DB, Statement>,
308 maybe_type_id: Option<TypeId>,
309 source: &dyn QueryFragmentForCachedStatement<DB>,
310 backend: &DB,
311 bind_types: &[DB::TypeMetadata],
312 conn: C,
313 prepare_fn: impl FnOnce(C, &str, bool) -> R,
314 ) -> R::Return<'a>
315where
316R: StatementCallbackReturnType<Statement, C> + 'a,
317 {
318// this function cannot use the `?` operator
319 // as we want to abstract over returning `QueryResult<MaybeCached>` and
320 // `impl Future<Output = QueryResult<MaybeCached>>` here
321 // to share the prepared statement cache implementation between diesel and
322 // diesel_async
323 //
324 // For this reason we need to match explicitly on each error and call `R::from_error()`
325 // to construct the right error return variant
326let cache_key =
327match StatementCacheKey::for_source(maybe_type_id, source, bind_types, backend) {
328Ok(o) => o,
329Err(e) => return R::from_error(e),
330 };
331let is_safe_to_cache_prepared = match source.is_safe_to_cache_prepared(backend) {
332Ok(o) => o,
333Err(e) => return R::from_error(e),
334 };
335// early return if the statement cannot be cached
336if !is_safe_to_cache_prepared {
337let sql = match cache_key.sql(source, backend) {
338Ok(sql) => sql,
339Err(e) => return R::from_error(e),
340 };
341return prepare_fn(conn, &sql, false).map_to_no_cache();
342 }
343let entry = cache.lookup_statement(cache_key);
344match entry {
345// The statement is already cached
346LookupStatementResult::CacheEntry(Entry::Occupied(e)) => {
347 R::map_to_cache(e.into_mut(), conn)
348 }
349// The statement is not cached but there is capacity to cache it
350LookupStatementResult::CacheEntry(Entry::Vacant(e)) => {
351let sql = match e.key().sql(source, backend) {
352Ok(sql) => sql,
353Err(e) => return R::from_error(e),
354 };
355let st = prepare_fn(conn, &sql, true);
356st.register_cache(|stmt| e.insert(stmt))
357 }
358// The statement is not cached and there is no capacity to cache it
359LookupStatementResult::NoCache(cache_key) => {
360let sql = match cache_key.sql(source, backend) {
361Ok(sql) => sql,
362Err(e) => return R::from_error(e),
363 };
364prepare_fn(conn, &sql, false).map_to_no_cache()
365 }
366 }
367 }
368}
369370/// Implemented for all `QueryFragment`s, dedicated to dynamic dispatch within the context of
371/// `statement_cache`
372///
373/// We want the generated code to be as small as possible, so for each query passed to
374/// [`StatementCache::cached_statement`] the generated assembly will just call a non generic
375/// version with dynamic dispatch pointing to the VTABLE of this minimal trait
376///
377/// This preserves the opportunity for the compiler to entirely optimize the `construct_sql`
378/// function as a function that simply returns a constant `String`.
379#[allow(unreachable_pub)]
380#[cfg_attr(
381 diesel_docsrs,
382 doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
383)]
384pub trait QueryFragmentForCachedStatement<DB> {
385/// Convert the query fragment into a SQL string for the given backend
386fn construct_sql(&self, backend: &DB) -> QueryResult<String>;
387388/// Check whether it's safe to cache the query
389fn is_safe_to_cache_prepared(&self, backend: &DB) -> QueryResult<bool>;
390}
391392impl<T, DB> QueryFragmentForCachedStatement<DB> for T
393where
394DB: Backend,
395 DB::QueryBuilder: Default,
396 T: QueryFragment<DB>,
397{
398fn construct_sql(&self, backend: &DB) -> QueryResult<String> {
399let mut query_builder = DB::QueryBuilder::default();
400self.to_sql(&mut query_builder, backend)?;
401Ok(query_builder.finish())
402 }
403404fn is_safe_to_cache_prepared(&self, backend: &DB) -> QueryResult<bool> {
405 <T as QueryFragment<DB>>::is_safe_to_cache_prepared(self, backend)
406 }
407}
408409/// Wraps a possibly cached prepared statement
410///
411/// Essentially a customized version of [`Cow`]
412/// that does not depend on [`ToOwned`]
413#[allow(missing_debug_implementations, unreachable_pub)]
414#[cfg_attr(
415 diesel_docsrs,
416 doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
417)]
418#[non_exhaustive]
419pub enum MaybeCached<'a, T: 'a> {
420/// Contains a not cached prepared statement
421CannotCache(T),
422/// Contains a reference cached prepared statement
423Cached(&'a mut T),
424}
425426/// This trait abstracts over the type returned by the prepare statement function
427///
428/// The main use-case for this abstraction is to share the same statement cache implementation
429/// between diesel and diesel-async.
430#[cfg_attr(
431 diesel_docsrs,
432 doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
433)]
434#[allow(unreachable_pub)]
435pub trait StatementCallbackReturnType<S: 'static, C> {
436/// The return type of `StatementCache::cached_statement`
437 ///
438 /// Either a `QueryResult<MaybeCached<S>>` or a future of that result type
439type Return<'a>;
440441/// Create the return type from an error
442fn from_error<'a>(e: diesel::result::Error) -> Self::Return<'a>;
443444/// Map the callback return type to the `MaybeCached::CannotCache` variant
445fn map_to_no_cache<'a>(self) -> Self::Return<'a>
446where
447Self: 'a;
448449/// Map the cached statement to the `MaybeCached::Cached` variant
450fn map_to_cache(stmt: &mut S, conn: C) -> Self::Return<'_>;
451452/// Insert the created statement into the cache via the provided callback
453 /// and then turn the returned reference into `MaybeCached::Cached`
454fn register_cache<'a>(
455self,
456 callback: impl FnOnce(S) -> &'a mut S + Send + 'a,
457 ) -> Self::Return<'a>
458where
459Self: 'a;
460}
461462impl<S, C> StatementCallbackReturnType<S, C> for QueryResult<S>
463where
464S: 'static,
465{
466type Return<'a> = QueryResult<MaybeCached<'a, S>>;
467468fn from_error<'a>(e: diesel::result::Error) -> Self::Return<'a> {
469Err(e)
470 }
471472fn map_to_no_cache<'a>(self) -> Self::Return<'a> {
473self.map(MaybeCached::CannotCache)
474 }
475476fn map_to_cache(stmt: &mut S, _conn: C) -> Self::Return<'_> {
477Ok(MaybeCached::Cached(stmt))
478 }
479480fn register_cache<'a>(
481self,
482 callback: impl FnOnce(S) -> &'a mut S + Send + 'a,
483 ) -> Self::Return<'a>
484where
485Self: 'a,
486 {
487Ok(MaybeCached::Cached(callback(self?)))
488 }
489}
490491impl<T> Dereffor MaybeCached<'_, T> {
492type Target = T;
493494fn deref(&self) -> &Self::Target {
495match *self {
496 MaybeCached::CannotCache(ref x) => x,
497 MaybeCached::Cached(ref x) => x,
498 }
499 }
500}
501502impl<T> DerefMutfor MaybeCached<'_, T> {
503fn deref_mut(&mut self) -> &mut Self::Target {
504match *self {
505 MaybeCached::CannotCache(ref mut x) => x,
506 MaybeCached::Cached(ref mut x) => x,
507 }
508 }
509}
510511/// The lookup key used by [`StatementCache`] internally
512///
513/// This can contain either a at compile time known type id
514/// (representing a statically known query) or a at runtime
515/// calculated query string + parameter types (for queries
516/// that may change depending on their parameters)
517#[allow(missing_debug_implementations, unreachable_pub)]
518#[derive(#[automatically_derived]
#[allow(missing_debug_implementations, unreachable_pub)]
impl<DB: ::core::hash::Hash + Backend> ::core::hash::Hash for
StatementCacheKey<DB> where DB::TypeMetadata: ::core::hash::Hash {
#[inline]
fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
let __self_discr = ::core::intrinsics::discriminant_value(self);
::core::hash::Hash::hash(&__self_discr, state);
match self {
StatementCacheKey::Type(__self_0) =>
::core::hash::Hash::hash(__self_0, state),
StatementCacheKey::Sql { sql: __self_0, bind_types: __self_1 } =>
{
::core::hash::Hash::hash(__self_0, state);
::core::hash::Hash::hash(__self_1, state)
}
}
}
}Hash, #[automatically_derived]
#[allow(missing_debug_implementations, unreachable_pub)]
impl<DB: ::core::cmp::PartialEq + Backend> ::core::cmp::PartialEq for
StatementCacheKey<DB> where DB::TypeMetadata: ::core::cmp::PartialEq {
#[inline]
fn eq(&self, other: &StatementCacheKey<DB>) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr &&
match (self, other) {
(StatementCacheKey::Type(__self_0),
StatementCacheKey::Type(__arg1_0)) => __self_0 == __arg1_0,
(StatementCacheKey::Sql { sql: __self_0, bind_types: __self_1
}, StatementCacheKey::Sql {
sql: __arg1_0, bind_types: __arg1_1 }) =>
__self_0 == __arg1_0 && __self_1 == __arg1_1,
_ => unsafe { ::core::intrinsics::unreachable() }
}
}
}PartialEq, #[automatically_derived]
#[allow(missing_debug_implementations, unreachable_pub)]
impl<DB: ::core::cmp::Eq + Backend> ::core::cmp::Eq for StatementCacheKey<DB>
where DB::TypeMetadata: ::core::cmp::Eq {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<TypeId>;
let _: ::core::cmp::AssertParamIsEq<String>;
let _: ::core::cmp::AssertParamIsEq<Vec<DB::TypeMetadata>>;
}
}Eq)]
519#[cfg_attr(
520 diesel_docsrs,
521 doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
522)]
523pub enum StatementCacheKey<DB: Backend> {
524/// Represents a at compile time known query
525 ///
526 /// Calculated via [`QueryId::QueryId`]
527Type(TypeId),
528/// Represents a dynamically constructed query
529 ///
530 /// This variant is used if [`QueryId::HAS_STATIC_QUERY_ID`]
531 /// is `false` and [`AstPass::unsafe_to_cache_prepared`] is not
532 /// called for a given query.
533Sql {
534/// contains the sql query string
535sql: String,
536/// contains the types of any bind parameter passed to the query
537bind_types: Vec<DB::TypeMetadata>,
538 },
539}
540541impl<DB> StatementCacheKey<DB>
542where
543DB: Backend,
544 DB::QueryBuilder: Default,
545 DB::TypeMetadata: Clone,
546{
547/// Create a new statement cache key for the given query source
548// Note: Intentionally monomorphic over source.
549#[allow(unreachable_pub)]
550pub fn for_source(
551 maybe_type_id: Option<TypeId>,
552 source: &dyn QueryFragmentForCachedStatement<DB>,
553 bind_types: &[DB::TypeMetadata],
554 backend: &DB,
555 ) -> QueryResult<Self> {
556match maybe_type_id {
557Some(id) => Ok(StatementCacheKey::Type(id)),
558None => {
559let sql = source.construct_sql(backend)?;
560Ok(StatementCacheKey::Sql {
561sql,
562 bind_types: bind_types.into(),
563 })
564 }
565 }
566 }
567568/// Get the sql for a given query source based
569 ///
570 /// This is an optimization that may skip constructing the query string
571 /// twice if it's already part of the current cache key
572// Note: Intentionally monomorphic over source.
573#[allow(unreachable_pub)]
574pub fn sql(
575&self,
576 source: &dyn QueryFragmentForCachedStatement<DB>,
577 backend: &DB,
578 ) -> QueryResult<Cow<'_, str>> {
579match *self {
580 StatementCacheKey::Type(_) => source.construct_sql(backend).map(Cow::Owned),
581 StatementCacheKey::Sql { ref sql, .. } => Ok(Cow::Borrowed(sql)),
582 }
583 }
584}