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};
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/// # rust_query::private::doctest::get_txn(|txn| {
17/// # use rust_query::optional;
18/// let res = txn.query_one(optional(|row| {
19///     let x = row.and(Some("test"));
20///     let y = row.and(Some(42));
21///     row.then((x, y))
22/// }));
23/// assert_eq!(res, Some(("test".to_owned(), 42)));
24/// # });
25/// ```
26///
27/// ```
28/// # use rust_query::IntoExpr;
29/// # rust_query::private::doctest::get_txn(|txn| {
30/// # use rust_query::optional;
31/// let res = txn.query_one(optional(|row| {
32///     let x = row.and(Some("test"));
33///     let y = row.and(None::<i64>);
34///     row.then((x, y))
35/// }));
36/// assert_eq!(res, None);
37/// # });
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: MyTyp>(
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::Expr = sea_query::Expr::Keyword(sea_query::Keyword::Null);
96
97        let col = col.into_expr().inner;
98        let is_none = self.is_none().inner;
99        Expr::adhoc(move |b| {
100            sea_query::Expr::case(is_none.build_expr(b), NULL)
101                .finally(col.build_expr(b))
102                .into()
103        })
104    }
105
106    /// Returns a [Select] with optional result.
107    pub fn then<Out: 'static>(
108        &self,
109        d: impl IntoSelect<'inner, S, Out = Out>,
110    ) -> Select<'outer, S, Option<Out>> {
111        Select::new(OptionalImpl {
112            inner: d.into_select().inner,
113            is_some: ColumnImpl {
114                expr: self.is_some().into_expr().inner,
115            },
116        })
117    }
118}
119
120pub struct OptionalImpl<X> {
121    inner: X,
122    is_some: ColumnImpl<bool>,
123}
124
125impl<X: SelectImpl> SelectImpl for OptionalImpl<X> {
126    type Out = Option<X::Out>;
127    type Prepared = OptionalPrepared<X::Prepared>;
128
129    fn prepare(self, cacher: &mut Cacher) -> Self::Prepared {
130        OptionalPrepared {
131            is_some: self.is_some.prepare(cacher),
132            inner: self.inner.prepare(cacher),
133        }
134    }
135}
136
137pub struct OptionalPrepared<X> {
138    inner: X,
139    is_some: Cached<bool>,
140}
141
142impl<X: Prepared> Prepared for OptionalPrepared<X> {
143    type Out = Option<X::Out>;
144
145    fn call(&mut self, row: Row<'_>) -> Self::Out {
146        if row.get(self.is_some) {
147            Some(self.inner.call(row))
148        } else {
149            None
150        }
151    }
152}