use async_stream::try_stream;
use futures_core::stream::BoxStream;
use futures_util::{TryStreamExt, future::BoxFuture};
use sqlx::{Database, Describe, Error, Execute, Executor};
use std::borrow::Cow;
use crate::{db, operation::AtomicOperation, sql_commenter};
#[derive(Debug)]
pub struct OneTimeExecutor<'c, E>
where
E: sqlx::Executor<'c, Database = db::Db> + 'c,
{
now: Option<chrono::DateTime<chrono::Utc>>,
executor: E,
_phantom: std::marker::PhantomData<&'c ()>,
}
impl<'c, E> OneTimeExecutor<'c, E>
where
E: sqlx::Executor<'c, Database = db::Db> + 'c,
{
pub(crate) fn new(executor: E, now: Option<chrono::DateTime<chrono::Utc>>) -> Self {
OneTimeExecutor {
executor,
now,
_phantom: std::marker::PhantomData,
}
}
pub fn maybe_now(&self) -> Option<chrono::DateTime<chrono::Utc>> {
self.now
}
pub async fn fetch_one<'q, F, O, A>(
self,
query: sqlx::query::Map<'q, db::Db, F, A>,
) -> Result<O, sqlx::Error>
where
F: FnMut(db::Row) -> Result<O, sqlx::Error> + Send,
O: Send + Unpin,
A: 'q + Send + sqlx::IntoArguments<'q, db::Db>,
{
query.fetch_one(self).await
}
pub async fn fetch_all<'q, F, O, A>(
self,
query: sqlx::query::Map<'q, sqlx::Postgres, F, A>,
) -> Result<Vec<O>, sqlx::Error>
where
F: FnMut(sqlx::postgres::PgRow) -> Result<O, sqlx::Error> + Send,
O: Send + Unpin,
A: 'q + Send + sqlx::IntoArguments<'q, sqlx::Postgres>,
{
query.fetch_all(self).await
}
pub async fn fetch_optional<'q, F, O, A>(
self,
query: sqlx::query::Map<'q, sqlx::Postgres, F, A>,
) -> Result<Option<O>, sqlx::Error>
where
F: FnMut(sqlx::postgres::PgRow) -> Result<O, sqlx::Error> + Send,
O: Send + Unpin,
A: 'q + Send + sqlx::IntoArguments<'q, sqlx::Postgres>,
{
query.fetch_optional(self).await
}
}
fn annotated_sql<'q>(query: &impl Execute<'q, db::Db>) -> Option<String> {
match sql_commenter::annotate_sql(query.sql()) {
Cow::Borrowed(_) => None,
Cow::Owned(sql) => Some(sql),
}
}
fn rebuild_annotated<'q>(
annotated: &str,
mut query: impl Execute<'q, db::Db>,
) -> Result<sqlx::query::Query<'_, db::Db, sqlx::postgres::PgArguments>, Error> {
let args = query
.take_arguments()
.map_err(Error::Encode)?
.unwrap_or_default();
Ok(sqlx::query_with::<db::Db, _>(annotated, args).persistent(false))
}
impl<'c, E> Executor<'c> for OneTimeExecutor<'c, E>
where
E: Executor<'c, Database = db::Db> + 'c,
{
type Database = db::Db;
fn fetch_many<'e, 'q: 'e, Q>(
self,
query: Q,
) -> BoxStream<'e, Result<sqlx::Either<<db::Db as Database>::QueryResult, db::Row>, Error>>
where
'c: 'e,
Q: 'q + Execute<'q, db::Db>,
{
let Some(annotated) = annotated_sql(&query) else {
return self.executor.fetch_many(query);
};
Box::pin(try_stream! {
let q = rebuild_annotated(annotated.as_str(), query)?;
let mut stream = self.executor.fetch_many(q);
while let Some(step) = stream.try_next().await? {
yield step;
}
})
}
fn fetch_optional<'e, 'q: 'e, Q>(
self,
query: Q,
) -> BoxFuture<'e, Result<Option<db::Row>, Error>>
where
'c: 'e,
Q: 'q + Execute<'q, db::Db>,
{
let Some(annotated) = annotated_sql(&query) else {
return self.executor.fetch_optional(query);
};
Box::pin(async move {
let q = rebuild_annotated(annotated.as_str(), query)?;
self.executor.fetch_optional(q).await
})
}
fn prepare_with<'e, 'q: 'e>(
self,
sql: &'q str,
parameters: &'e [<db::Db as Database>::TypeInfo],
) -> BoxFuture<'e, Result<<db::Db as Database>::Statement<'q>, Error>>
where
'c: 'e,
{
self.executor.prepare_with(sql, parameters)
}
fn describe<'e, 'q: 'e>(self, sql: &'q str) -> BoxFuture<'e, Result<Describe<db::Db>, Error>>
where
'c: 'e,
{
self.executor.describe(sql)
}
}
pub trait IntoOneTimeExecutor<'c>: IntoOneTimeExecutorAt<'c> + 'c {}
impl<'c, T> IntoOneTimeExecutor<'c> for T where T: IntoOneTimeExecutorAt<'c> + 'c {}
pub trait IntoOneTimeExecutorAt<'c> {
type Executor: sqlx::Executor<'c, Database = db::Db>;
fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
where
Self: 'c;
}
impl<'c, E> IntoOneTimeExecutorAt<'c> for OneTimeExecutor<'c, E>
where
E: sqlx::Executor<'c, Database = db::Db> + 'c,
{
type Executor = E;
fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
where
Self: 'c,
{
self
}
}
impl<'c> IntoOneTimeExecutorAt<'c> for &db::Pool {
type Executor = &'c db::Pool;
fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
where
Self: 'c,
{
OneTimeExecutor::new(self, None)
}
}
impl<'c, O> IntoOneTimeExecutorAt<'c> for &mut O
where
O: AtomicOperation,
{
type Executor = &'c mut db::Connection;
fn into_executor(self) -> OneTimeExecutor<'c, Self::Executor>
where
Self: 'c,
{
let now = self.maybe_now();
OneTimeExecutor::new(self.connection(), now)
}
}