use std::marker::PhantomData;
use sea_query::ExprTrait;
use crate::{
IntoSelect,
select::{Cached, Cacher, ColumnImpl, Prepared, Row, Select, SelectImpl},
value::{DynTypedExpr, EqTyp},
};
use super::{Expr, IntoExpr};
pub fn optional<'outer, S, R>(
f: impl for<'inner> FnOnce(&mut Optional<'outer, 'inner, S>) -> R,
) -> R {
let mut optional = Optional {
nulls: Vec::new(),
_p: PhantomData,
_p2: PhantomData,
};
f(&mut optional)
}
pub struct Optional<'outer, 'inner, S> {
nulls: Vec<DynTypedExpr>,
_p: PhantomData<fn(&'inner ()) -> &'inner &'outer ()>,
_p2: PhantomData<S>,
}
impl<'outer, 'inner, S> Optional<'outer, 'inner, S> {
#[doc(alias = "join")]
pub fn and<T: EqTyp>(
&mut self,
col: impl IntoExpr<'inner, S, Typ = Option<T>>,
) -> Expr<'inner, S, T> {
let column = col.into_expr();
self.nulls.push(DynTypedExpr::erase(column.is_none()));
Expr::adhoc(move |b| column.inner.build_expr(b))
}
pub fn is_none(&self) -> Expr<'outer, S, bool> {
let nulls = self.nulls.clone();
Expr::adhoc(move |b| {
nulls
.iter()
.map(|x| (x.func)(b))
.reduce(|a, b| a.or(b))
.unwrap_or(false.into())
})
}
pub fn is_some(&self) -> Expr<'outer, S, bool> {
self.is_none().not()
}
pub fn and_then<T: EqTyp>(
&self,
col: impl IntoExpr<'inner, S, Typ = Option<T>>,
) -> Expr<'outer, S, Option<T>> {
const NULL: sea_query::Expr = sea_query::Expr::Keyword(sea_query::Keyword::Null);
let col = col.into_expr().inner;
let is_none = self.is_none().inner;
Expr::adhoc(move |b| {
sea_query::Expr::case(is_none.build_expr(b), NULL)
.finally(col.build_expr(b))
.into()
})
}
pub fn then<T: EqTyp + 'outer>(
&self,
col: impl IntoExpr<'inner, S, Typ = T>,
) -> Expr<'outer, S, Option<T>> {
self.and_then(Some(col))
}
pub fn then_select<Out: 'static>(
&self,
d: impl IntoSelect<'inner, S, Out = Out>,
) -> Select<'outer, S, Option<Out>> {
Select::new(OptionalImpl {
inner: d.into_select().inner,
is_some: ColumnImpl {
expr: DynTypedExpr::erase(self.is_some()),
_p: PhantomData,
},
})
}
}
pub struct OptionalImpl<X> {
inner: X,
is_some: ColumnImpl<bool>,
}
impl<X: SelectImpl> SelectImpl for OptionalImpl<X> {
type Out = Option<X::Out>;
type Prepared = OptionalPrepared<X::Prepared>;
fn prepare(self, cacher: &mut Cacher) -> Self::Prepared {
OptionalPrepared {
is_some: self.is_some.prepare(cacher),
inner: self.inner.prepare(cacher),
}
}
}
pub struct OptionalPrepared<X> {
inner: X,
is_some: Cached<bool>,
}
impl<X: Prepared> Prepared for OptionalPrepared<X> {
type Out = Option<X::Out>;
fn call(&mut self, row: Row<'_>) -> Self::Out {
if row.get(self.is_some) {
Some(self.inner.call(row))
} else {
None
}
}
}