rust_query/value/
optional.rs

1use std::marker::PhantomData;
2
3use sea_query::{ExprTrait, Nullable};
4
5use crate::{
6    IntoSelect,
7    select::{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_select((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_select((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 expressions or selections using [Optional::then] and [Optional::then_select].
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    /// Return a [bool] column indicating whether the current row does not exists.
77    pub fn is_none(&self) -> Expr<'outer, S, bool> {
78        let nulls = self.nulls.clone();
79        Expr::adhoc(move |b| {
80            nulls
81                .iter()
82                .map(|x| (x.func)(b))
83                .reduce(|a, b| a.or(b))
84                .unwrap_or(false.into())
85        })
86    }
87
88    /// Return a [bool] column indicating whether the current row exists.
89    pub fn is_some(&self) -> Expr<'outer, S, bool> {
90        self.is_none().not()
91    }
92
93    /// This is much like combining [Self::and] with [Self::then], but it
94    /// allows returning an optional value without mutating self.
95    pub fn and_then<T: MyTyp<Sql: Nullable>>(
96        &self,
97        col: impl IntoExpr<'inner, S, Typ = Option<T>>,
98    ) -> Expr<'outer, S, Option<T>> {
99        const NULL: sea_query::Expr = sea_query::Expr::Keyword(sea_query::Keyword::Null);
100
101        let col = col.into_expr().inner;
102        let is_none = self.is_none().inner;
103        Expr::adhoc(move |b| {
104            sea_query::Expr::case(is_none.build_expr(b), NULL)
105                .finally(col.build_expr(b))
106                .into()
107        })
108    }
109
110    /// Return [Some] column if the current row exists and [None] column otherwise.
111    pub fn then<T: MyTyp<Sql: Nullable> + 'outer>(
112        &self,
113        col: impl IntoExpr<'inner, S, Typ = T>,
114    ) -> Expr<'outer, S, Option<T>> {
115        self.and_then(Some(col))
116    }
117
118    /// Returns a [Select] with optional result.
119    pub fn then_select<Out: 'static>(
120        &self,
121        d: impl IntoSelect<'inner, S, Out = Out>,
122    ) -> Select<'outer, S, Option<Out>> {
123        Select::new(OptionalImpl {
124            inner: d.into_select().inner,
125            is_some: ColumnImpl {
126                expr: DynTypedExpr::erase(self.is_some()),
127                _p: PhantomData,
128            },
129        })
130    }
131}
132
133pub struct OptionalImpl<X> {
134    inner: X,
135    is_some: ColumnImpl<bool>,
136}
137
138impl<X: SelectImpl> SelectImpl for OptionalImpl<X> {
139    type Out = Option<X::Out>;
140    type Prepared = OptionalPrepared<X::Prepared>;
141
142    fn prepare(self, cacher: &mut Cacher) -> Self::Prepared {
143        OptionalPrepared {
144            is_some: self.is_some.prepare(cacher),
145            inner: self.inner.prepare(cacher),
146        }
147    }
148}
149
150pub struct OptionalPrepared<X> {
151    inner: X,
152    is_some: Cached<bool>,
153}
154
155impl<X: Prepared> Prepared for OptionalPrepared<X> {
156    type Out = Option<X::Out>;
157
158    fn call(&mut self, row: Row<'_>) -> Self::Out {
159        if row.get(self.is_some) {
160            Some(self.inner.call(row))
161        } else {
162            None
163        }
164    }
165}