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    now: Option<chrono::DateTime<chrono::Utc>>,
29    executor: E,
30    _phantom: std::marker::PhantomData<&'c ()>,
31}
32
33impl<'c, E> OneTimeExecutor<'c, E>
34where
35    E: sqlx::PgExecutor<'c>,
36{
37    fn new(executor: E, now: Option<chrono::DateTime<chrono::Utc>>) -> Self {
38        OneTimeExecutor {
39            executor,
40            now,
41            _phantom: std::marker::PhantomData,
42        }
43    }
44
45    pub fn maybe_now(&self) -> Option<chrono::DateTime<chrono::Utc>> {
46        self.now
47    }
48
49    /// Proxy call to `query.fetch_one` but guarantees the inner executor will only be used once.
50    pub async fn fetch_one<'q, F, O, A>(
51        self,
52        query: sqlx::query::Map<'q, sqlx::Postgres, F, A>,
53    ) -> Result<O, sqlx::Error>
54    where
55        F: FnMut(sqlx::postgres::PgRow) -> Result<O, sqlx::Error> + Send,
56        O: Send + Unpin,
57        A: 'q + Send + sqlx::IntoArguments<'q, sqlx::Postgres>,
58    {
59        query.fetch_one(self.executor).await
60    }
61
62    /// Proxy call to `query.fetch_all` but guarantees the inner executor will only be used once.
63    pub async fn fetch_all<'q, F, O, A>(
64        self,
65        query: sqlx::query::Map<'q, sqlx::Postgres, F, A>,
66    ) -> Result<Vec<O>, sqlx::Error>
67    where
68        F: FnMut(sqlx::postgres::PgRow) -> Result<O, sqlx::Error> + Send,
69        O: Send + Unpin,
70        A: 'q + Send + sqlx::IntoArguments<'q, sqlx::Postgres>,
71    {
72        query.fetch_all(self.executor).await
73    }
74
75    /// Proxy call to `query.fetch_optional` but guarantees the inner executor will only be used once.
76    pub async fn fetch_optional<'q, F, O, A>(
77        self,
78        query: sqlx::query::Map<'q, sqlx::Postgres, F, A>,
79    ) -> Result<Option<O>, sqlx::Error>
80    where
81        F: FnMut(sqlx::postgres::PgRow) -> Result<O, sqlx::Error> + Send,
82        O: Send + Unpin,
83        A: 'q + Send + sqlx::IntoArguments<'q, sqlx::Postgres>,
84    {
85        query.fetch_optional(self.executor).await
86    }
87}
88
89/// Marker trait for [`IntoOneTimeExecutorAt<'a> + 'a`](`IntoOneTimeExecutorAt`). Do not implement directly.
90///
91/// Used as sugar to avoid writing:
92/// ```rust,ignore
93/// fn some_query<'a>(op: impl IntoOnetOneExecutorAt<'a> + 'a)
94/// ```
95/// Instead we can shorten the signature by using elision:
96/// ```rust,ignore
97/// fn some_query(op: impl IntoOnetOneExecutor<'_>)
98/// ```
99pub trait IntoOneTimeExecutor<'c>: IntoOneTimeExecutorAt<'c> + 'c {}
100impl<'c, T> IntoOneTimeExecutor<'c> for T where T: IntoOneTimeExecutorAt<'c> + 'c {}
101
102/// A trait to signify that we can use an argument for 1 round trip to the database
103///
104/// Auto implemented on all [`&mut AtomicOperation`](`AtomicOperation`) types and
105/// [`&sqlx::PgPool`](`sqlx::PgPool`).
106pub trait IntoOneTimeExecutorAt<'c> {
107    /// The concrete executor type.
108    type Executor: sqlx::PgExecutor<'c>;
109
110    /// Transforms into a [`OneTimeExecutor`] which can be used to execute a round trip.
111    fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
112    where
113        Self: 'c;
114}
115
116impl<'c, E> IntoOneTimeExecutorAt<'c> for OneTimeExecutor<'c, E>
117where
118    E: sqlx::PgExecutor<'c> + 'c,
119{
120    type Executor = E;
121
122    fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
123    where
124        Self: 'c,
125    {
126        self
127    }
128}
129
130impl<'c> IntoOneTimeExecutorAt<'c> for &sqlx::PgPool {
131    type Executor = &'c sqlx::PgPool;
132
133    fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
134    where
135        Self: 'c,
136    {
137        OneTimeExecutor::new(self, None)
138    }
139}
140
141impl<'c, O> IntoOneTimeExecutorAt<'c> for &mut O
142where
143    O: AtomicOperation,
144{
145    type Executor = &'c mut sqlx::PgConnection;
146
147    fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
148    where
149        Self: 'c,
150    {
151        let now = self.maybe_now();
152        OneTimeExecutor::new(self.as_executor(), now)
153    }
154}