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}