1use async_stream::try_stream;
28use futures_core::stream::BoxStream;
29use futures_util::{TryStreamExt, future::BoxFuture};
30use sqlx::{Database, Describe, Error, Execute, Executor};
31
32use std::borrow::Cow;
33
34use crate::{db, operation::AtomicOperation, sql_commenter};
35
36#[derive(Debug)]
56pub struct OneTimeExecutor<'c, E>
57where
58 E: sqlx::Executor<'c, Database = db::Db> + 'c,
59{
60 now: Option<chrono::DateTime<chrono::Utc>>,
61 executor: E,
62 _phantom: std::marker::PhantomData<&'c ()>,
63}
64
65impl<'c, E> OneTimeExecutor<'c, E>
66where
67 E: sqlx::Executor<'c, Database = db::Db> + 'c,
68{
69 pub(crate) fn new(executor: E, now: Option<chrono::DateTime<chrono::Utc>>) -> Self {
70 OneTimeExecutor {
71 executor,
72 now,
73 _phantom: std::marker::PhantomData,
74 }
75 }
76
77 pub fn maybe_now(&self) -> Option<chrono::DateTime<chrono::Utc>> {
78 self.now
79 }
80
81 pub async fn fetch_one<'q, F, O, A>(
83 self,
84 query: sqlx::query::Map<'q, db::Db, F, A>,
85 ) -> Result<O, sqlx::Error>
86 where
87 F: FnMut(db::Row) -> Result<O, sqlx::Error> + Send,
88 O: Send + Unpin,
89 A: 'q + Send + sqlx::IntoArguments<'q, db::Db>,
90 {
91 query.fetch_one(self).await
92 }
93
94 pub async fn fetch_all<'q, F, O, A>(
96 self,
97 query: sqlx::query::Map<'q, sqlx::Postgres, F, A>,
98 ) -> Result<Vec<O>, sqlx::Error>
99 where
100 F: FnMut(sqlx::postgres::PgRow) -> Result<O, sqlx::Error> + Send,
101 O: Send + Unpin,
102 A: 'q + Send + sqlx::IntoArguments<'q, sqlx::Postgres>,
103 {
104 query.fetch_all(self).await
105 }
106
107 pub async fn fetch_optional<'q, F, O, A>(
109 self,
110 query: sqlx::query::Map<'q, sqlx::Postgres, F, A>,
111 ) -> Result<Option<O>, sqlx::Error>
112 where
113 F: FnMut(sqlx::postgres::PgRow) -> Result<O, sqlx::Error> + Send,
114 O: Send + Unpin,
115 A: 'q + Send + sqlx::IntoArguments<'q, sqlx::Postgres>,
116 {
117 query.fetch_optional(self).await
118 }
119}
120
121fn annotated_sql<'q>(query: &impl Execute<'q, db::Db>) -> Option<String> {
124 match sql_commenter::annotate_sql(query.sql()) {
125 Cow::Borrowed(_) => None,
126 Cow::Owned(sql) => Some(sql),
127 }
128}
129
130fn rebuild_annotated<'q>(
137 annotated: &str,
138 mut query: impl Execute<'q, db::Db>,
139) -> Result<sqlx::query::Query<'_, db::Db, sqlx::postgres::PgArguments>, Error> {
140 let args = query
141 .take_arguments()
142 .map_err(Error::Encode)?
143 .unwrap_or_default();
144 Ok(sqlx::query_with::<db::Db, _>(annotated, args).persistent(false))
145}
146
147impl<'c, E> Executor<'c> for OneTimeExecutor<'c, E>
148where
149 E: Executor<'c, Database = db::Db> + 'c,
150{
151 type Database = db::Db;
152
153 fn fetch_many<'e, 'q: 'e, Q>(
154 self,
155 query: Q,
156 ) -> BoxStream<'e, Result<sqlx::Either<<db::Db as Database>::QueryResult, db::Row>, Error>>
157 where
158 'c: 'e,
159 Q: 'q + Execute<'q, db::Db>,
160 {
161 let Some(annotated) = annotated_sql(&query) else {
162 return self.executor.fetch_many(query);
163 };
164 Box::pin(try_stream! {
165 let q = rebuild_annotated(annotated.as_str(), query)?;
166 let mut stream = self.executor.fetch_many(q);
167 while let Some(step) = stream.try_next().await? {
168 yield step;
169 }
170 })
171 }
172
173 fn fetch_optional<'e, 'q: 'e, Q>(
174 self,
175 query: Q,
176 ) -> BoxFuture<'e, Result<Option<db::Row>, Error>>
177 where
178 'c: 'e,
179 Q: 'q + Execute<'q, db::Db>,
180 {
181 let Some(annotated) = annotated_sql(&query) else {
182 return self.executor.fetch_optional(query);
183 };
184 Box::pin(async move {
185 let q = rebuild_annotated(annotated.as_str(), query)?;
186 self.executor.fetch_optional(q).await
187 })
188 }
189
190 fn prepare_with<'e, 'q: 'e>(
191 self,
192 sql: &'q str,
193 parameters: &'e [<db::Db as Database>::TypeInfo],
194 ) -> BoxFuture<'e, Result<<db::Db as Database>::Statement<'q>, Error>>
195 where
196 'c: 'e,
197 {
198 self.executor.prepare_with(sql, parameters)
202 }
203
204 fn describe<'e, 'q: 'e>(self, sql: &'q str) -> BoxFuture<'e, Result<Describe<db::Db>, Error>>
205 where
206 'c: 'e,
207 {
208 self.executor.describe(sql)
210 }
211}
212
213pub trait IntoOneTimeExecutor<'c>: IntoOneTimeExecutorAt<'c> + 'c {}
224impl<'c, T> IntoOneTimeExecutor<'c> for T where T: IntoOneTimeExecutorAt<'c> + 'c {}
225
226pub trait IntoOneTimeExecutorAt<'c> {
231 type Executor: sqlx::Executor<'c, Database = db::Db>;
233
234 fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
236 where
237 Self: 'c;
238}
239
240impl<'c, E> IntoOneTimeExecutorAt<'c> for OneTimeExecutor<'c, E>
241where
242 E: sqlx::Executor<'c, Database = db::Db> + 'c,
243{
244 type Executor = E;
245
246 fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
247 where
248 Self: 'c,
249 {
250 self
251 }
252}
253
254impl<'c> IntoOneTimeExecutorAt<'c> for &db::Pool {
255 type Executor = &'c db::Pool;
256
257 fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
258 where
259 Self: 'c,
260 {
261 OneTimeExecutor::new(self, None)
262 }
263}
264
265impl<'c, O> IntoOneTimeExecutorAt<'c> for &mut O
266where
267 O: AtomicOperation,
268{
269 type Executor = &'c mut db::Connection;
270
271 fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
272 where
273 Self: 'c,
274 {
275 let now = self.maybe_now();
276 OneTimeExecutor::new(self.connection(), now)
277 }
278}