Skip to main content

rust_query/value/
optional.rs

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