Skip to main content

deadpool_diesel/
manager.rs

1use std::{borrow::Cow, fmt, marker::PhantomData, sync::Arc};
2
3use deadpool::{
4    managed::{self, Metrics, RecycleError, RecycleResult},
5    Runtime,
6};
7use deadpool_sync::SyncWrapper;
8use diesel::{query_builder::QueryFragment, IntoSql, RunQueryDsl};
9
10use crate::Error;
11
12/// [`Connection`] [`Manager`] for use with [`diesel`].
13///
14/// See the [`deadpool` documentation](deadpool) for usage examples.
15///
16/// [`Manager`]: managed::Manager
17/// [`Connection`]: crate::Connection
18pub struct Manager<C> {
19    database_url: String,
20    runtime: Runtime,
21    manager_config: Arc<ManagerConfig<C>>,
22    _marker: PhantomData<fn() -> C>,
23}
24
25/// Type of the recycle check callback for the [`RecyclingMethod::CustomFunction`] variant
26pub type RecycleCheckCallback<C> = dyn Fn(&mut C) -> Result<(), Error> + Send + Sync;
27
28/// Possible methods of how a connection is recycled.
29pub enum RecyclingMethod<C> {
30    /// Only check for open transactions when recycling existing connections
31    /// Unless you have special needs this is a safe choice.
32    ///
33    /// If the database connection is closed you will recieve an error on the first place
34    /// you actually try to use the connection
35    Fast,
36    /// In addition to checking for open transactions a test query is executed
37    ///
38    /// This is slower, but guarantees that the database connection is ready to be used.
39    Verified,
40    /// Like `Verified` but with a custom query
41    CustomQuery(Cow<'static, str>),
42    /// Like `Verified` but with a custom callback that allows to perform more checks
43    ///
44    /// The connection is only recycled if the callback returns `Ok(())`
45    CustomFunction(Box<RecycleCheckCallback<C>>),
46}
47
48// We use manual implementation here instead of `#[derive(Default)]` as of MSRV 1.63, it generates
49// redundant `C: Default` bound, which imposes problems in the code.
50// TODO: Use `#[derive(Default)]` with `#[default]` attribute once MSRV is bumped to 1.66 or above.
51impl<C> Default for RecyclingMethod<C> {
52    fn default() -> Self {
53        Self::Fast
54    }
55}
56
57/// Configuration object for a Manager.
58///
59/// This currently only makes it possible to specify which [`RecyclingMethod`]
60/// should be used when retrieving existing objects from the [`Pool`].
61///
62/// [`Pool`]: crate::Pool
63#[derive(Debug)]
64pub struct ManagerConfig<C> {
65    /// Method of how a connection is recycled. See [RecyclingMethod].
66    pub recycling_method: RecyclingMethod<C>,
67}
68
69impl<C> Default for ManagerConfig<C> {
70    fn default() -> Self {
71        Self {
72            recycling_method: Default::default(),
73        }
74    }
75}
76
77impl<C: fmt::Debug> fmt::Debug for RecyclingMethod<C> {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        match self {
80            Self::Fast => write!(f, "Fast"),
81            Self::Verified => write!(f, "Verified"),
82            Self::CustomQuery(arg0) => f.debug_tuple("CustomQuery").field(arg0).finish(),
83            Self::CustomFunction(_) => f.debug_tuple("CustomFunction").finish(),
84        }
85    }
86}
87
88// Implemented manually to avoid unnecessary trait bound on `C` type parameter.
89impl<C> fmt::Debug for Manager<C> {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.debug_struct("Manager")
92            .field("database_url", &self.database_url)
93            .field("runtime", &self.runtime)
94            .field("_marker", &self._marker)
95            .finish()
96    }
97}
98
99impl<C> Manager<C>
100where
101    C: diesel::Connection,
102{
103    /// Creates a new [`Manager`] which establishes [`Connection`]s to the given
104    /// `database_url`.
105    ///
106    /// [`Connection`]: crate::Connection
107    #[must_use]
108    pub fn new<S: Into<String>>(database_url: S, runtime: Runtime) -> Self {
109        Self::from_config(database_url, runtime, Default::default())
110    }
111
112    /// Creates a new [`Manager`] which establishes [`Connection`]s to the given
113    /// `database_url` with a specific [`ManagerConfig`].
114    ///
115    /// [`Connection`]: crate::Connection
116    #[must_use]
117    pub fn from_config(
118        database_url: impl Into<String>,
119        runtime: Runtime,
120        manager_config: ManagerConfig<C>,
121    ) -> Self {
122        Manager {
123            database_url: database_url.into(),
124            runtime,
125            manager_config: Arc::new(manager_config),
126            _marker: PhantomData,
127        }
128    }
129}
130
131impl<C> managed::Manager for Manager<C>
132where
133    C: diesel::Connection + 'static,
134    diesel::dsl::BareSelect<diesel::dsl::AsExprOf<i32, diesel::sql_types::Integer>>:
135        QueryFragment<C::Backend>,
136    diesel::query_builder::SqlQuery: QueryFragment<C::Backend>,
137{
138    type Type = crate::Connection<C>;
139    type Error = Error;
140
141    async fn create(&self) -> Result<Self::Type, Self::Error> {
142        let database_url = self.database_url.clone();
143        SyncWrapper::new(self.runtime, move || {
144            C::establish(&database_url).map_err(Into::into)
145        })
146        .await
147    }
148
149    async fn recycle(&self, obj: &mut Self::Type, _: &Metrics) -> RecycleResult<Self::Error> {
150        if obj.is_mutex_poisoned() {
151            return Err(RecycleError::message(
152                "Mutex is poisoned. Connection is considered unusable.",
153            ));
154        }
155        let config = Arc::clone(&self.manager_config);
156        obj.interact(move |conn| config.recycling_method.perform_recycle_check(conn))
157            .await
158            .map_err(|e| RecycleError::message(format!("Panic: {:?}", e)))
159            .and_then(|r| r.map_err(RecycleError::Backend))
160    }
161}
162
163impl<C> RecyclingMethod<C>
164where
165    C: diesel::Connection,
166    diesel::dsl::BareSelect<diesel::dsl::AsExprOf<i32, diesel::sql_types::Integer>>:
167        QueryFragment<C::Backend>,
168    diesel::query_builder::SqlQuery: QueryFragment<C::Backend>,
169{
170    fn perform_recycle_check(&self, conn: &mut C) -> Result<(), Error> {
171        use diesel::connection::TransactionManager;
172
173        // first always check for open transactions because
174        // we really do not want to have a connection with a
175        // dangling transaction in our connection pool
176        if C::TransactionManager::is_broken_transaction_manager(conn) {
177            return Err(Error::BrokenTransactionManger);
178        }
179        match self {
180            // For fast we are basically done
181            RecyclingMethod::Fast => {}
182            // For verified we perform a `SELECT 1` statement
183            // We use the DSL here to make this somewhat independent from
184            // the backend SQL dialect
185            RecyclingMethod::Verified => {
186                let _ = diesel::select(1.into_sql::<diesel::sql_types::Integer>())
187                    .execute(conn)
188                    .map_err(Error::Ping)?;
189            }
190            // For custom query we just execute the user provided query
191            RecyclingMethod::CustomQuery(query) => {
192                let _ = diesel::sql_query(query.as_ref())
193                    .execute(conn)
194                    .map_err(Error::Ping)?;
195            }
196            // for custom function we call the relevant closure
197            RecyclingMethod::CustomFunction(check) => check(conn)?,
198        }
199        Ok(())
200    }
201}