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<&'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_some().not().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 nulls = self.nulls.clone();
100        Expr::adhoc(move |b| {
101            nulls.iter().fold(col.build_expr(b), |accum, e| {
102                sea_query::Expr::case(e.build_expr(b), NULL)
103                    .finally(accum)
104                    .into()
105            })
106        })
107    }
108
109    /// Returns a [Select] with optional result.
110    pub fn then<'transaction, Out: 'transaction>(
111        &self,
112        d: impl IntoSelect<'inner, 'transaction, S, Out = Out>,
113    ) -> Select<'outer, 'transaction, S, Option<Out>> {
114        Select::new(OptionalImpl {
115            inner: d.into_select().inner,
116            is_some: ColumnImpl {
117                expr: self.is_some().into_expr().inner,
118            },
119        })
120    }
121}
122
123pub struct OptionalImpl<X> {
124    inner: X,
125    is_some: ColumnImpl<bool>,
126}
127
128impl<'transaction, X: SelectImpl<'transaction>> SelectImpl<'transaction> 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}