rust_query/value/
optional.rs

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