Skip to main content

rust_query/value/
operations.rs

1use sea_query::{Alias, ExprTrait, extension::sqlite::SqliteExpr};
2
3use crate::{
4    ast::CONST_0,
5    value::{BuffTyp, MyTyp},
6};
7
8use super::{EqTyp, Expr, IntoExpr, NumTyp};
9
10impl<'column, S, T: NumTyp> Expr<'column, S, T> {
11    /// Add two expressions together.
12    ///
13    /// ```
14    /// # use rust_query::IntoExpr;
15    /// # rust_query::private::doctest::get_txn(|txn| {
16    /// assert_eq!(txn.query_one(1.into_expr().add(2)), 3);
17    /// assert_eq!(txn.query_one(1.0.into_expr().add(2.0)), 3.0);
18    /// # });
19    /// ```
20    pub fn add(&self, rhs: impl IntoExpr<'column, S, Typ = T>) -> Self {
21        let lhs = self.inner.clone();
22        let rhs = rhs.into_expr().inner;
23        Expr::adhoc(move |b| lhs.build_expr(b).add(rhs.build_expr(b)))
24    }
25
26    /// Subtract one expression from another.
27    ///
28    /// ```
29    /// # use rust_query::IntoExpr;
30    /// # rust_query::private::doctest::get_txn(|txn| {
31    /// assert_eq!(txn.query_one(1.into_expr().sub(2)), -1);
32    /// assert_eq!(txn.query_one(1.0.into_expr().sub(2.0)), -1.0);
33    /// # });
34    /// ```
35    pub fn sub(&self, rhs: impl IntoExpr<'column, S, Typ = T>) -> Self {
36        let lhs = self.inner.clone();
37        let rhs = rhs.into_expr().inner;
38        Expr::adhoc(move |b| lhs.build_expr(b).sub(rhs.build_expr(b)))
39    }
40
41    /// Multiply two expressions together.
42    ///
43    /// ```
44    /// # use rust_query::IntoExpr;
45    /// # rust_query::private::doctest::get_txn(|txn| {
46    /// assert_eq!(txn.query_one(2.into_expr().mul(3)), 6);
47    /// assert_eq!(txn.query_one(2.0.into_expr().mul(3.0)), 6.0);
48    /// # });
49    /// ```
50    pub fn mul(&self, rhs: impl IntoExpr<'column, S, Typ = T>) -> Self {
51        let lhs = self.inner.clone();
52        let rhs = rhs.into_expr().inner;
53        Expr::adhoc(move |b| lhs.build_expr(b).mul(rhs.build_expr(b)))
54    }
55
56    /// Divide one expression by another.
57    ///
58    /// For integers, the result is truncated towards zero.
59    /// See also [Expr::modulo].
60    ///
61    /// ```
62    /// # use rust_query::IntoExpr;
63    /// # rust_query::private::doctest::get_txn(|txn| {
64    /// assert_eq!(txn.query_one(5.into_expr().div(3)), 1);
65    /// assert_eq!(txn.query_one((-5).into_expr().div(3)), -1);
66    /// assert_eq!(txn.query_one(1.0.into_expr().div(2.0)), 0.5);
67    /// # });
68    /// ```
69    pub fn div(&self, rhs: impl IntoExpr<'column, S, Typ = T>) -> Self {
70        let lhs = self.inner.clone();
71        let rhs = rhs.into_expr().inner;
72        Expr::adhoc(move |b| lhs.build_expr(b).div(rhs.build_expr(b)))
73    }
74
75    /// Compute the less than operator (<) of two expressions.
76    ///
77    /// ```
78    /// # use rust_query::IntoExpr;
79    /// # rust_query::private::doctest::get_txn(|txn| {
80    /// assert_eq!(txn.query_one(2.into_expr().lt(3)), true);
81    /// assert_eq!(txn.query_one(1.into_expr().lt(1)), false);
82    /// assert_eq!(txn.query_one(3.0.into_expr().lt(1.0)), false);
83    /// # });
84    /// ```
85    pub fn lt(&self, rhs: impl IntoExpr<'column, S, Typ = T>) -> Expr<'column, S, bool> {
86        let lhs = self.inner.clone();
87        let rhs = rhs.into_expr().inner;
88        Expr::adhoc(move |b| lhs.build_expr(b).lt(rhs.build_expr(b)))
89    }
90
91    /// Compute the less than or equal operator (<=) of two expressions.
92    ///
93    /// ```
94    /// # use rust_query::IntoExpr;
95    /// # rust_query::private::doctest::get_txn(|txn| {
96    /// assert_eq!(txn.query_one(2.into_expr().lte(2)), true);
97    /// assert_eq!(txn.query_one(3.0.into_expr().lte(1.0)), false);
98    /// # });
99    /// ```
100    pub fn lte(&self, rhs: impl IntoExpr<'column, S, Typ = T>) -> Expr<'column, S, bool> {
101        let lhs = self.inner.clone();
102        let rhs = rhs.into_expr().inner;
103        Expr::adhoc(move |b| lhs.build_expr(b).lte(rhs.build_expr(b)))
104    }
105
106    /// Compute the greater than operator (>) of two expressions.
107    ///
108    /// ```
109    /// # use rust_query::IntoExpr;
110    /// # rust_query::private::doctest::get_txn(|txn| {
111    /// assert_eq!(txn.query_one(2.into_expr().gt(2)), false);
112    /// assert_eq!(txn.query_one(3.0.into_expr().gt(1.0)), true);
113    /// # });
114    /// ```
115    pub fn gt(&self, rhs: impl IntoExpr<'column, S, Typ = T>) -> Expr<'column, S, bool> {
116        let lhs = self.inner.clone();
117        let rhs = rhs.into_expr().inner;
118        Expr::adhoc(move |b| lhs.build_expr(b).gt(rhs.build_expr(b)))
119    }
120
121    /// Compute the greater than or equal (>=) operator of two expressions.
122    ///
123    /// ```
124    /// # use rust_query::IntoExpr;
125    /// # rust_query::private::doctest::get_txn(|txn| {
126    /// assert_eq!(txn.query_one(2.into_expr().gte(3)), false);
127    /// assert_eq!(txn.query_one(3.0.into_expr().gte(3.0)), true);
128    /// # });
129    /// ```
130    pub fn gte(&self, rhs: impl IntoExpr<'column, S, Typ = T>) -> Expr<'column, S, bool> {
131        let lhs = self.inner.clone();
132        let rhs = rhs.into_expr().inner;
133        Expr::adhoc(move |b| lhs.build_expr(b).gte(rhs.build_expr(b)))
134    }
135
136    /// Get the maximum of two values.
137    ///
138    /// ```
139    /// # use rust_query::IntoExpr;
140    /// # rust_query::private::doctest::get_txn(|txn| {
141    /// assert_eq!(txn.query_one(2.into_expr().max(3)), 3);
142    /// assert_eq!(txn.query_one(5.0.into_expr().max(3.0)), 5.0);
143    /// # });
144    /// ```
145    pub fn max(&self, rhs: impl IntoExpr<'column, S, Typ = T>) -> Self {
146        let lhs = self.inner.clone();
147        let rhs = rhs.into_expr().inner;
148        Expr::adhoc(move |b| {
149            sea_query::Expr::expr(
150                sea_query::Func::cust("max")
151                    .arg(lhs.build_expr(b))
152                    .arg(rhs.build_expr(b)),
153            )
154        })
155    }
156
157    /// Get the minimum of two values.
158    ///
159    /// ```
160    /// # use rust_query::IntoExpr;
161    /// # rust_query::private::doctest::get_txn(|txn| {
162    /// assert_eq!(txn.query_one(2.into_expr().min(3)), 2);
163    /// assert_eq!(txn.query_one(5.0.into_expr().min(3.0)), 3.0);
164    /// # });
165    /// ```
166    pub fn min(&self, rhs: impl IntoExpr<'column, S, Typ = T>) -> Self {
167        let lhs = self.inner.clone();
168        let rhs = rhs.into_expr().inner;
169        Expr::adhoc(move |b| {
170            sea_query::Expr::expr(
171                sea_query::Func::cust("min")
172                    .arg(lhs.build_expr(b))
173                    .arg(rhs.build_expr(b)),
174            )
175        })
176    }
177
178    /// Get the sign of the expression.
179    ///
180    /// The result is -1, 0 or 1 depending on if the expression is
181    /// negative, zero or positive.
182    ///
183    /// ```
184    /// # use rust_query::IntoExpr;
185    /// # rust_query::private::doctest::get_txn(|txn| {
186    /// assert_eq!(txn.query_one(2.into_expr().sign()), 1);
187    /// assert_eq!(txn.query_one((-5.0).into_expr().sign()), -1);
188    /// assert_eq!(txn.query_one((-0.0).into_expr().sign()), 0);
189    /// # });
190    /// ```
191    pub fn sign(&self) -> Expr<'column, S, i64> {
192        let lhs = self.inner.clone();
193        Expr::adhoc(move |b| {
194            sea_query::Expr::expr(sea_query::Func::cust("sign").arg(lhs.build_expr(b)))
195        })
196    }
197
198    /// Get the absolute value of the expression.
199    ///
200    /// ```
201    /// # use rust_query::IntoExpr;
202    /// # rust_query::private::doctest::get_txn(|txn| {
203    /// assert_eq!(txn.query_one(2.into_expr().abs()), 2);
204    /// assert_eq!(txn.query_one((-5.0).into_expr().abs()), 5.0);
205    /// # });
206    /// ```
207    pub fn abs(&self) -> Self {
208        let lhs = self.inner.clone();
209        Expr::adhoc(move |b| {
210            sea_query::Expr::expr(sea_query::Func::cust("abs").arg(lhs.build_expr(b)))
211        })
212    }
213
214    /// Check if a value is between two other values.
215    ///
216    /// The range is inclusive on both sides.
217    ///
218    /// ```
219    /// # use rust_query::IntoExpr;
220    /// # rust_query::private::doctest::get_txn(|txn| {
221    /// assert_eq!(txn.query_one(2.into_expr().between(2, 3)), true);
222    /// assert_eq!(txn.query_one(3.into_expr().between(2, 3)), true);
223    /// assert_eq!(txn.query_one(5.into_expr().between(2, 3)), false);
224    /// assert_eq!(txn.query_one(1.into_expr().between(2, 3)), false);
225    /// # });
226    /// ```
227    pub fn between(
228        &self,
229        low: impl IntoExpr<'column, S, Typ = T>,
230        high: impl IntoExpr<'column, S, Typ = T>,
231    ) -> Expr<'column, S, bool> {
232        let lhs = self.inner.clone();
233        let low = low.into_expr().inner;
234        let high = high.into_expr().inner;
235        Expr::adhoc(move |b| {
236            lhs.build_expr(b)
237                .between(low.build_expr(b), high.build_expr(b))
238        })
239    }
240}
241
242impl<'column, S, T: EqTyp + 'static> Expr<'column, S, T> {
243    /// Check whether two expressions are equal.
244    ///
245    /// ```
246    /// # use rust_query::IntoExpr;
247    /// # rust_query::private::doctest::get_txn(|txn| {
248    /// assert_eq!(txn.query_one(2.into_expr().eq(2)), true);
249    /// assert_eq!(txn.query_one(3.0.into_expr().eq(3.0)), true);
250    /// assert_eq!(txn.query_one("test".into_expr().eq("test")), true);
251    /// assert_eq!(txn.query_one(b"test".into_expr().eq(b"test" as &[u8])), true);
252    /// assert_eq!(txn.query_one(false.into_expr().eq(false)), true);
253    ///
254    /// assert_eq!(txn.query_one(1.into_expr().eq(2)), false);
255    /// # });
256    /// ```
257    pub fn eq(&self, rhs: impl IntoExpr<'column, S, Typ = T>) -> Expr<'column, S, bool> {
258        let lhs = self.inner.clone();
259        let rhs = rhs.into_expr().inner;
260        Expr::adhoc(move |b| lhs.build_expr(b).is(rhs.build_expr(b)))
261    }
262
263    /// Check whether two expressions are not equal.
264    ///
265    /// ```
266    /// # use rust_query::IntoExpr;
267    /// # rust_query::private::doctest::get_txn(|txn| {
268    /// assert_eq!(txn.query_one(2.into_expr().neq(2)), false);
269    /// assert_eq!(txn.query_one(3.0.into_expr().neq(3.1)), true);
270    /// assert_eq!(txn.query_one("test".into_expr().neq("test")), false);
271    /// assert_eq!(txn.query_one(b"test".into_expr().neq(b"test" as &[u8])), false);
272    /// assert_eq!(txn.query_one(false.into_expr().neq(false)), false);
273    ///
274    /// assert_eq!(txn.query_one(1.into_expr().neq(2)), true);
275    /// # });
276    /// ```
277    pub fn neq(&self, rhs: impl IntoExpr<'column, S, Typ = T>) -> Expr<'column, S, bool> {
278        let lhs = self.inner.clone();
279        let rhs = rhs.into_expr().inner;
280        Expr::adhoc(move |b| lhs.build_expr(b).is_not(rhs.build_expr(b)))
281    }
282}
283
284impl<'column, S> Expr<'column, S, bool> {
285    /// Checks whether an expression is false.
286    ///
287    /// ```
288    /// # use rust_query::IntoExpr;
289    /// # rust_query::private::doctest::get_txn(|txn| {
290    /// assert_eq!(txn.query_one(true.into_expr().not()), false);
291    /// assert_eq!(txn.query_one(false.into_expr().not()), true);
292    /// # });
293    /// ```
294    pub fn not(&self) -> Self {
295        let val = self.inner.clone();
296        Expr::adhoc(move |b| val.build_expr(b).not())
297    }
298
299    /// Check if two expressions are both true.
300    ///
301    /// ```
302    /// # use rust_query::IntoExpr;
303    /// # rust_query::private::doctest::get_txn(|txn| {
304    /// assert_eq!(txn.query_one(true.into_expr().and(true)), true);
305    /// assert_eq!(txn.query_one(false.into_expr().and(true)), false);
306    /// assert_eq!(txn.query_one(false.into_expr().and(false)), false);
307    /// # });
308    /// ```
309    pub fn and(&self, rhs: impl IntoExpr<'column, S, Typ = bool>) -> Self {
310        let lhs = self.inner.clone();
311        let rhs = rhs.into_expr().inner;
312        Expr::adhoc(move |b| lhs.build_expr(b).and(rhs.build_expr(b)))
313    }
314
315    /// Check if one of two expressions is true.
316    ///
317    /// ```
318    /// # use rust_query::IntoExpr;
319    /// # rust_query::private::doctest::get_txn(|txn| {
320    /// assert_eq!(txn.query_one(true.into_expr().or(true)), true);
321    /// assert_eq!(txn.query_one(false.into_expr().or(true)), true);
322    /// assert_eq!(txn.query_one(false.into_expr().or(false)), false);
323    /// # });
324    /// ```
325    pub fn or(&self, rhs: impl IntoExpr<'column, S, Typ = bool>) -> Self {
326        let lhs = self.inner.clone();
327        let rhs = rhs.into_expr().inner;
328        Expr::adhoc(move |b| lhs.build_expr(b).or(rhs.build_expr(b)))
329    }
330}
331
332impl<'column, S, Typ: MyTyp> Expr<'column, S, Option<Typ>> {
333    /// Use the first expression if it is [Some], otherwise use the second expression.
334    ///
335    /// ```
336    /// # use rust_query::IntoExpr;
337    /// # rust_query::private::doctest::get_txn(|txn| {
338    /// assert_eq!(txn.query_one(Some(10).into_expr().unwrap_or(5)), 10);
339    /// assert_eq!(txn.query_one(None::<String>.into_expr().unwrap_or("foo")), "foo");
340    /// # });
341    /// ```
342    pub fn unwrap_or(&self, rhs: impl IntoExpr<'column, S, Typ = Typ>) -> Expr<'column, S, Typ> {
343        let lhs = self.inner.clone();
344        let rhs = rhs.into_expr().inner;
345        let maybe_optional = rhs.maybe_optional;
346        Expr::adhoc_promise(
347            move |b| sea_query::Expr::expr(lhs.build_expr(b)).if_null(rhs.build_expr(b)),
348            maybe_optional,
349        )
350    }
351
352    /// Check that the expression is [Some].
353    ///
354    /// ```
355    /// # use rust_query::IntoExpr;
356    /// # rust_query::private::doctest::get_txn(|txn| {
357    /// assert_eq!(txn.query_one(Some(10).into_expr().is_some()), true);
358    /// assert_eq!(txn.query_one(None::<i64>.into_expr().is_some()), false);
359    /// # });
360    /// ```
361    pub fn is_some(&self) -> Expr<'column, S, bool> {
362        let val = self.inner.clone();
363        Expr::adhoc(move |b| val.build_expr(b).is_not_null())
364    }
365
366    /// Check that the expression is [None].
367    ///
368    /// ```
369    /// # use rust_query::IntoExpr;
370    /// # rust_query::private::doctest::get_txn(|txn| {
371    /// assert_eq!(txn.query_one(Some(10).into_expr().is_none()), false);
372    /// assert_eq!(txn.query_one(None::<i64>.into_expr().is_none()), true);
373    /// # });
374    /// ```
375    pub fn is_none(&self) -> Expr<'column, S, bool> {
376        let val = self.inner.clone();
377        Expr::adhoc(move |b| val.build_expr(b).is_null())
378    }
379}
380
381impl<'column, S> Expr<'column, S, i64> {
382    #[deprecated(note = "Renamed to [Self::to_f64]")]
383    pub fn as_float(&self) -> Expr<'column, S, f64> {
384        self.to_f64()
385    }
386
387    /// Convert the [i64] expression to [f64] type.
388    ///
389    /// The conversion may not be lossless for integers larger than 2^53 or smaller than (-2^53).
390    ///
391    /// ```
392    /// # use rust_query::IntoExpr;
393    /// # rust_query::private::doctest::get_txn(|txn| {
394    /// assert_eq!(txn.query_one(10.into_expr().as_float()), 10.0);
395    /// # });
396    /// ```
397    pub fn to_f64(&self) -> Expr<'column, S, f64> {
398        let val = self.inner.clone();
399        Expr::adhoc(move |b| val.build_expr(b).cast_as(Alias::new("REAL")))
400    }
401
402    /// Calculate the remainder for integer division.
403    ///
404    /// The remainder is the missing part after division.
405    ///
406    /// ```
407    /// # use rust_query::IntoExpr;
408    /// # rust_query::private::doctest::get_txn(|txn| {
409    /// assert_eq!(txn.query_one(5.into_expr().div(3)), 1);
410    /// assert_eq!(txn.query_one(5.into_expr().modulo(3)), 2);
411    /// assert_eq!(txn.query_one((-5).into_expr().div(3)), -1);
412    /// assert_eq!(txn.query_one((-5).into_expr().modulo(3)), -2);
413    /// # });
414    /// ```
415    pub fn modulo(&self, rhs: impl IntoExpr<'column, S, Typ = i64>) -> Self {
416        let lhs = self.inner.clone();
417        let rhs = rhs.into_expr().inner;
418        Expr::adhoc(move |b| lhs.build_expr(b).modulo(rhs.build_expr(b)))
419    }
420
421    /// Get the current timestamp as milliseconds since unix epoch.
422    pub fn unix_epoch() -> Self {
423        #[expect(deprecated)]
424        crate::value::UnixEpoch.into_expr()
425    }
426}
427
428impl<'column, S> Expr<'column, S, f64> {
429    #[deprecated(note = "Renamed to [Self::to_i64]")]
430    pub fn truncate(&self) -> Expr<'column, S, i64> {
431        self.to_i64()
432    }
433
434    /// Convert the [f64] expression to [i64] type.
435    ///
436    /// Always rounds towards zero for floats that are not already an integer.
437    ///
438    /// Values outside the range `i64::MIN..=i64::MAX`, will be converted to
439    /// the closest integer in that range.
440    ///
441    /// ```
442    /// # use rust_query::IntoExpr;
443    /// # rust_query::private::doctest::get_txn(|txn| {
444    /// assert_eq!(txn.query_one(10.9.into_expr().truncate()), 10);
445    /// assert_eq!(txn.query_one((-10.9).into_expr().truncate()), -10);
446    /// assert_eq!(txn.query_one((342143124.0).into_expr().truncate()), 342143124);
447    /// assert_eq!(txn.query_one((f64::MIN).into_expr().truncate()), i64::MIN);
448    /// assert_eq!(txn.query_one((f64::NEG_INFINITY).into_expr().truncate()), i64::MIN);
449    /// # });
450    /// ```
451    pub fn to_i64(&self) -> Expr<'column, S, i64> {
452        let val = self.inner.clone();
453        Expr::adhoc(move |b| val.build_expr(b).cast_as(Alias::new("INTEGER")))
454    }
455
456    /// Round the [f64] expression down.
457    ///
458    /// ```
459    /// # use rust_query::IntoExpr;
460    /// # rust_query::private::doctest::get_txn(|txn| {
461    /// assert_eq!(txn.query_one(10.9.into_expr().floor()), 10.0);
462    /// assert_eq!(txn.query_one((-10.9).into_expr().floor()), -11.0);
463    /// assert_eq!(txn.query_one((f64::MIN).into_expr().floor()), f64::MIN);
464    /// assert_eq!(txn.query_one((f64::NEG_INFINITY).into_expr().floor()), f64::NEG_INFINITY);
465    /// # });
466    /// ```
467    pub fn floor(&self) -> Expr<'column, S, f64> {
468        let val = self.inner.clone();
469        Expr::adhoc(move |b| sea_query::Func::cust("floor").arg(val.build_expr(b)).into())
470    }
471
472    /// Round the [f64] expression up.
473    ///
474    /// ```
475    /// # use rust_query::IntoExpr;
476    /// # rust_query::private::doctest::get_txn(|txn| {
477    /// assert_eq!(txn.query_one(10.9.into_expr().ceil()), 11.0);
478    /// assert_eq!(txn.query_one((-10.9).into_expr().ceil()), -10.0);
479    /// assert_eq!(txn.query_one((f64::MIN).into_expr().ceil()), f64::MIN);
480    /// assert_eq!(txn.query_one((f64::NEG_INFINITY).into_expr().ceil()), f64::NEG_INFINITY);
481    /// # });
482    /// ```
483    pub fn ceil(&self) -> Expr<'column, S, f64> {
484        let val = self.inner.clone();
485        Expr::adhoc(move |b| sea_query::Func::cust("ceil").arg(val.build_expr(b)).into())
486    }
487
488    /// Round the [f64] expression to the specified precision.
489    ///
490    /// Precision specifies the number of decimal places to keep.
491    /// A negative precision is treated as zero.
492    ///
493    /// ```
494    /// # use rust_query::IntoExpr;
495    /// # rust_query::private::doctest::get_txn(|txn| {
496    /// assert_eq!(txn.query_one(10.85.into_expr().round_with_precision(0)), 11.0);
497    /// assert_eq!(txn.query_one((-10.85).into_expr().round_with_precision(0)), -11.0);
498    /// assert_eq!(txn.query_one(10.85.into_expr().round_with_precision(1)), 10.8);
499    /// assert_eq!(txn.query_one((-10.85).into_expr().round_with_precision(1)), -10.8);
500    /// assert_eq!(txn.query_one((f64::MIN).into_expr().round_with_precision(0)), f64::MIN);
501    /// assert_eq!(txn.query_one((f64::NEG_INFINITY).into_expr().round_with_precision(0)), f64::NEG_INFINITY);
502    /// # });
503    /// ```
504    pub fn round_with_precision(&self, precision: impl IntoExpr<'column, S, Typ = i64>) -> Self {
505        let val = self.inner.clone();
506        let precision = precision.into_expr().inner;
507        Expr::adhoc(move |b| {
508            sea_query::Func::round_with_precision(val.build_expr(b), precision.build_expr(b)).into()
509        })
510    }
511}
512
513impl<'column, S> Expr<'column, S, String> {
514    /// Check if the expression starts with the string pattern.
515    ///
516    /// Matches case-sensitive. The pattern gets automatically escaped.
517    ///
518    /// ```
519    /// # use rust_query::IntoExpr;
520    /// # rust_query::private::doctest::get_txn(|txn| {
521    /// assert_eq!(txn.query_one("hello world".into_expr().starts_with("hello")), true);
522    /// assert_eq!(txn.query_one("hello world".into_expr().starts_with("Hello")), false);
523    /// # });
524    /// ```
525    pub fn starts_with(&self, pattern: impl AsRef<str>) -> Expr<'column, S, bool> {
526        self.glob(format!("{}*", escape_glob(pattern)))
527    }
528
529    /// Check if the expression ends with the string pattern.
530    ///
531    /// Matches case-sensitive. The pattern gets automatically escaped.
532    ///
533    /// ```
534    /// # use rust_query::IntoExpr;
535    /// # rust_query::private::doctest::get_txn(|txn| {
536    /// assert_eq!(txn.query_one("hello world".into_expr().ends_with("world")), true);
537    /// assert_eq!(txn.query_one("hello world".into_expr().ends_with("World")), false);
538    /// # });
539    /// ```
540    pub fn ends_with(&self, pattern: impl AsRef<str>) -> Expr<'column, S, bool> {
541        self.glob(format!("*{}", escape_glob(pattern)))
542    }
543
544    /// Check if the expression contains the string pattern.
545    ///
546    /// Matches case-sensitive. The pattern gets automatically escaped.
547    ///
548    /// ```
549    /// # use rust_query::IntoExpr;
550    /// # rust_query::private::doctest::get_txn(|txn| {
551    /// assert_eq!(txn.query_one("rhubarb".into_expr().contains("bar")), true);
552    /// assert_eq!(txn.query_one("rhubarb".into_expr().contains("Bar")), false);
553    /// # });
554    /// ```
555    #[doc(alias = "instr")]
556    pub fn contains(&self, rhs: impl IntoExpr<'column, S, Typ = String>) -> Expr<'column, S, bool> {
557        let lhs = self.inner.clone();
558        let rhs = rhs.into_expr().inner;
559        Expr::adhoc(move |b| {
560            sea_query::Expr::expr(
561                sea_query::Func::cust("instr")
562                    .arg(lhs.build_expr(b))
563                    .arg(rhs.build_expr(b)),
564            )
565            .is_not(CONST_0)
566        })
567    }
568
569    /// Replace all occurences of a string with another string.
570    ///
571    /// ```
572    /// # use rust_query::IntoExpr;
573    /// # rust_query::private::doctest::get_txn(|txn| {
574    /// assert_eq!(txn.query_one("very,cool,list".into_expr().replace(",", "::")), "very::cool::list");
575    /// assert_eq!(txn.query_one("rarar".into_expr().replace("rar", "rer")), "rerar");
576    /// # });
577    /// ```
578    pub fn replace(
579        &self,
580        pattern: impl IntoExpr<'column, S, Typ = String>,
581        new: impl IntoExpr<'column, S, Typ = String>,
582    ) -> Self {
583        let lhs = self.inner.clone();
584        let pat = pattern.into_expr().inner;
585        let new = new.into_expr().inner;
586        Expr::adhoc(move |b| {
587            sea_query::Func::cust("replace")
588                .args([lhs.build_expr(b), pat.build_expr(b), new.build_expr(b)])
589                .into()
590        })
591    }
592
593    /// Removes all characters from a set from the start of a string.
594    ///
595    /// ```
596    /// # use rust_query::IntoExpr;
597    /// # rust_query::private::doctest::get_txn(|txn| {
598    /// assert_eq!(txn.query_one("abacda".into_expr().ltrim("ab")), "cda");
599    /// # });
600    /// ```
601    pub fn ltrim(&self, char_set: impl IntoExpr<'column, S, Typ = String>) -> Self {
602        let lhs = self.inner.clone();
603        let char_set = char_set.into_expr().inner;
604        Expr::adhoc(move |b| {
605            sea_query::Func::cust("ltrim")
606                .args([lhs.build_expr(b), char_set.build_expr(b)])
607                .into()
608        })
609    }
610
611    /// Removes all characters from a set from the end of a string.
612    ///
613    /// ```
614    /// # use rust_query::IntoExpr;
615    /// # rust_query::private::doctest::get_txn(|txn| {
616    /// assert_eq!(txn.query_one("adcaba".into_expr().rtrim("ab")), "adc");
617    /// # });
618    /// ```
619    pub fn rtrim(&self, char_set: impl IntoExpr<'column, S, Typ = String>) -> Self {
620        let lhs = self.inner.clone();
621        let char_set = char_set.into_expr().inner;
622        Expr::adhoc(move |b| {
623            sea_query::Func::cust("rtrim")
624                .args([lhs.build_expr(b), char_set.build_expr(b)])
625                .into()
626        })
627    }
628
629    /// Removes all characters from a set from both the start and end of a string.
630    ///
631    /// ```
632    /// # use rust_query::IntoExpr;
633    /// # rust_query::private::doctest::get_txn(|txn| {
634    /// assert_eq!(txn.query_one("ffzfzf".into_expr().trim("f")), "zfz");
635    /// # });
636    /// ```
637    pub fn trim(&self, char_set: impl IntoExpr<'column, S, Typ = String>) -> Self {
638        let lhs = self.inner.clone();
639        let char_set = char_set.into_expr().inner;
640        Expr::adhoc(move |b| {
641            sea_query::Func::cust("trim")
642                .args([lhs.build_expr(b), char_set.build_expr(b)])
643                .into()
644        })
645    }
646
647    /// Check if the expression matches the pattern [sqlite docs](https://www.sqlite.org/lang_expr.html#like).
648    ///
649    /// This is a case-sensitive version of [like](Self::like). It uses Unix file globbing syntax for wild
650    /// cards. `*` matches any sequence of characters and `?` matches any single character. `[0-9]` matches
651    /// any single digit and `[a-z]` matches any single lowercase letter. `^` negates the pattern.
652    ///
653    /// ```
654    /// # use rust_query::IntoExpr;
655    /// # rust_query::private::doctest::get_txn(|txn| {
656    /// assert_eq!(txn.query_one("hello world".into_expr().glob("?ello*")), true);
657    /// assert_eq!(txn.query_one("hello world".into_expr().glob("Hell*")), false);
658    /// # });
659    /// ```
660    pub fn glob(&self, rhs: impl IntoExpr<'column, S, Typ = String>) -> Expr<'column, S, bool> {
661        let lhs = self.inner.clone();
662        let rhs = rhs.into_expr().inner;
663        Expr::adhoc(move |b| sea_query::Expr::expr(lhs.build_expr(b)).glob(rhs.build_expr(b)))
664    }
665
666    /// Check if the expression matches the pattern [sqlite docs](https://www.sqlite.org/lang_expr.html#like).
667    ///
668    /// As noted in the docs, it is **case-insensitive** for ASCII characters. Other characters are case-sensitive.
669    /// For creating patterns it uses `%` as a wildcard for any sequence of characters and `_` for any single character.
670    /// Special characters should be escaped with `\`.
671    ///
672    /// ```
673    /// # use rust_query::IntoExpr;
674    /// # rust_query::private::doctest::get_txn(|txn| {
675    /// assert_eq!(txn.query_one("hello world".into_expr().like("HELLO%")), true);
676    /// assert_eq!(txn.query_one("hello world".into_expr().like("he_o%")), false);
677    /// # });
678    /// ```
679    pub fn like(&self, pattern: impl Into<String>) -> Expr<'column, S, bool> {
680        let lhs = self.inner.clone();
681        let rhs = pattern.into();
682        Expr::adhoc(move |b| {
683            sea_query::Expr::expr(lhs.build_expr(b))
684                .like(sea_query::LikeExpr::new(&rhs).escape('\\'))
685        })
686    }
687
688    /// Concatenate two strings.
689    ///
690    /// ```
691    /// # use rust_query::IntoExpr;
692    /// # rust_query::private::doctest::get_txn(|txn| {
693    /// assert_eq!(txn.query_one("hello ".into_expr().concat("world").concat("!")), "hello world!");
694    /// # });
695    /// ```
696    pub fn concat(&self, rhs: impl IntoExpr<'column, S, Typ = String>) -> Self {
697        let lhs = self.inner.clone();
698        let rhs = rhs.into_expr().inner;
699        Expr::adhoc(move |b| {
700            sea_query::Expr::expr(
701                sea_query::Func::cust("concat")
702                    .arg(lhs.build_expr(b))
703                    .arg(rhs.build_expr(b)),
704            )
705        })
706    }
707
708    /// Convert ascii to lowercase.
709    ///
710    /// ```
711    /// # use rust_query::IntoExpr;
712    /// # rust_query::private::doctest::get_txn(|txn| {
713    /// assert_eq!(txn.query_one("Hello".into_expr().lower()), "hello");
714    /// assert_eq!(txn.query_one("WHAT".into_expr().lower()), "what");
715    /// # });
716    /// ```
717    pub fn lower(&self) -> Self {
718        let lhs = self.inner.clone();
719        Expr::adhoc(move |b| {
720            sea_query::Expr::expr(sea_query::Func::cust("lower").arg(lhs.build_expr(b)))
721        })
722    }
723
724    /// Convert ascii to uppercase.
725    ///
726    /// ```
727    /// # use rust_query::IntoExpr;
728    /// # rust_query::private::doctest::get_txn(|txn| {
729    /// assert_eq!(txn.query_one("Hello".into_expr().upper()), "HELLO");
730    /// assert_eq!(txn.query_one("what".into_expr().upper()), "WHAT");
731    /// # });
732    /// ```
733    pub fn upper(&self) -> Self {
734        let lhs = self.inner.clone();
735        Expr::adhoc(move |b| {
736            sea_query::Expr::expr(sea_query::Func::cust("upper").arg(lhs.build_expr(b)))
737        })
738    }
739
740    /// The number of unicode code points in the string.
741    ///
742    /// ```
743    /// # use rust_query::IntoExpr;
744    /// # rust_query::private::doctest::get_txn(|txn| {
745    /// assert_eq!(txn.query_one("€".into_expr().char_len()), 1);
746    /// assert_eq!(txn.query_one("what".into_expr().char_len()), 4);
747    /// # });
748    /// ```
749    pub fn char_len(&self) -> Expr<'column, S, i64> {
750        let lhs = self.inner.clone();
751        Expr::adhoc(move |b| {
752            sea_query::Expr::expr(sea_query::Func::cust("length").arg(lhs.build_expr(b)))
753        })
754    }
755}
756
757impl<'column, S, T: BuffTyp> Expr<'column, S, T> {
758    /// The length of the value in bytes.
759    ///
760    /// The byte length of strings can depend on the encoding (UTF-8 or UTF-16).
761    ///
762    /// ```
763    /// # use rust_query::IntoExpr;
764    /// # rust_query::private::doctest::get_txn(|txn| {
765    /// assert_eq!(txn.query_one("€".into_expr().byte_len()), 3);
766    /// assert_eq!(txn.query_one("what".into_expr().byte_len()), 4);
767    /// assert_eq!(txn.query_one(vec![1, 2].into_expr().byte_len()), 2);
768    /// # });
769    /// ```
770    pub fn byte_len(&self) -> Expr<'column, S, i64> {
771        let lhs = self.inner.clone();
772        Expr::adhoc(move |b| {
773            sea_query::Expr::expr(sea_query::Func::cust("octet_length").arg(lhs.build_expr(b)))
774        })
775    }
776}
777
778impl<'column, S> Expr<'column, S, Vec<u8>> {
779    /// Create a new blob of zero bytes of the specified length.
780    ///
781    /// ```
782    /// # use rust_query::Expr;
783    /// # rust_query::private::doctest::get_txn(|txn| {
784    /// assert_eq!(txn.query_one(Expr::zero_blob(40)), vec![0; 40]);
785    /// # });
786    /// ```
787    pub fn zero_blob(len: impl IntoExpr<'column, S, Typ = i64>) -> Self {
788        let len = len.into_expr().inner;
789        Expr::adhoc(move |b| {
790            sea_query::Expr::expr(sea_query::Func::cust("zeroblob").arg(len.build_expr(b)))
791        })
792    }
793}
794
795// This is a copy of the function from the glob crate https://github.com/rust-lang/glob/blob/49ee1e92bd6e8c5854c0b339634f9b4b733aba4f/src/lib.rs#L720-L737.
796fn escape_glob(s: impl AsRef<str>) -> String {
797    let mut escaped = String::new();
798    for c in s.as_ref().chars() {
799        match c {
800            // note that ! does not need escaping because it is only special
801            // inside brackets
802            '?' | '*' | '[' | ']' => {
803                escaped.push('[');
804                escaped.push(c);
805                escaped.push(']');
806            }
807            c => {
808                escaped.push(c);
809            }
810        }
811    }
812    escaped
813}