es_entity/
one_time_executor.rs

1//! Type-safe wrapper to ensure one database operation per executor.
2
3use crate::operation::AtomicOperation;
4
5/// A struct that owns an [`sqlx::Executor`].
6///
7/// Calling one of the `fetch_` `fn`s will consume it
8/// thus garuanteeing a 1 time usage.
9///
10/// It is not used directly but passed via the [`IntoOneTimeExecutor`] trait.
11///
12/// In order to make the consumption of the executor work we have to pass the query to the
13/// executor:
14/// ```rust
15/// async fn query(ex: impl es_entity::IntoOneTimeExecutor<'_>) -> Result<(), sqlx::Error> {
16///     ex.into_executor().fetch_optional(
17///         sqlx::query!(
18///             "SELECT NOW()"
19///         )
20///     ).await?;
21///     Ok(())
22/// }
23/// ```
24pub struct OneTimeExecutor<'c, E>
25where
26    E: sqlx::PgExecutor<'c>,
27{
28    executor: E,
29    _phantom: std::marker::PhantomData<&'c ()>,
30}
31
32impl<'c, E> OneTimeExecutor<'c, E>
33where
34    E: sqlx::PgExecutor<'c>,
35{
36    pub fn new(executor: E) -> Self {
37        OneTimeExecutor {
38            executor,
39            _phantom: std::marker::PhantomData,
40        }
41    }
42
43    /// Proxy call to `query.fetch_all` but guarantees the inner executor will only be used once.
44    pub async fn fetch_all<'q, F, O, A>(
45        self,
46        query: sqlx::query::Map<'q, sqlx::Postgres, F, A>,
47    ) -> Result<Vec<O>, sqlx::Error>
48    where
49        F: FnMut(sqlx::postgres::PgRow) -> Result<O, sqlx::Error> + Send,
50        O: Send + Unpin,
51        A: 'q + Send + sqlx::IntoArguments<'q, sqlx::Postgres>,
52    {
53        query.fetch_all(self.executor).await
54    }
55
56    /// Proxy call to `query.fetch_optional` but guarantees the inner executor will only be used once.
57    pub async fn fetch_optional<'q, F, O, A>(
58        self,
59        query: sqlx::query::Map<'q, sqlx::Postgres, F, A>,
60    ) -> Result<Option<O>, sqlx::Error>
61    where
62        F: FnMut(sqlx::postgres::PgRow) -> Result<O, sqlx::Error> + Send,
63        O: Send + Unpin,
64        A: 'q + Send + sqlx::IntoArguments<'q, sqlx::Postgres>,
65    {
66        query.fetch_optional(self.executor).await
67    }
68}
69
70/// Marker trait for [`IntoOneTimeExecutorAt<'a> + 'a`](`IntoOneTimeExecutorAt`). Do not implement directly.
71///
72/// Used as sugar to avoid writing:
73/// ```rust,ignore
74/// fn some_query<'a>(op: impl IntoOnetOneExecutorAt<'a> + 'a)
75/// ```
76/// Instead we can shorten the signature by using elision:
77/// ```rust,ignore
78/// fn some_query(op: impl IntoOnetOneExecutor<'_>)
79/// ```
80pub trait IntoOneTimeExecutor<'c>: IntoOneTimeExecutorAt<'c> + 'c {}
81impl<'c, T> IntoOneTimeExecutor<'c> for T where T: IntoOneTimeExecutorAt<'c> + 'c {}
82
83/// A trait to signify that we can use an argument for 1 round trip to the database
84///
85/// Auto implemented on all [`&mut AtomicOperation`](`AtomicOperation`) types and
86/// [`&sqlx::PgPool`](`sqlx::PgPool`).
87pub trait IntoOneTimeExecutorAt<'c> {
88    /// The concrete executor type.
89    type Executor: sqlx::PgExecutor<'c>;
90
91    /// Transforms into a [`OneTimeExecutor`] which can be used to execute a round trip.
92    fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
93    where
94        Self: 'c;
95}
96
97impl<'c> IntoOneTimeExecutorAt<'c> for &sqlx::PgPool {
98    type Executor = &'c sqlx::PgPool;
99
100    fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
101    where
102        Self: 'c,
103    {
104        OneTimeExecutor::new(self)
105    }
106}
107
108impl<'c, O> IntoOneTimeExecutorAt<'c> for &mut O
109where
110    O: AtomicOperation,
111{
112    type Executor = &'c mut sqlx::PgConnection;
113
114    fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
115    where
116        Self: 'c,
117    {
118        OneTimeExecutor::new(self.as_executor())
119    }
120}