use std::{borrow::Cow, fmt, marker::PhantomData, sync::Arc};
use deadpool::{
Runtime,
managed::{self, Metrics, RecycleError, RecycleResult},
};
use deadpool_sync::SyncWrapper;
use diesel::{IntoSql, RunQueryDsl, query_builder::QueryFragment};
use crate::Error;
pub struct Manager<C> {
database_url: String,
runtime: Runtime,
manager_config: Arc<ManagerConfig<C>>,
_marker: PhantomData<fn() -> C>,
}
pub type RecycleCheckCallback<C> = dyn Fn(&mut C) -> Result<(), Error> + Send + Sync;
#[derive(Default)]
pub enum RecyclingMethod<C> {
#[default]
Verified,
CustomQuery(Cow<'static, str>),
CustomFunction(Box<RecycleCheckCallback<C>>),
}
#[derive(Debug)]
pub struct ManagerConfig<C> {
pub recycling_method: RecyclingMethod<C>,
}
impl<C> Default for ManagerConfig<C> {
fn default() -> Self {
Self {
recycling_method: Default::default(),
}
}
}
impl<C: fmt::Debug> fmt::Debug for RecyclingMethod<C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Verified => write!(f, "Verified"),
Self::CustomQuery(arg0) => f.debug_tuple("CustomQuery").field(arg0).finish(),
Self::CustomFunction(_) => f.debug_tuple("CustomFunction").finish(),
}
}
}
impl<C> fmt::Debug for Manager<C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Manager")
.field("database_url", &self.database_url)
.field("runtime", &self.runtime)
.field("_marker", &self._marker)
.finish()
}
}
impl<C> Manager<C>
where
C: diesel::Connection,
{
#[must_use]
pub fn new<S: Into<String>>(database_url: S, runtime: Runtime) -> Self {
Self::from_config(database_url, runtime, Default::default())
}
#[must_use]
pub fn from_config(
database_url: impl Into<String>,
runtime: Runtime,
manager_config: ManagerConfig<C>,
) -> Self {
Manager {
database_url: database_url.into(),
runtime,
manager_config: Arc::new(manager_config),
_marker: PhantomData,
}
}
}
impl<C> managed::Manager for Manager<C>
where
C: diesel::Connection + 'static,
diesel::helper_types::select<diesel::dsl::AsExprOf<i32, diesel::sql_types::Integer>>:
QueryFragment<C::Backend>,
diesel::query_builder::SqlQuery: QueryFragment<C::Backend>,
{
type Type = crate::Connection<C>;
type Error = Error;
async fn create(&self) -> Result<Self::Type, Self::Error> {
let database_url = self.database_url.clone();
SyncWrapper::new(self.runtime, move || {
C::establish(&database_url).map_err(Into::into)
})
.await
}
async fn recycle(&self, obj: &mut Self::Type, _: &Metrics) -> RecycleResult<Self::Error> {
if obj.is_mutex_poisoned() {
return Err(RecycleError::message(
"Mutex is poisoned. Connection is considered unusable.",
));
}
let config = Arc::clone(&self.manager_config);
obj.interact(move |conn| config.recycling_method.perform_recycle_check(conn))
.await
.map_err(|e| RecycleError::message(format!("Panic: {:?}", e)))
.and_then(|r| r.map_err(RecycleError::Backend))
}
}
impl<C> RecyclingMethod<C>
where
C: diesel::Connection,
diesel::helper_types::select<diesel::dsl::AsExprOf<i32, diesel::sql_types::Integer>>:
QueryFragment<C::Backend>,
diesel::query_builder::SqlQuery: QueryFragment<C::Backend>,
{
fn perform_recycle_check(&self, conn: &mut C) -> Result<(), Error> {
use diesel::connection::TransactionManager;
if C::TransactionManager::is_broken_transaction_manager(conn) {
return Err(Error::BrokenTransactionManger);
}
match self {
RecyclingMethod::Verified => {
let _ = diesel::select(1.into_sql::<diesel::sql_types::Integer>())
.execute(conn)
.map_err(Error::Ping)?;
}
RecyclingMethod::CustomQuery(query) => {
let _ = diesel::sql_query(query.as_ref())
.execute(conn)
.map_err(Error::Ping)?;
}
RecyclingMethod::CustomFunction(check) => check(conn)?,
}
Ok(())
}
}