server/user/connection.rs
1//! A tenant connection that keeps its pool alive.
2
3use crate::user::userpool::registry::TenantPool;
4use sqlx::pool::PoolConnection;
5use sqlx::{PgConnection, Postgres};
6use std::ops::{Deref, DerefMut};
7use std::sync::Arc;
8
9/// A checked-out connection, holding the tenant pool it came from.
10///
11/// The anchor is what makes the residency bound true. A permit lives inside
12/// [`TenantPool`] and is returned when the last handle drops, so handing back a
13/// bare `PoolConnection` released the permit while the connection was still
14/// open — sqlx keeps the pool alive behind the connection, so the process could
15/// then admit a ninth resident pool and exceed the budget it claims to enforce.
16///
17/// Dereferences to [`PgConnection`], so `&mut *conn` and `&mut conn` reach an
18/// executor exactly as they did before.
19pub struct TenantConnection {
20 conn: PoolConnection<Postgres>,
21 /// `None` for the global/admin pool, which is bounded by its own cap rather
22 /// than by residency permits.
23 _anchor: Option<Arc<TenantPool>>,
24}
25
26impl TenantConnection {
27 /// Only the tenant-routing half builds these, and that half is excluded
28 /// from server's own `cfg(test)` runs.
29 #[cfg(not(test))]
30 pub(crate) const fn tenant(conn: PoolConnection<Postgres>, anchor: Arc<TenantPool>) -> Self {
31 Self {
32 conn,
33 _anchor: Some(anchor),
34 }
35 }
36
37 /// Only the test and `test-utils` arms of `User::get_connection` route a
38 /// global connection through the tenant type; production always has a pool.
39 #[cfg(any(test, feature = "test-utils"))]
40 pub(crate) const fn global(conn: PoolConnection<Postgres>) -> Self {
41 Self {
42 conn,
43 _anchor: None,
44 }
45 }
46}
47
48impl Deref for TenantConnection {
49 type Target = PgConnection;
50
51 fn deref(&self) -> &Self::Target {
52 &self.conn
53 }
54}
55
56impl DerefMut for TenantConnection {
57 fn deref_mut(&mut self) -> &mut Self::Target {
58 &mut self.conn
59 }
60}