Skip to main content

tank_core/
pool.rs

1use crate::{
2    AsQuery, Connection, Driver, Entity, Error, Executor, Query, QueryResult, Result, Row,
3    RowsAffected,
4};
5use deadpool::managed::{Manager, Metrics, Object, Pool, RecycleResult, Timeouts};
6use futures::{FutureExt, Stream, future::BoxFuture};
7use std::{
8    borrow::Cow,
9    fmt::Debug,
10    future,
11    ops::{Deref, DerefMut},
12    time::Duration,
13};
14
15/// The [`Manager`] that backs every Tank connection pool.
16///
17/// A manager holds the [`Driver`] and the database URL; it knows how to open
18/// new connections on demand and how to validate recycled ones.  You do not
19/// need to construct or interact with this type directly, it is created
20/// internally by [`Driver::connect_pool`].
21#[derive(Debug)]
22pub struct DBConnectionManager<D: Driver> {
23    driver: D,
24    url: Cow<'static, str>,
25}
26
27impl<D: Driver> DBConnectionManager<D> {
28    pub fn new(driver: D, url: Cow<'static, str>) -> Self {
29        Self { driver, url }
30    }
31}
32
33impl<D: Driver> Manager for DBConnectionManager<D> {
34    type Type = D::Connection;
35    type Error = Error;
36    async fn create(&self) -> Result<Self::Type> {
37        Ok(D::Connection::connect(&self.driver, self.url.clone()).await?)
38    }
39    fn recycle(
40        &self,
41        _: &mut Self::Type,
42        _: &Metrics,
43    ) -> impl Future<Output = RecycleResult<Self::Error>> + Send {
44        future::ready(RecycleResult::Ok(()))
45    }
46}
47
48/// A database connection borrowed from a [`ConnectionPool`].
49///
50/// Implements both [`Executor`] and [`Connection`], so it can be used anywhere
51/// a plain connection is expected.  Deref-coerces to the underlying driver
52/// connection (`D::Connection`) via [`Deref`]/[`DerefMut`], giving access to
53/// any driver-specific extension methods.
54///
55/// The borrowed connection is automatically returned to the pool when this
56/// value is dropped.  If you need to take full ownership of the connection
57/// outside pool management, call [`ConnectionPool::detach`] instead.
58///
59/// # Obtaining a `PooledConnection`
60///
61/// Call [`ConnectionPool::get`] or [`ConnectionPool::timeout_get`] on a pool:
62///
63/// ```rust
64/// let mut pool = PostgresDriver::new()
65///     .connect_pool("postgres://localhost/mydb".into(), PoolConfig::new())
66///     .await?;
67///
68/// let mut conn = pool.get().await?;
69/// Tank::create_table(&mut conn, true, true).await?;
70/// ```
71#[derive(Debug)]
72pub struct PooledConnection<D: Driver> {
73    pub(crate) object: Object<DBConnectionManager<D>>,
74}
75
76/// A managed pool of reusable database connections.
77///
78/// Every method that yields connections produces a [`PooledConnection`]
79/// that is automatically returned to the pool on drop, keeping the number
80/// of open database connections bounded.
81pub trait ConnectionPool<D: Driver>: Debug {
82    /// Acquires a connection from the pool.
83    ///
84    /// If all connections are in use and the pool is at its maximum size, this
85    /// call waits until one becomes available or the configured
86    /// [`PoolConfig::wait_timeout`] elapses, at which point an error is
87    /// returned.
88    fn get<'s>(&'s self) -> BoxFuture<'s, Result<PooledConnection<D>>>;
89
90    /// Acquires a connection from the pool with an explicit timeout.
91    ///
92    /// Identical to [`get`](ConnectionPool::get) but overrides the configured
93    /// wait timeout with `timeout`.  Useful when individual call sites need
94    /// stricter or looser deadlines than the pool-level default.
95    fn timeout_get<'s>(&'s self, timeout: Duration) -> BoxFuture<'s, Result<PooledConnection<D>>>;
96
97    /// Acquires a connection and removes it from pool management.
98    ///
99    /// The returned `D::Connection` is a plain, pool-unaware connection.  It
100    /// will **not** be returned to the pool when dropped, the underlying
101    /// database connection is closed instead. Use this when you need to hand
102    /// a connection to code that does not know about the pool, or when you
103    /// want to guarantee the connection is fully closed rather than recycled.
104    fn detach<'s>(&'s self) -> BoxFuture<'s, Result<D::Connection>>;
105
106    /// Changes the maximum number of connections the pool may hold.
107    fn resize(&self, max_size: usize) -> Result<()>;
108
109    /// Converts this pool into a sized type-erased `Box<dyn ConnectionPool<D>>`.
110    ///
111    /// `Driver::connect_pool` returns an opaque `impl ConnectionPool<D>` type
112    /// that cannot be named or stored in struct fields, returned from trait
113    /// implementations, or otherwise used where a concrete named type is required.
114    fn into_box(self) -> Box<dyn ConnectionPool<D>>
115    where
116        D: 'static;
117
118    /// Closes the pool, marking it as unavailable and dropping all managed
119    /// connections.
120    fn close(self) -> BoxFuture<'static, Result<()>>;
121}
122
123impl<D: Driver> ConnectionPool<D> for Pool<DBConnectionManager<D>>
124where
125    <D as Driver>::Connection: Debug,
126{
127    fn get<'s>(&'s self) -> BoxFuture<'s, Result<PooledConnection<D>>> {
128        async move {
129            let object = Pool::<DBConnectionManager<D>>::get(self)
130                .await
131                .map_err(|e| Error::msg(format!("{e:#?}")))?;
132            Ok(PooledConnection { object })
133        }
134        .boxed()
135    }
136
137    fn timeout_get<'s>(&'s self, timeout: Duration) -> BoxFuture<'s, Result<PooledConnection<D>>> {
138        async move {
139            let object = Pool::<DBConnectionManager<D>>::timeout_get(
140                self,
141                &Timeouts::wait_millis(timeout.as_millis() as u64),
142            )
143            .await
144            .map_err(|e| Error::msg(format!("{e:#?}")))?;
145            Ok(PooledConnection::<D> { object })
146        }
147        .boxed()
148    }
149
150    fn detach<'s>(&'s self) -> BoxFuture<'s, Result<D::Connection>>
151    where
152        Self: Sized,
153    {
154        async {
155            let v = Pool::<DBConnectionManager<D>>::get(self)
156                .await
157                .map_err(|e| Error::msg(format!("{e:#?}")))?;
158            Ok(Object::<DBConnectionManager<D>>::take(v))
159        }
160        .boxed()
161    }
162
163    fn resize(&self, max_size: usize) -> Result<()> {
164        Ok(self.resize(max_size))
165    }
166
167    fn into_box(self) -> Box<dyn ConnectionPool<D>>
168    where
169        D: 'static,
170    {
171        Box::new(self)
172    }
173
174    fn close(self) -> BoxFuture<'static, Result<()>> {
175        Self::close(&self);
176        future::ready(Ok(())).boxed()
177    }
178}
179
180impl<D: Driver> Executor for PooledConnection<D> {
181    type Driver = D;
182
183    fn accepts_multiple_statements(&self) -> bool {
184        self.object.accepts_multiple_statements()
185    }
186
187    fn driver(&self) -> D {
188        self.object.driver()
189    }
190
191    fn prepare<'s>(
192        &'s mut self,
193        query: impl AsQuery<D> + 's,
194    ) -> impl Future<Output = Result<Query<D>>> + Send {
195        self.object.prepare(query)
196    }
197
198    fn do_prepare(&mut self, sql: String) -> impl Future<Output = Result<Query<D>>> + Send {
199        self.object.do_prepare(sql)
200    }
201
202    fn run<'s>(
203        &'s mut self,
204        query: impl AsQuery<D> + 's,
205    ) -> impl Stream<Item = Result<QueryResult>> + Send {
206        self.object.run(query)
207    }
208
209    fn fetch<'s>(
210        &'s mut self,
211        query: impl AsQuery<D> + 's,
212    ) -> impl Stream<Item = Result<Row>> + Send {
213        self.object.fetch(query)
214    }
215
216    fn execute<'s>(
217        &'s mut self,
218        query: impl AsQuery<D> + 's,
219    ) -> impl Future<Output = Result<RowsAffected>> + Send {
220        self.object.execute(query)
221    }
222
223    fn append<'a, E, It>(
224        &mut self,
225        entities: It,
226    ) -> impl Future<Output = Result<RowsAffected>> + Send
227    where
228        E: Entity + 'a,
229        It: IntoIterator<Item = &'a E> + Send,
230        <It as IntoIterator>::IntoIter: Send,
231    {
232        self.object.append(entities)
233    }
234}
235
236impl<D: Driver> Connection for PooledConnection<D> {
237    fn connect(
238        _driver: &D,
239        _url: Cow<'static, str>,
240    ) -> impl Future<Output = Result<<D as Driver>::Connection>> + Send
241    where
242        Self: Sized,
243    {
244        future::ready(Err(Error::msg(
245            "Cannot connect using a PooledConnection, such object must be obtained from a connection pool",
246        )))
247    }
248
249    fn begin(&mut self) -> impl Future<Output = Result<<D as Driver>::Transaction<'_>>> + Send {
250        self.object.begin()
251    }
252}
253
254impl<D: Driver> Deref for PooledConnection<D> {
255    type Target = D::Connection;
256    fn deref(&self) -> &Self::Target {
257        &self.object
258    }
259}
260
261impl<D: Driver> DerefMut for PooledConnection<D> {
262    fn deref_mut(&mut self) -> &mut Self::Target {
263        &mut self.object
264    }
265}
266
267impl<D: Driver> AsRef<D::Connection> for PooledConnection<D> {
268    fn as_ref(&self) -> &D::Connection {
269        &self.object
270    }
271}
272
273impl<D: Driver> AsMut<D::Connection> for PooledConnection<D> {
274    fn as_mut(&mut self) -> &mut D::Connection {
275        &mut self.object
276    }
277}