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