use crate::{
db,
error::EntityHydrationError,
events::{EntityEvents, GenericEvent},
one_time_executor::IntoOneTimeExecutor,
traits::*,
tree_query::{TreeQuerySource, build_tree_query, partition_by_tag},
};
pub struct EsQuery<'q, Repo, Flavor, F, A>
where
Repo: EsRepo,
{
inner: sqlx::query::Map<'q, db::Db, F, A>,
source: TreeQuerySource<<<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId>,
_repo: std::marker::PhantomData<Repo>,
_flavor: std::marker::PhantomData<Flavor>,
}
pub struct EsQueryFlavorFlat;
pub struct EsQueryFlavorNested;
impl<'q, Repo, Flavor, F, A> EsQuery<'q, Repo, Flavor, F, A>
where
Repo: EsRepo,
<<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId: Unpin,
F: FnMut(
db::Row,
) -> Result<
GenericEvent<<<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId>,
sqlx::Error,
> + Send,
A: 'q + Send + sqlx::IntoArguments<'q, db::Db>,
{
pub fn new(
query: sqlx::query::Map<'q, db::Db, F, A>,
source: TreeQuerySource<
<<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId,
>,
) -> Self {
Self {
inner: query,
source,
_repo: std::marker::PhantomData,
_flavor: std::marker::PhantomData,
}
}
async fn fetch_optional_inner<E: From<sqlx::Error> + From<EntityHydrationError>>(
self,
op: impl IntoOneTimeExecutor<'_>,
) -> Result<Option<<Repo as EsRepo>::Entity>, E> {
let executor = op.into_executor();
let rows = executor.fetch_all(self.inner).await?;
if rows.is_empty() {
return Ok(None);
}
Ok(EntityEvents::load_first(rows.into_iter())?)
}
async fn fetch_n_inner<E: From<sqlx::Error> + From<EntityHydrationError>>(
self,
op: impl IntoOneTimeExecutor<'_>,
first: usize,
) -> Result<(Vec<<Repo as EsRepo>::Entity>, bool), E> {
let executor = op.into_executor();
let rows = executor.fetch_all(self.inner).await?;
Ok(EntityEvents::load_n(rows.into_iter(), first)?)
}
async fn fetch_tree_rows<E: From<sqlx::Error>>(
self,
op: impl IntoOneTimeExecutor<'_>,
include_deleted: bool,
) -> Result<
(
Vec<GenericEvent<<<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId>>,
std::collections::HashMap<i32, Vec<db::Row>>,
),
E,
> {
let executor = op.into_executor();
let spec = <Repo as EsRepo>::nested_tree_spec();
let sql = build_tree_query(
self.source.user_sql,
self.source.order_by_cols,
&spec,
include_deleted,
self.source.n_user_args + 1,
);
let mut inner = self.inner;
let args = sqlx::Execute::take_arguments(&mut inner)
.map_err(sqlx::Error::Encode)?
.unwrap_or_default();
let rows: Vec<db::Row> = sqlx::query_with::<db::Db, _>(&sql, args)
.fetch_all(executor)
.await?;
let mut by_tag = partition_by_tag(rows)?;
let root_rows = by_tag.remove(&0).unwrap_or_default();
let root = root_rows
.iter()
.map(self.source.decode)
.collect::<Result<Vec<_>, sqlx::Error>>()?;
Ok((root, by_tag))
}
}
impl<'q, Repo, F, A> EsQuery<'q, Repo, EsQueryFlavorFlat, F, A>
where
Repo: EsRepo,
<<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId: Unpin,
F: FnMut(
db::Row,
) -> Result<
GenericEvent<<<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId>,
sqlx::Error,
> + Send,
A: 'q + Send + sqlx::IntoArguments<'q, db::Db>,
{
pub async fn fetch_optional(
self,
op: impl IntoOneTimeExecutor<'_>,
) -> Result<Option<<Repo as EsRepo>::Entity>, <Repo as EsRepo>::QueryError> {
self.fetch_optional_inner(op).await
}
pub async fn fetch_n(
self,
op: impl IntoOneTimeExecutor<'_>,
first: usize,
) -> Result<(Vec<<Repo as EsRepo>::Entity>, bool), <Repo as EsRepo>::QueryError> {
self.fetch_n_inner(op, first).await
}
}
impl<'q, Repo, F, A> EsQuery<'q, Repo, EsQueryFlavorNested, F, A>
where
Repo: EsRepo,
<<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId: Unpin,
F: FnMut(
db::Row,
) -> Result<
GenericEvent<<<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId>,
sqlx::Error,
> + Send,
A: 'q + Send + sqlx::IntoArguments<'q, db::Db>,
{
pub async fn fetch_optional(
self,
op: impl IntoOneTimeExecutor<'_>,
) -> Result<Option<<Repo as EsRepo>::Entity>, <Repo as EsRepo>::QueryError> {
self.fetch_optional_tree(op, false).await
}
pub async fn fetch_n(
self,
op: impl IntoOneTimeExecutor<'_>,
first: usize,
) -> Result<(Vec<<Repo as EsRepo>::Entity>, bool), <Repo as EsRepo>::QueryError> {
self.fetch_n_tree(op, first, false).await
}
pub async fn fetch_optional_include_deleted(
self,
op: impl IntoOneTimeExecutor<'_>,
) -> Result<Option<<Repo as EsRepo>::Entity>, <Repo as EsRepo>::QueryError> {
self.fetch_optional_tree(op, true).await
}
pub async fn fetch_n_include_deleted(
self,
op: impl IntoOneTimeExecutor<'_>,
first: usize,
) -> Result<(Vec<<Repo as EsRepo>::Entity>, bool), <Repo as EsRepo>::QueryError> {
self.fetch_n_tree(op, first, true).await
}
async fn fetch_optional_tree(
self,
op: impl IntoOneTimeExecutor<'_>,
include_deleted: bool,
) -> Result<Option<<Repo as EsRepo>::Entity>, <Repo as EsRepo>::QueryError> {
let (root, mut by_tag) = self
.fetch_tree_rows::<<Repo as EsRepo>::QueryError>(op, include_deleted)
.await?;
let Some(entity) = EntityEvents::load_first::<<Repo as EsRepo>::Entity>(root)? else {
return Ok(None);
};
let mut entities = [entity];
let mut cursor = 1i32;
<Repo as EsRepo>::hydrate_nested_from_rows::<<Repo as EsRepo>::QueryError>(
&mut by_tag,
&mut cursor,
&mut entities,
)?;
let [entity] = entities;
Ok(Some(entity))
}
async fn fetch_n_tree(
self,
op: impl IntoOneTimeExecutor<'_>,
first: usize,
include_deleted: bool,
) -> Result<(Vec<<Repo as EsRepo>::Entity>, bool), <Repo as EsRepo>::QueryError> {
let (root, mut by_tag) = self
.fetch_tree_rows::<<Repo as EsRepo>::QueryError>(op, include_deleted)
.await?;
let (mut entities, more) = EntityEvents::load_n::<<Repo as EsRepo>::Entity>(root, first)?;
let mut cursor = 1i32;
<Repo as EsRepo>::hydrate_nested_from_rows::<<Repo as EsRepo>::QueryError>(
&mut by_tag,
&mut cursor,
&mut entities,
)?;
Ok((entities, more))
}
}