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}