use std::{marker::PhantomData, rc::Rc};
use crate::{
IntoSelect,
lower::{self, CONST_NULL},
select::{Cached, Cacher, ColumnImpl, Prepared, Row, Select, SelectImpl},
value::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<Rc<lower::Expr>>,
_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(column.is_none().inner);
Expr::new(column.inner)
}
pub fn is_none(&self) -> Expr<'outer, S, bool> {
let nulls = self.nulls.clone();
Expr::new(
nulls
.into_iter()
.reduce(|a, b| Rc::new(lower::Expr::Infix(a, "OR", b)))
.unwrap_or(Rc::new(lower::CONST_FALSE)),
)
}
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>> {
let col = col.into_expr().inner;
let is_none = self.is_none().inner;
Expr::adhoc(lower::Expr::Func(
"iif",
Box::new([is_none, Rc::new(CONST_NULL), col]),
))
}
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: self.is_some().inner,
_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
}
}
}