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}