rust_query/value/
optional.rs

1use std::marker::PhantomData;
2
3use sea_query::{ExprTrait, Nullable};
4
5use crate::{
6    IntoSelect,
7    dummy_impl::{Cached, Cacher, ColumnImpl, Prepared, Row, Select, SelectImpl},
8    value::DynTypedExpr,
9};
10
11use super::{Expr, IntoExpr, MyTyp};
12
13/// This is a combinator function that allows constructing single row optional queries.
14///
15/// ```
16/// # use rust_query::IntoExpr;
17/// # rust_query::private::doctest::get_txn(|txn| {
18/// # use rust_query::optional;
19/// let res = txn.query_one(optional(|row| {
20///     let x = row.and(Some("test"));
21///     let y = row.and(Some(42));
22///     row.then((x, y))
23/// }));
24/// assert_eq!(res, Some(("test".to_owned(), 42)));
25/// # });
26/// ```
27///
28/// ```
29/// # use rust_query::IntoExpr;
30/// # rust_query::private::doctest::get_txn(|txn| {
31/// # use rust_query::optional;
32/// let res = txn.query_one(optional(|row| {
33///     let x = row.and(Some("test"));
34///     let y = row.and(None::<i64>);
35///     row.then((x, y))
36/// }));
37/// assert_eq!(res, None);
38/// # });
39/// ```
40pub fn optional<'outer, S, R>(
41    f: impl for<'inner> FnOnce(&mut Optional<'outer, 'inner, S>) -> R,
42) -> R {
43    let mut optional = Optional {
44        nulls: Vec::new(),
45        _p: PhantomData,
46        _p2: PhantomData,
47    };
48    f(&mut optional)
49}
50
51/// This is the argument type used by the [optional] combinator.
52///
53/// Joining more optional columns can be done with the [Optional::and] method.
54/// Finally it is possible to return selections or expressions using [Optional::then] and [Optional::then_expr].
55pub struct Optional<'outer, 'inner, S> {
56    nulls: Vec<DynTypedExpr>,
57    _p: PhantomData<fn(&'inner ()) -> &'inner &'outer ()>,
58    _p2: PhantomData<S>,
59}
60
61impl<'outer, 'inner, S> Optional<'outer, 'inner, S> {
62    /// Join an optional column to the current row.
63    ///
64    /// If the joined column is [None], then the whole [optional] combinator will return [None].
65    #[doc(alias = "join")]
66    pub fn and<T: MyTyp>(
67        &mut self,
68        col: impl IntoExpr<'inner, S, Typ = Option<T>>,
69    ) -> Expr<'inner, S, T> {
70        let column = col.into_expr();
71        self.nulls.push(DynTypedExpr::erase(column.is_none()));
72        // `Expr::adhoc` is used here to reset `maybe_optional` to `true`.
73        Expr::adhoc(move |b| column.inner.build_expr(b))
74    }
75
76    pub fn is_none(&self) -> Expr<'outer, S, bool> {
77        let nulls = self.nulls.clone();
78        Expr::adhoc(move |b| {
79            nulls
80                .iter()
81                .map(|x| (x.func)(b))
82                .reduce(|a, b| a.or(b))
83                .unwrap_or(false.into())
84        })
85    }
86
87    /// Return a [bool] column indicating whether the current row exists.
88    pub fn is_some(&self) -> Expr<'outer, S, bool> {
89        self.is_none().not()
90    }
91
92    /// Return [Some] column if the current row exists and [None] column otherwise.
93    pub fn then_expr<T: MyTyp<Sql: Nullable> + 'outer>(
94        &self,
95        col: impl IntoExpr<'inner, S, Typ = T>,
96    ) -> Expr<'outer, S, Option<T>> {
97        const NULL: sea_query::Expr = sea_query::Expr::Keyword(sea_query::Keyword::Null);
98
99        let col = col.into_expr().inner;
100        let is_none = self.is_none().inner;
101        Expr::adhoc(move |b| {
102            sea_query::Expr::case(is_none.build_expr(b), NULL)
103                .finally(col.build_expr(b))
104                .into()
105        })
106    }
107
108    /// Returns a [Select] with optional result.
109    pub fn then<Out: 'static>(
110        &self,
111        d: impl IntoSelect<'inner, S, Out = Out>,
112    ) -> Select<'outer, S, Option<Out>> {
113        Select::new(OptionalImpl {
114            inner: d.into_select().inner,
115            is_some: ColumnImpl {
116                expr: DynTypedExpr::erase(self.is_some()),
117                _p: PhantomData,
118            },
119        })
120    }
121}
122
123pub struct OptionalImpl<X> {
124    inner: X,
125    is_some: ColumnImpl<bool>,
126}
127
128impl<X: SelectImpl> SelectImpl for OptionalImpl<X> {
129    type Out = Option<X::Out>;
130    type Prepared = OptionalPrepared<X::Prepared>;
131
132    fn prepare(self, cacher: &mut Cacher) -> Self::Prepared {
133        OptionalPrepared {
134            is_some: self.is_some.prepare(cacher),
135            inner: self.inner.prepare(cacher),
136        }
137    }
138}
139
140pub struct OptionalPrepared<X> {
141    inner: X,
142    is_some: Cached<bool>,
143}
144
145impl<X: Prepared> Prepared for OptionalPrepared<X> {
146    type Out = Option<X::Out>;
147
148    fn call(&mut self, row: Row<'_>) -> Self::Out {
149        if row.get(self.is_some) {
150            Some(self.inner.call(row))
151        } else {
152            None
153        }
154    }
155}