use downcast_rs::Downcast;
use std::fmt::{Debug, Display};
use std::num::NonZeroU32;
use std::ops::{Deref, DerefMut};
static GLOBAL_INSTRUMENTATION: std::sync::RwLock<fn() -> Option<Box<dyn Instrumentation>>> =
std::sync::RwLock::new(|| None);
pub trait DebugQuery: Debug + Display {}
impl<T, DB> DebugQuery for crate::query_builder::DebugQuery<'_, T, DB> where Self: Debug + Display {}
#[diesel_derives::__diesel_public_if(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
)]
pub(crate) struct StrQueryHelper<'query> {
s: &'query str,
}
impl<'query> StrQueryHelper<'query> {
#[diesel_derives::__diesel_public_if(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
)]
#[cfg(any(
feature = "postgres",
feature = "sqlite",
feature = "mysql",
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
))]
pub(crate) fn new(s: &'query str) -> Self {
Self { s }
}
}
impl Debug for StrQueryHelper<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(self.s, f)
}
}
impl Display for StrQueryHelper<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.s, f)
}
}
impl DebugQuery for StrQueryHelper<'_> {}
#[derive(Debug)]
#[non_exhaustive]
pub enum InstrumentationEvent<'a> {
#[non_exhaustive]
StartEstablishConnection {
url: &'a str,
},
#[non_exhaustive]
FinishEstablishConnection {
url: &'a str,
error: Option<&'a crate::result::ConnectionError>,
},
#[non_exhaustive]
StartQuery {
query: &'a dyn DebugQuery,
},
#[non_exhaustive]
CacheQuery {
sql: &'a str,
},
#[non_exhaustive]
FinishQuery {
query: &'a dyn DebugQuery,
error: Option<&'a crate::result::Error>,
},
#[non_exhaustive]
BeginTransaction {
depth: NonZeroU32,
},
#[non_exhaustive]
CommitTransaction {
depth: NonZeroU32,
},
#[non_exhaustive]
RollbackTransaction {
depth: NonZeroU32,
},
}
#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
impl<'a> InstrumentationEvent<'a> {
#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
pub fn start_establish_connection(url: &'a str) -> Self {
Self::StartEstablishConnection { url }
}
#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
pub fn finish_establish_connection(
url: &'a str,
error: Option<&'a crate::result::ConnectionError>,
) -> Self {
Self::FinishEstablishConnection { url, error }
}
#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
pub fn start_query(query: &'a dyn DebugQuery) -> Self {
Self::StartQuery { query }
}
#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
pub fn cache_query(sql: &'a str) -> Self {
Self::CacheQuery { sql }
}
#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
pub fn finish_query(
query: &'a dyn DebugQuery,
error: Option<&'a crate::result::Error>,
) -> Self {
Self::FinishQuery { query, error }
}
#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
pub fn begin_transaction(depth: NonZeroU32) -> Self {
Self::BeginTransaction { depth }
}
#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
pub fn rollback_transaction(depth: NonZeroU32) -> Self {
Self::RollbackTransaction { depth }
}
#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
pub fn commit_transaction(depth: NonZeroU32) -> Self {
Self::CommitTransaction { depth }
}
}
pub trait Instrumentation: Downcast + Send + 'static {
fn on_connection_event(&mut self, event: InstrumentationEvent<'_>);
}
downcast_rs::impl_downcast!(Instrumentation);
pub fn get_default_instrumentation() -> Option<Box<dyn Instrumentation>> {
match GLOBAL_INSTRUMENTATION.read() {
Ok(f) => (*f)(),
Err(_) => None,
}
}
pub fn set_default_instrumentation(
default: fn() -> Option<Box<dyn Instrumentation>>,
) -> crate::QueryResult<()> {
match GLOBAL_INSTRUMENTATION.write() {
Ok(mut l) => {
*l = default;
Ok(())
}
Err(e) => Err(crate::result::Error::DatabaseError(
crate::result::DatabaseErrorKind::Unknown,
Box::new(e.to_string()),
)),
}
}
impl<F> Instrumentation for F
where
F: FnMut(InstrumentationEvent<'_>) + Send + 'static,
{
fn on_connection_event(&mut self, event: InstrumentationEvent<'_>) {
(self)(event)
}
}
impl Instrumentation for Box<dyn Instrumentation> {
fn on_connection_event(&mut self, event: InstrumentationEvent<'_>) {
self.deref_mut().on_connection_event(event)
}
}
impl<T> Instrumentation for Option<T>
where
T: Instrumentation,
{
fn on_connection_event(&mut self, event: InstrumentationEvent<'_>) {
if let Some(i) = self {
i.on_connection_event(event)
}
}
}
#[diesel_derives::__diesel_public_if(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
)]
pub(crate) struct DynInstrumentation {
no_instrumentation: NoInstrumentation,
inner: Option<Box<dyn Instrumentation>>,
}
impl Deref for DynInstrumentation {
type Target = dyn Instrumentation;
fn deref(&self) -> &Self::Target {
self.inner.as_deref().unwrap_or(&self.no_instrumentation)
}
}
impl DerefMut for DynInstrumentation {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner
.as_deref_mut()
.unwrap_or(&mut self.no_instrumentation)
}
}
impl DynInstrumentation {
#[diesel_derives::__diesel_public_if(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
)]
#[cfg(any(
feature = "postgres",
feature = "sqlite",
feature = "mysql",
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
))]
pub(crate) fn default_instrumentation() -> Self {
Self {
inner: get_default_instrumentation(),
no_instrumentation: NoInstrumentation,
}
}
#[diesel_derives::__diesel_public_if(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
)]
#[cfg(any(
feature = "postgres",
feature = "sqlite",
feature = "mysql",
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
))]
pub(crate) fn none() -> Self {
Self {
inner: None,
no_instrumentation: NoInstrumentation,
}
}
#[diesel_derives::__diesel_public_if(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
)]
#[cfg(any(
feature = "postgres",
feature = "sqlite",
feature = "mysql",
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
))]
pub(crate) fn on_connection_event(&mut self, event: InstrumentationEvent<'_>) {
if let Some(inner) = self.inner.as_deref_mut() {
inner.on_connection_event(event)
}
}
}
impl<I: Instrumentation> From<I> for DynInstrumentation {
fn from(instrumentation: I) -> Self {
Self {
inner: Some(unpack_instrumentation(Box::new(instrumentation))),
no_instrumentation: NoInstrumentation,
}
}
}
struct NoInstrumentation;
impl Instrumentation for NoInstrumentation {
fn on_connection_event(&mut self, _: InstrumentationEvent<'_>) {}
}
fn unpack_instrumentation(
mut instrumentation: Box<dyn Instrumentation>,
) -> Box<dyn Instrumentation> {
loop {
match instrumentation.downcast::<Box<dyn Instrumentation>>() {
Ok(extra_boxed_instrumentation) => instrumentation = *extra_boxed_instrumentation,
Err(not_extra_boxed_instrumentation) => {
break not_extra_boxed_instrumentation;
}
}
}
}