Skip to main content

rust_query/value/
optional.rs

1use std::marker::PhantomData;
2
3use sea_query::ExprTrait;
4
5use crate::{
6    IntoSelect,
7    select::{Cached, Cacher, ColumnImpl, Prepared, Row, Select, SelectImpl},
8    value::{DynTypedExpr, EqTyp},
9};
10
11use super::{Expr, IntoExpr};
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 row
65    /// will become [None], from that moment forward.
66    ///
67    /// ```
68    /// # use rust_query::{private::doctest::*, optional};
69    /// # get_txn(|txn| {
70    /// let (total1, total2) = txn.query_one(optional(|row| {
71    ///     let val1 = row.and(Some(100));
72    ///     let val2 = row.and(Some(20));
73    ///     let total = val1.add(val2);
74    ///     let total1 = row.then(&total);
75    ///
76    ///     let _ = row.and(None::<i64>);
77    ///     let total2 = row.then(total);
78    ///
79    ///      (total1, total2)
80    /// }));
81    /// assert_eq!(total1, Some(120));
82    /// assert_eq!(total2, None);
83    /// # });
84    /// ```
85    #[doc(alias = "join")]
86    pub fn and<T: EqTyp>(
87        &mut self,
88        col: impl IntoExpr<'inner, S, Typ = Option<T>>,
89    ) -> Expr<'inner, S, T> {
90        let column = col.into_expr();
91        self.nulls.push(DynTypedExpr::erase(column.is_none()));
92        // `Expr::adhoc` is used here to reset `maybe_optional` to `true`.
93        Expr::adhoc(move |b| column.inner.build_expr(b))
94    }
95
96    /// Return a [bool] column indicating whether the current row does not exists.
97    ///
98    /// ```
99    /// # use rust_query::{private::doctest::*, optional};
100    /// # get_txn(|txn| {
101    /// let (none1, none2) = txn.query_one(optional(|row| {
102    ///     let none1 = row.is_none();
103    ///     let _ = row.and(None::<i64>);
104    ///     let none2 = row.is_none();
105    ///     (none1, none2)
106    /// }));
107    /// assert_eq!(none1, false);
108    /// assert_eq!(none2, true);
109    /// # });
110    /// ```
111    pub fn is_none(&self) -> Expr<'outer, S, bool> {
112        let nulls = self.nulls.clone();
113        Expr::adhoc(move |b| {
114            nulls
115                .iter()
116                .map(|x| (x.func)(b))
117                .reduce(|a, b| a.or(b))
118                .unwrap_or(false.into())
119        })
120    }
121
122    /// Return a [bool] column indicating whether the current row exists.
123    ///
124    /// ```
125    /// # use rust_query::{private::doctest::*, optional};
126    /// # get_txn(|txn| {
127    /// let (some1, some2) = txn.query_one(optional(|row| {
128    ///     let some1 = row.is_some();
129    ///     let _ = row.and(None::<i64>);
130    ///     let some2 = row.is_some();
131    ///     (some1, some2)
132    /// }));
133    /// assert_eq!(some1, true);
134    /// assert_eq!(some2, false);
135    /// # });
136    /// ```
137    pub fn is_some(&self) -> Expr<'outer, S, bool> {
138        self.is_none().not()
139    }
140
141    /// This is much like combining [Self::and] with [Self::then], but it
142    /// allows returning an optional value without mutating the [Optional] row.
143    ///
144    ///  ```
145    /// # use rust_query::{private::doctest::*, optional};
146    /// # get_txn(|txn| {
147    /// let (val1, val2) = txn.query_one(optional(|row| {
148    ///     let val1 = row.and_then(None::<i64>);
149    ///     let val2 = row.and_then(Some(1));
150    ///     (val1, val2)
151    /// }));
152    /// assert_eq!(val1, None);
153    /// assert_eq!(val2, Some(1));
154    /// # });
155    /// ```
156    pub fn and_then<T: EqTyp>(
157        &self,
158        col: impl IntoExpr<'inner, S, Typ = Option<T>>,
159    ) -> Expr<'outer, S, Option<T>> {
160        const NULL: sea_query::Expr = sea_query::Expr::Keyword(sea_query::Keyword::Null);
161
162        let col = col.into_expr().inner;
163        let is_none = self.is_none().inner;
164        Expr::adhoc(move |b| {
165            sea_query::Expr::case(is_none.build_expr(b), NULL)
166                .finally(col.build_expr(b))
167                .into()
168        })
169    }
170
171    /// Return [Some] column if the current row exists and [None] column otherwise.
172    ///  ```
173    /// # use rust_query::{private::doctest::*, optional};
174    /// # get_txn(|txn| {
175    /// let (val1, val2) = txn.query_one(optional(|row| {
176    ///     let val1 = row.then(1);
177    ///     let _ = row.and(None::<i64>);
178    ///     let val2 = row.then(1);
179    ///     (val1, val2)
180    /// }));
181    /// assert_eq!(val1, Some(1));
182    /// assert_eq!(val2, None);
183    /// # });
184    /// ```
185    pub fn then<T: EqTyp + 'outer>(
186        &self,
187        col: impl IntoExpr<'inner, S, Typ = T>,
188    ) -> Expr<'outer, S, Option<T>> {
189        self.and_then(Some(col))
190    }
191
192    /// Returns a [Select] with optional result. Useful for returning multiple values
193    /// in a single [Option].
194    ///
195    ///  ```
196    /// # use rust_query::{private::doctest::*, optional};
197    /// # get_txn(|txn| {
198    /// let pair = txn.query_one(optional(|row| {
199    ///     let val1 = row.and(Some(1));
200    ///     let val2 = row.and(Some(2));
201    ///     row.then_select((val1, val2))
202    /// }));
203    /// assert_eq!(pair, Some((1, 2)));
204    /// # });
205    /// ```
206    pub fn then_select<Out: 'static>(
207        &self,
208        d: impl IntoSelect<'inner, S, Out = Out>,
209    ) -> Select<'outer, S, Option<Out>> {
210        Select::new(OptionalImpl {
211            inner: d.into_select().inner,
212            is_some: ColumnImpl {
213                expr: DynTypedExpr::erase(self.is_some()),
214                _p: PhantomData,
215            },
216        })
217    }
218}
219
220pub struct OptionalImpl<X> {
221    inner: X,
222    is_some: ColumnImpl<bool>,
223}
224
225impl<X: SelectImpl> SelectImpl for OptionalImpl<X> {
226    type Out = Option<X::Out>;
227    type Prepared = OptionalPrepared<X::Prepared>;
228
229    fn prepare(self, cacher: &mut Cacher) -> Self::Prepared {
230        OptionalPrepared {
231            is_some: self.is_some.prepare(cacher),
232            inner: self.inner.prepare(cacher),
233        }
234    }
235}
236
237pub struct OptionalPrepared<X> {
238    inner: X,
239    is_some: Cached<bool>,
240}
241
242impl<X: Prepared> Prepared for OptionalPrepared<X> {
243    type Out = Option<X::Out>;
244
245    fn call(&mut self, row: Row<'_>) -> Self::Out {
246        if row.get(self.is_some) {
247            Some(self.inner.call(row))
248        } else {
249            None
250        }
251    }
252}