Skip to main content

alibi_sqlx/
hooks.rs

1//! `SQLx` bindings for the shared database lifecycle hooks.
2
3use crate::error::map_sqlx_err;
4use crate::pool::{SqlxPool, SqlxTransaction, SqlxTransactionGuard};
5use alibi_core::error::AuthResult;
6pub use alibi_core::hooks::current_request_hook_context;
7pub use alibi_core::store::{DatabaseHookContext, DatabaseHooks, HookBackend, HookControl};
8
9/// The `SQLx` hook backend: implement [`DatabaseHooks<S, SqlxBackend>`] to
10/// receive its pool and transaction.
11///
12/// Transactional creation after callbacks retain the created snapshots and
13/// run in operation order after commit. Rollback discards them; callback errors
14/// are returned to the caller after the auth writes have committed.
15#[derive(Debug, Clone, Copy)]
16pub struct SqlxBackend;
17
18impl HookBackend for SqlxBackend {
19    type Connection = SqlxPool;
20    type Transaction = SqlxTransaction;
21}
22
23/// Context passed to `SQLx` lifecycle hooks.
24pub type SqlxHookContext<'a> = DatabaseHookContext<'a, SqlxBackend>;
25
26/// Query access through the active auth transaction, or the pool outside one.
27pub trait SqlxHookContextExt {
28    /// Acquire a connection for application queries.
29    ///
30    /// Inside a transaction this locks its connection, so queries see earlier
31    /// uncommitted auth writes and participate in commit or rollback. Outside
32    /// one (including creation after hooks), this acquires a pool connection.
33    /// Drop the guard before another auth operation or connection acquisition
34    /// in the same transaction; holding it would block that operation.
35    fn connection(&self) -> impl Future<Output = AuthResult<SqlxHookConnection<'_>>> + Send;
36}
37
38impl SqlxHookContextExt for SqlxHookContext<'_> {
39    async fn connection(&self) -> AuthResult<SqlxHookConnection<'_>> {
40        let inner = if let Some(transaction) = self.tx {
41            HookConnection::Transaction(transaction.lock().await)
42        } else {
43            match self.db {
44                #[cfg(feature = "sqlite")]
45                SqlxPool::Sqlite(pool) => {
46                    HookConnection::Sqlite(pool.acquire().await.map_err(map_sqlx_err)?)
47                }
48                #[cfg(feature = "postgres")]
49                SqlxPool::Postgres(pool) => {
50                    HookConnection::Postgres(pool.acquire().await.map_err(map_sqlx_err)?)
51                }
52            }
53        };
54        Ok(SqlxHookConnection { inner })
55    }
56}
57
58#[derive(Debug)]
59enum HookConnection<'a> {
60    Transaction(SqlxTransactionGuard<'a>),
61    #[cfg(feature = "sqlite")]
62    Sqlite(sqlx::pool::PoolConnection<sqlx::Sqlite>),
63    #[cfg(feature = "postgres")]
64    Postgres(sqlx::pool::PoolConnection<sqlx::Postgres>),
65}
66
67/// A native connection acquired by [`SqlxHookContextExt::connection`].
68///
69/// Dropping this guard releases the transaction lock or returns the connection
70/// to its pool. It never commits or rolls back the auth transaction itself.
71#[derive(Debug)]
72pub struct SqlxHookConnection<'a> {
73    inner: HookConnection<'a>,
74}
75
76impl SqlxHookConnection<'_> {
77    /// The SQLite connection, or `None` for a PostgreSQL backend.
78    #[cfg(feature = "sqlite")]
79    pub fn sqlite(&mut self) -> Option<&mut sqlx::SqliteConnection> {
80        match &mut self.inner {
81            HookConnection::Transaction(guard) => guard.sqlite(),
82            HookConnection::Sqlite(connection) => Some(connection),
83            #[cfg(feature = "postgres")]
84            HookConnection::Postgres(_) => None,
85        }
86    }
87
88    /// The PostgreSQL connection, or `None` for a SQLite backend.
89    #[cfg(feature = "postgres")]
90    pub fn postgres(&mut self) -> Option<&mut sqlx::PgConnection> {
91        match &mut self.inner {
92            HookConnection::Transaction(guard) => guard.postgres(),
93            HookConnection::Postgres(connection) => Some(connection),
94            #[cfg(feature = "sqlite")]
95            HookConnection::Sqlite(_) => None,
96        }
97    }
98}