use std::any::TypeId;
use std::borrow::Cow;
use std::collections::HashMap;
use std::hash::Hash;
use std::ops::{Deref, DerefMut};
use crate::backend::Backend;
use crate::connection::InstrumentationEvent;
use crate::query_builder::*;
use crate::result::QueryResult;
use super::Instrumentation;
#[allow(missing_debug_implementations, unreachable_pub)]
#[cfg_attr(
docsrs,
doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
)]
pub struct StatementCache<DB: Backend, Statement> {
pub(crate) cache: HashMap<StatementCacheKey<DB>, Statement>,
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(
docsrs,
doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
)]
#[allow(unreachable_pub)]
pub enum PrepareForCache {
Yes,
No,
}
#[allow(
clippy::len_without_is_empty,
clippy::new_without_default,
unreachable_pub
)]
impl<DB, Statement> StatementCache<DB, Statement>
where
DB: Backend,
DB::TypeMetadata: Clone,
DB::QueryBuilder: Default,
StatementCacheKey<DB>: Hash + Eq,
{
#[allow(unreachable_pub)]
pub fn new() -> Self {
StatementCache {
cache: HashMap::new(),
}
}
#[allow(unreachable_pub)]
#[cfg(any(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
feature = "postgres",
all(feature = "sqlite", test)
))]
#[cfg_attr(
docsrs,
doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
)]
pub fn len(&self) -> usize {
self.cache.len()
}
#[allow(unreachable_pub)]
pub fn cached_statement<T, F>(
&mut self,
source: &T,
backend: &DB,
bind_types: &[DB::TypeMetadata],
mut prepare_fn: F,
instrumentation: &mut dyn Instrumentation,
) -> QueryResult<MaybeCached<'_, Statement>>
where
T: QueryFragment<DB> + QueryId,
F: FnMut(&str, PrepareForCache) -> QueryResult<Statement>,
{
self.cached_statement_non_generic(
T::query_id(),
source,
backend,
bind_types,
&mut prepare_fn,
instrumentation,
)
}
fn cached_statement_non_generic(
&mut self,
maybe_type_id: Option<TypeId>,
source: &dyn QueryFragmentForCachedStatement<DB>,
backend: &DB,
bind_types: &[DB::TypeMetadata],
prepare_fn: &mut dyn FnMut(&str, PrepareForCache) -> QueryResult<Statement>,
instrumentation: &mut dyn Instrumentation,
) -> QueryResult<MaybeCached<'_, Statement>> {
use std::collections::hash_map::Entry::{Occupied, Vacant};
let cache_key = StatementCacheKey::for_source(maybe_type_id, source, bind_types, backend)?;
if !source.is_safe_to_cache_prepared(backend)? {
let sql = cache_key.sql(source, backend)?;
return prepare_fn(&sql, PrepareForCache::No).map(MaybeCached::CannotCache);
}
let cached_result = match self.cache.entry(cache_key) {
Occupied(entry) => entry.into_mut(),
Vacant(entry) => {
let statement = {
let sql = entry.key().sql(source, backend)?;
instrumentation
.on_connection_event(InstrumentationEvent::CacheQuery { sql: &sql });
prepare_fn(&sql, PrepareForCache::Yes)
};
entry.insert(statement?)
}
};
Ok(MaybeCached::Cached(cached_result))
}
}
#[allow(unreachable_pub)]
#[cfg_attr(
docsrs,
doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
)]
pub trait QueryFragmentForCachedStatement<DB> {
fn construct_sql(&self, backend: &DB) -> QueryResult<String>;
fn is_safe_to_cache_prepared(&self, backend: &DB) -> QueryResult<bool>;
}
impl<T, DB> QueryFragmentForCachedStatement<DB> for T
where
DB: Backend,
DB::QueryBuilder: Default,
T: QueryFragment<DB>,
{
fn construct_sql(&self, backend: &DB) -> QueryResult<String> {
let mut query_builder = DB::QueryBuilder::default();
self.to_sql(&mut query_builder, backend)?;
Ok(query_builder.finish())
}
fn is_safe_to_cache_prepared(&self, backend: &DB) -> QueryResult<bool> {
<T as QueryFragment<DB>>::is_safe_to_cache_prepared(self, backend)
}
}
#[allow(missing_debug_implementations, unreachable_pub)]
#[cfg_attr(
docsrs,
doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
)]
#[non_exhaustive]
pub enum MaybeCached<'a, T: 'a> {
CannotCache(T),
Cached(&'a mut T),
}
impl<'a, T> Deref for MaybeCached<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match *self {
MaybeCached::CannotCache(ref x) => x,
MaybeCached::Cached(ref x) => x,
}
}
}
impl<'a, T> DerefMut for MaybeCached<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
match *self {
MaybeCached::CannotCache(ref mut x) => x,
MaybeCached::Cached(ref mut x) => x,
}
}
}
#[allow(missing_debug_implementations, unreachable_pub)]
#[derive(Hash, PartialEq, Eq)]
#[cfg_attr(
docsrs,
doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
)]
pub enum StatementCacheKey<DB: Backend> {
Type(TypeId),
Sql {
sql: String,
bind_types: Vec<DB::TypeMetadata>,
},
}
impl<DB> StatementCacheKey<DB>
where
DB: Backend,
DB::QueryBuilder: Default,
DB::TypeMetadata: Clone,
{
#[allow(unreachable_pub)]
pub fn for_source(
maybe_type_id: Option<TypeId>,
source: &dyn QueryFragmentForCachedStatement<DB>,
bind_types: &[DB::TypeMetadata],
backend: &DB,
) -> QueryResult<Self> {
match maybe_type_id {
Some(id) => Ok(StatementCacheKey::Type(id)),
None => {
let sql = source.construct_sql(backend)?;
Ok(StatementCacheKey::Sql {
sql,
bind_types: bind_types.into(),
})
}
}
}
#[allow(unreachable_pub)]
pub fn sql(
&self,
source: &dyn QueryFragmentForCachedStatement<DB>,
backend: &DB,
) -> QueryResult<Cow<'_, str>> {
match *self {
StatementCacheKey::Type(_) => source.construct_sql(backend).map(Cow::Owned),
StatementCacheKey::Sql { ref sql, .. } => Ok(Cow::Borrowed(sql)),
}
}
}