drizzle_core/expr/string.rs
1//! String functions: `UPPER`, `LOWER`, `TRIM`, `LENGTH`, `SUBSTR`, `REPLACE`, ...
2//!
3//! Text arguments must have a text SQL type and position or length arguments
4//! an integer type; anything else does not compile. Functions that exist on
5//! only some databases do not compile for the others.
6
7use crate::dialect::{Dialect, DialectTypes};
8use crate::dialect::{DialectSupports, feature};
9use crate::sql::{SQL, Token};
10use crate::traits::{SQLParam, ToSQL};
11use crate::types::{DataType, Integral, Textual};
12use crate::{MySQLDialect, PostgresDialect, SQLiteDialect};
13use drizzle_types::postgres::types::{
14 Char as PgChar, Int4 as PgInt4, Text as PgText, Varchar as PgVarchar,
15};
16use drizzle_types::sqlite::types::{Integer as SqliteInteger, Text as SqliteText};
17
18use super::ExprSources;
19use super::{AggregateKind, Expr, NonNull, Nullability, SQLExpr};
20use crate::scope::Arg;
21
22#[diagnostic::on_unimplemented(
23 message = "no length policy for `{Self}` on this dialect",
24 label = "length return type is not defined for this SQL type/dialect"
25)]
26/// Result type of [`length`], [`char_length`] and [`octet_length`] for a
27/// text SQL type on dialect `D`: `INTEGER` on SQLite, `int4` on PostgreSQL,
28/// `BIGINT` on MySQL.
29pub trait LengthPolicy<D>: DataType {
30 /// Result type of the length functions.
31 type Output: DataType;
32}
33
34#[diagnostic::on_unimplemented(
35 message = "INSTR is not available for this dialect",
36 label = "use a dialect-specific substring-position function"
37)]
38/// Dialects that provide [`instr`] (SQLite and MySQL), and its result type.
39pub trait InstrPolicy {
40 /// Result type of `INSTR`.
41 type Output: DataType;
42}
43
44impl LengthPolicy<SQLiteDialect> for SqliteText {
45 type Output = SqliteInteger;
46}
47impl LengthPolicy<SQLiteDialect> for drizzle_types::sqlite::types::Any {
48 type Output = SqliteInteger;
49}
50
51impl LengthPolicy<PostgresDialect> for PgVarchar {
52 type Output = PgInt4;
53}
54impl LengthPolicy<PostgresDialect> for PgText {
55 type Output = PgInt4;
56}
57impl LengthPolicy<PostgresDialect> for PgChar {
58 type Output = PgInt4;
59}
60
61macro_rules! mysql_length_policy {
62 ($($ty:ty),+ $(,)?) => {
63 $(
64 impl LengthPolicy<MySQLDialect> for $ty {
65 type Output = drizzle_types::mysql::types::BigInt;
66 }
67 )+
68 };
69}
70
71mysql_length_policy!(
72 drizzle_types::mysql::types::Char,
73 drizzle_types::mysql::types::Varchar,
74 drizzle_types::mysql::types::TinyText,
75 drizzle_types::mysql::types::Text,
76 drizzle_types::mysql::types::MediumText,
77 drizzle_types::mysql::types::LongText,
78 drizzle_types::mysql::types::Enum,
79 drizzle_types::mysql::types::Set,
80);
81
82impl DialectSupports<feature::PostgresString> for PostgresDialect {}
83
84impl InstrPolicy for SQLiteDialect {
85 type Output = SqliteInteger;
86}
87impl InstrPolicy for MySQLDialect {
88 type Output = drizzle_types::mysql::types::BigInt;
89}
90
91impl DialectSupports<feature::LeftRight> for PostgresDialect {}
92impl DialectSupports<feature::LeftRight> for MySQLDialect {}
93impl DialectSupports<feature::Pad> for PostgresDialect {}
94impl DialectSupports<feature::Pad> for MySQLDialect {}
95impl DialectSupports<feature::Reverse> for PostgresDialect {}
96impl DialectSupports<feature::Reverse> for MySQLDialect {}
97impl DialectSupports<feature::Repeat> for PostgresDialect {}
98impl DialectSupports<feature::Repeat> for MySQLDialect {}
99
100// =============================================================================
101// CASE CONVERSION
102// =============================================================================
103
104/// Converts text to upper case (`UPPER`).
105///
106/// The argument must be text. The result is text and keeps the argument's
107/// nullability.
108///
109/// # Examples
110///
111/// ```rust
112/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
113/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
114/// # #[derive(Clone, Debug)] struct Value(String);
115/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
116/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
117/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
118/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
119/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
120/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
121/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
122/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
123/// assert_eq!(upper(users.name).sql(), r#"UPPER ("users"."name")"#);
124/// ```
125///
126/// # Type safety
127///
128/// `UPPER` of an integer column does not compile:
129///
130/// ```rust,compile_fail
131/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
132/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
133/// # #[derive(Clone, Debug)] struct Value(String);
134/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
135/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
136/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
137/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
138/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
139/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
140/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
141/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
142/// let wrong = upper(users.id);
143/// ```
144#[allow(clippy::type_complexity)]
145pub fn upper<'a, V, E>(
146 expr: E,
147) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Text, E::Nullable, E::Aggregate, E::Sources>
148where
149 V: SQLParam + 'a,
150 E: Expr<'a, V>,
151 E::SQLType: Textual,
152{
153 SQLExpr::new(SQL::func("UPPER", expr.into_sql()))
154}
155
156/// Converts text to lower case (`LOWER`).
157///
158/// The argument must be text. The result is text and keeps the argument's
159/// nullability.
160///
161/// # Examples
162///
163/// ```rust
164/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
165/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
166/// # #[derive(Clone, Debug)] struct Value(String);
167/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
168/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
169/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
170/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
171/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
172/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
173/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
174/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
175/// assert_eq!(lower(users.email).sql(), r#"LOWER ("users"."email")"#);
176/// ```
177#[allow(clippy::type_complexity)]
178pub fn lower<'a, V, E>(
179 expr: E,
180) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Text, E::Nullable, E::Aggregate, E::Sources>
181where
182 V: SQLParam + 'a,
183 E: Expr<'a, V>,
184 E::SQLType: Textual,
185{
186 SQLExpr::new(SQL::func("LOWER", expr.into_sql()))
187}
188
189// =============================================================================
190// TRIM FUNCTIONS
191// =============================================================================
192
193/// Removes leading and trailing spaces (`TRIM`).
194///
195/// The argument must be text. The result is text and keeps the argument's
196/// nullability.
197///
198/// # Examples
199///
200/// ```rust
201/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
202/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
203/// # #[derive(Clone, Debug)] struct Value(String);
204/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
205/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
206/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
207/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
208/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
209/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
210/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
211/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
212/// assert_eq!(trim(users.name).sql(), r#"TRIM ("users"."name")"#);
213/// ```
214#[allow(clippy::type_complexity)]
215pub fn trim<'a, V, E>(
216 expr: E,
217) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Text, E::Nullable, E::Aggregate, E::Sources>
218where
219 V: SQLParam + 'a,
220 E: Expr<'a, V>,
221 E::SQLType: Textual,
222{
223 SQLExpr::new(SQL::func("TRIM", expr.into_sql()))
224}
225
226/// Removes leading spaces (`LTRIM`).
227///
228/// The argument must be text. The result is text and keeps the argument's
229/// nullability.
230///
231/// # Examples
232///
233/// ```rust
234/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
235/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
236/// # #[derive(Clone, Debug)] struct Value(String);
237/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
238/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
239/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
240/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
241/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
242/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
243/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
244/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
245/// assert_eq!(ltrim(users.name).sql(), r#"LTRIM ("users"."name")"#);
246/// ```
247#[allow(clippy::type_complexity)]
248pub fn ltrim<'a, V, E>(
249 expr: E,
250) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Text, E::Nullable, E::Aggregate, E::Sources>
251where
252 V: SQLParam + 'a,
253 E: Expr<'a, V>,
254 E::SQLType: Textual,
255{
256 SQLExpr::new(SQL::func("LTRIM", expr.into_sql()))
257}
258
259/// Removes trailing spaces (`RTRIM`).
260///
261/// The argument must be text. The result is text and keeps the argument's
262/// nullability.
263///
264/// # Examples
265///
266/// ```rust
267/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
268/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
269/// # #[derive(Clone, Debug)] struct Value(String);
270/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
271/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
272/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
273/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
274/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
275/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
276/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
277/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
278/// assert_eq!(rtrim(users.name).sql(), r#"RTRIM ("users"."name")"#);
279/// ```
280#[allow(clippy::type_complexity)]
281pub fn rtrim<'a, V, E>(
282 expr: E,
283) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Text, E::Nullable, E::Aggregate, E::Sources>
284where
285 V: SQLParam + 'a,
286 E: Expr<'a, V>,
287 E::SQLType: Textual,
288{
289 SQLExpr::new(SQL::func("RTRIM", expr.into_sql()))
290}
291
292// =============================================================================
293// COLLATE
294// =============================================================================
295
296/// Applies a collation to a text expression (`expr COLLATE "name"`).
297///
298/// Renders `(expr COLLATE "name")`, with the name quoted as an identifier,
299/// which both SQLite and PostgreSQL accept. The argument must be text. The
300/// result keeps its SQL type, nullability and aggregate kind. Use it in a
301/// comparison for case-insensitive matching, or in `ORDER BY`.
302///
303/// # Examples
304///
305/// ```rust
306/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
307/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
308/// # #[derive(Clone, Debug)] struct Value(String);
309/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
310/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
311/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
312/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
313/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
314/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
315/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
316/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
317/// // Case-insensitive comparison on SQLite.
318/// let cond = eq(collate(users.name, "NOCASE"), "alice");
319/// assert_eq!(cond.sql(), r#"("users"."name" COLLATE "NOCASE")= ?"#);
320/// ```
321pub fn collate<'a, V, E>(
322 expr: E,
323 name: &'static str,
324) -> SQLExpr<'a, V, E::SQLType, E::Nullable, E::Aggregate, E::Sources>
325where
326 V: SQLParam + 'a,
327 E: Expr<'a, V>,
328 E::SQLType: Textual,
329{
330 let inner = expr.into_sql().parens_if_subquery();
331 SQLExpr::new(
332 SQL::token(Token::LPAREN)
333 .append(inner)
334 .push(Token::COLLATE)
335 .append(SQL::ident(name))
336 .push(Token::RPAREN),
337 )
338}
339
340// =============================================================================
341// LENGTH
342// =============================================================================
343
344/// Length of a text value (`LENGTH`).
345///
346/// SQLite and PostgreSQL count characters; MySQL counts bytes. Use
347/// [`char_length`] to count characters on every dialect. The argument must be
348/// text. The result is an integer (see [`LengthPolicy`]) and keeps the
349/// argument's nullability.
350///
351/// # Examples
352///
353/// ```rust
354/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
355/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
356/// # #[derive(Clone, Debug)] struct Value(String);
357/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
358/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
359/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
360/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
361/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
362/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
363/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
364/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
365/// assert_eq!(length(users.name).sql(), r#"LENGTH ("users"."name")"#);
366/// ```
367#[allow(clippy::type_complexity)]
368pub fn length<'a, V, E>(
369 expr: E,
370) -> SQLExpr<
371 'a,
372 V,
373 <E::SQLType as LengthPolicy<V::DialectMarker>>::Output,
374 E::Nullable,
375 E::Aggregate,
376 E::Sources,
377>
378where
379 V: SQLParam + 'a,
380 E: Expr<'a, V>,
381 E::SQLType: LengthPolicy<V::DialectMarker>,
382{
383 SQLExpr::new(SQL::func("LENGTH", expr.into_sql()))
384}
385
386// =============================================================================
387// SUBSTRING
388// =============================================================================
389
390/// Part of a text value (`SUBSTR(expr, start, len)`).
391///
392/// Returns `len` characters starting at `start`, counting from 1. `expr`
393/// must be text; `start` and `len` must be integers. The result is text,
394/// nullable if any argument is.
395///
396/// # Examples
397///
398/// ```rust
399/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
400/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
401/// # #[derive(Clone, Debug)] struct Value(String);
402/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
403/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
404/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
405/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
406/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
407/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
408/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
409/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
410/// // The first three characters.
411/// let prefix = substr(users.name, 1, 3);
412/// assert_eq!(prefix.sql(), r#"SUBSTR ("users"."name", ?, ?)"#);
413/// ```
414#[allow(clippy::type_complexity)]
415pub fn substr<'a, V, E, S, L>(
416 expr: E,
417 start: S,
418 len: L,
419) -> SQLExpr<
420 'a,
421 V,
422 <V::DialectMarker as DialectTypes>::Text,
423 <<E::Nullable as Nullability>::Or<S::Nullable> as Nullability>::Or<L::Nullable>,
424 <<E::Aggregate as AggregateKind>::Or<S::Aggregate> as AggregateKind>::Or<L::Aggregate>,
425 (E::Sources, (S::Sources, L::Sources)),
426>
427where
428 V: SQLParam + 'a,
429 E: Expr<'a, V>,
430 E::SQLType: Textual,
431 S: Expr<'a, V>,
432 S::SQLType: Integral,
433 S::Nullable: Nullability,
434 S::Aggregate: AggregateKind,
435 L: Expr<'a, V>,
436 L::SQLType: Integral,
437 L::Nullable: Nullability,
438 L::Aggregate: AggregateKind,
439{
440 SQLExpr::new(SQL::func(
441 "SUBSTR",
442 expr.into_sql()
443 .push(Token::COMMA)
444 .append(start.into_sql())
445 .push(Token::COMMA)
446 .append(len.into_sql()),
447 ))
448}
449
450// =============================================================================
451// REPLACE
452// =============================================================================
453
454/// Replaces every occurrence of `from` with `to` (`REPLACE`).
455///
456/// All three arguments must be text. The result is text, nullable if any
457/// argument is.
458///
459/// # Examples
460///
461/// ```rust
462/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
463/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
464/// # #[derive(Clone, Debug)] struct Value(String);
465/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
466/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
467/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
468/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
469/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
470/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
471/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
472/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
473/// let email = replace(users.email, "@old.example", "@new.example");
474/// assert_eq!(email.sql(), r#"REPLACE ("users"."email", ?, ?)"#);
475/// ```
476#[allow(clippy::type_complexity)]
477pub fn replace<'a, V, E, F, T>(
478 expr: E,
479 from: F,
480 to: T,
481) -> SQLExpr<
482 'a,
483 V,
484 <V::DialectMarker as DialectTypes>::Text,
485 <<E::Nullable as Nullability>::Or<F::Nullable> as Nullability>::Or<T::Nullable>,
486 <<E::Aggregate as AggregateKind>::Or<F::Aggregate> as AggregateKind>::Or<T::Aggregate>,
487 (E::Sources, (F::Sources, T::Sources)),
488>
489where
490 V: SQLParam + 'a,
491 E: Expr<'a, V>,
492 E::SQLType: Textual,
493 F: Expr<'a, V>,
494 F::SQLType: Textual,
495 F::Nullable: Nullability,
496 F::Aggregate: AggregateKind,
497 T: Expr<'a, V>,
498 T::SQLType: Textual,
499 T::Nullable: Nullability,
500 T::Aggregate: AggregateKind,
501{
502 SQLExpr::new(SQL::func(
503 "REPLACE",
504 expr.into_sql()
505 .push(Token::COMMA)
506 .append(from.into_sql())
507 .push(Token::COMMA)
508 .append(to.into_sql()),
509 ))
510}
511
512// =============================================================================
513// INSTR
514// =============================================================================
515
516/// Position of `search` within a text value (`INSTR`), on SQLite and MySQL.
517///
518/// Returns the 1-based position of the first match, or 0 when there is none.
519/// Both arguments must be text. The result is an integer, nullable if either
520/// argument is. On PostgreSQL, use [`strpos`].
521///
522/// # Examples
523///
524/// ```rust
525/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
526/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
527/// # #[derive(Clone, Debug)] struct Value(String);
528/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
529/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
530/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
531/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
532/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
533/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
534/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
535/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
536/// assert_eq!(instr(users.email, "@").sql(), r#"INSTR ("users"."email", ?)"#);
537/// ```
538#[allow(clippy::type_complexity)]
539pub fn instr<'a, V, E, S>(
540 expr: E,
541 search: S,
542) -> SQLExpr<
543 'a,
544 V,
545 <V::DialectMarker as InstrPolicy>::Output,
546 <E::Nullable as Nullability>::Or<S::Nullable>,
547 <E::Aggregate as AggregateKind>::Or<S::Aggregate>,
548 (E::Sources, S::Sources),
549>
550where
551 V: SQLParam + 'a,
552 V::DialectMarker: InstrPolicy,
553 E: Expr<'a, V>,
554 E::SQLType: Textual,
555 S: Expr<'a, V>,
556 S::SQLType: Textual,
557 S::Nullable: Nullability,
558 S::Aggregate: AggregateKind,
559{
560 SQLExpr::new(SQL::func(
561 "INSTR",
562 expr.into_sql().push(Token::COMMA).append(search.into_sql()),
563 ))
564}
565
566/// Position of `search` within a text value (`STRPOS`), on PostgreSQL.
567///
568/// Returns the 1-based position of the first match, or 0 when there is none.
569/// Both arguments must be text. The result is `int4`, nullable if either
570/// argument is. On SQLite and MySQL, use [`instr`].
571///
572/// # Examples
573///
574/// ```rust
575/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
576/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
577/// # #[derive(Clone, Debug)] struct Value(String);
578/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
579/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
580/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
581/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
582/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
583/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
584/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
585/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
586/// assert_eq!(strpos(users.name, "a").sql(), r#"STRPOS ("users"."name", $1)"#);
587/// ```
588#[allow(clippy::type_complexity)]
589pub fn strpos<'a, V, E, S>(
590 expr: E,
591 search: S,
592) -> SQLExpr<
593 'a,
594 V,
595 drizzle_types::postgres::types::Int4,
596 <E::Nullable as Nullability>::Or<S::Nullable>,
597 <E::Aggregate as AggregateKind>::Or<S::Aggregate>,
598 (E::Sources, S::Sources),
599>
600where
601 V: SQLParam + 'a,
602 V::DialectMarker: DialectSupports<feature::PostgresString>,
603 E: Expr<'a, V>,
604 E::SQLType: Textual,
605 S: Expr<'a, V>,
606 S::SQLType: Textual,
607 S::Nullable: Nullability,
608 S::Aggregate: AggregateKind,
609{
610 SQLExpr::new(SQL::func(
611 "STRPOS",
612 expr.into_sql().push(Token::COMMA).append(search.into_sql()),
613 ))
614}
615
616// =============================================================================
617// CONCAT (with NULL propagation)
618// =============================================================================
619
620/// Joins two text values.
621///
622/// Renders `left || right` on SQLite and PostgreSQL and `CONCAT(left, right)`
623/// on MySQL, where `||` means logical OR by default. Both arguments must be
624/// text. The result is text, nullable if either argument is (concatenating
625/// NULL gives NULL). [`string_concat`](super::string_concat) is the same
626/// function.
627///
628/// # Examples
629///
630/// ```rust
631/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
632/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
633/// # #[derive(Clone, Debug)] struct Value(String);
634/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
635/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
636/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
637/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
638/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
639/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
640/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
641/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
642/// let label = concat(concat(users.name, " <"), concat(users.email, ">"));
643/// assert_eq!(label.sql(), r#""users"."name" || ? || "users"."email" || ?"#);
644/// ```
645///
646/// # Type safety
647///
648/// Joining an integer column does not compile:
649///
650/// ```rust,compile_fail
651/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
652/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
653/// # #[derive(Clone, Debug)] struct Value(String);
654/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
655/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
656/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
657/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
658/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
659/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
660/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
661/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
662/// let wrong = concat(users.id, users.name);
663/// ```
664#[allow(clippy::type_complexity)]
665pub fn concat<'a, V, E1, E2>(
666 expr1: E1,
667 expr2: E2,
668) -> SQLExpr<
669 'a,
670 V,
671 <V::DialectMarker as DialectTypes>::Text,
672 <E1::Nullable as Nullability>::Or<E2::Nullable>,
673 <E1::Aggregate as AggregateKind>::Or<E2::Aggregate>,
674 (E1::Sources, E2::Sources),
675>
676where
677 V: SQLParam + 'a,
678 E1: Expr<'a, V>,
679 E1::SQLType: Textual,
680 E2: Expr<'a, V>,
681 E2::SQLType: Textual,
682 E2::Nullable: Nullability,
683 E2::Aggregate: AggregateKind,
684{
685 let left = expr1.into_sql();
686 let right = expr2.into_sql();
687 let sql = match V::DIALECT {
688 Dialect::MySQL => SQL::func("CONCAT", left.push(Token::COMMA).append(right)),
689 Dialect::SQLite | Dialect::PostgreSQL => {
690 super::ops::binary_operator_sql(left, Token::CONCAT, right)
691 }
692 };
693 SQLExpr::new(sql)
694}
695
696// =============================================================================
697// CONCAT_WS (with separator)
698// =============================================================================
699
700/// Joins values with a separator, skipping NULLs (`CONCAT_WS`).
701///
702/// Renders `CONCAT_WS(sep, v1, v2, ...)`. The separator and values must be
703/// text; the values share one Rust type. NULL values are skipped, so the
704/// result is nullable only if the separator is. Needs SQLite 3.44 or later;
705/// also available on PostgreSQL and MySQL.
706///
707/// # Examples
708///
709/// ```rust
710/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
711/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
712/// # #[derive(Clone, Debug)] struct Value(String);
713/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
714/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
715/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
716/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
717/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
718/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
719/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
720/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
721/// let names = concat_ws(", ", [users.name, users.name]);
722/// assert_eq!(names.sql(), r#"CONCAT_WS (?, "users"."name", "users"."name")"#);
723/// ```
724#[allow(clippy::type_complexity)]
725pub fn concat_ws<'a, V, S, I>(
726 sep: S,
727 values: I,
728) -> SQLExpr<
729 'a,
730 V,
731 <V::DialectMarker as DialectTypes>::Text,
732 S::Nullable,
733 <S::Aggregate as AggregateKind>::Or<<I::Item as Expr<'a, V>>::Aggregate>,
734 (S::Sources, <I::Item as ExprSources>::Sources),
735>
736where
737 V: SQLParam + 'a,
738 S: Expr<'a, V>,
739 S::SQLType: Textual,
740 I: IntoIterator,
741 I::Item: Expr<'a, V>,
742 <I::Item as Expr<'a, V>>::SQLType: Textual,
743 <I::Item as Expr<'a, V>>::Aggregate: AggregateKind,
744{
745 let mut sql = sep.into_sql();
746 for value in values {
747 sql = sql.push(Token::COMMA).append(value.into_sql());
748 }
749 SQLExpr::new(SQL::func("CONCAT_WS", sql))
750}
751
752// =============================================================================
753// Dialect-gated String Functions
754// =============================================================================
755
756/// The first `n` characters of a text value (`LEFT`), on PostgreSQL and MySQL.
757///
758/// `expr` must be text and `n` an integer. The result is text, nullable if
759/// either argument is. SQLite has no `LEFT`; use [`substr`] there.
760///
761/// # Examples
762///
763/// ```rust
764/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
765/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
766/// # #[derive(Clone, Debug)] struct Value(String);
767/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
768/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
769/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
770/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
771/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
772/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
773/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
774/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
775/// assert_eq!(left(users.name, 3).sql(), r#"LEFT ("users"."name", $1)"#);
776/// ```
777#[allow(clippy::type_complexity)]
778pub fn left<'a, V, E, N>(
779 expr: E,
780 n: N,
781) -> SQLExpr<
782 'a,
783 V,
784 <V::DialectMarker as DialectTypes>::Text,
785 <E::Nullable as Nullability>::Or<N::Nullable>,
786 <E::Aggregate as AggregateKind>::Or<N::Aggregate>,
787 (E::Sources, N::Sources),
788>
789where
790 V: SQLParam + 'a,
791 V::DialectMarker: DialectSupports<feature::LeftRight>,
792 E: Expr<'a, V>,
793 E::SQLType: Textual,
794 N: Expr<'a, V>,
795 N::SQLType: Integral,
796 N::Nullable: Nullability,
797 N::Aggregate: AggregateKind,
798{
799 SQLExpr::new(SQL::func(
800 "LEFT",
801 expr.into_sql().push(Token::COMMA).append(n.into_sql()),
802 ))
803}
804
805/// The last `n` characters of a text value (`RIGHT`), on PostgreSQL and MySQL.
806///
807/// `expr` must be text and `n` an integer. The result is text, nullable if
808/// either argument is. SQLite has no `RIGHT`; use [`substr`] there.
809///
810/// # Examples
811///
812/// ```rust
813/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
814/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
815/// # #[derive(Clone, Debug)] struct Value(String);
816/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
817/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
818/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
819/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
820/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
821/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
822/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
823/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
824/// assert_eq!(right(users.name, 3).sql(), r#"RIGHT ("users"."name", $1)"#);
825/// ```
826#[allow(clippy::type_complexity)]
827pub fn right<'a, V, E, N>(
828 expr: E,
829 n: N,
830) -> SQLExpr<
831 'a,
832 V,
833 <V::DialectMarker as DialectTypes>::Text,
834 <E::Nullable as Nullability>::Or<N::Nullable>,
835 <E::Aggregate as AggregateKind>::Or<N::Aggregate>,
836 (E::Sources, N::Sources),
837>
838where
839 V: SQLParam + 'a,
840 V::DialectMarker: DialectSupports<feature::LeftRight>,
841 E: Expr<'a, V>,
842 E::SQLType: Textual,
843 N: Expr<'a, V>,
844 N::SQLType: Integral,
845 N::Nullable: Nullability,
846 N::Aggregate: AggregateKind,
847{
848 SQLExpr::new(SQL::func(
849 "RIGHT",
850 expr.into_sql().push(Token::COMMA).append(n.into_sql()),
851 ))
852}
853
854/// The `n`-th field of a text value split on `delimiter` (`SPLIT_PART`), on PostgreSQL.
855///
856/// Fields are numbered from 1. `expr` and `delimiter` must be text and `n` an
857/// integer. The result is text, nullable if any argument is.
858///
859/// # Examples
860///
861/// ```rust
862/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
863/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
864/// # #[derive(Clone, Debug)] struct Value(String);
865/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
866/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
867/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
868/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
869/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
870/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
871/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
872/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
873/// // The domain part of an email address.
874/// let domain = split_part(users.email, "@", 2);
875/// assert_eq!(domain.sql(), r#"SPLIT_PART ("users"."email", $1, $2)"#);
876/// ```
877#[allow(clippy::type_complexity)]
878pub fn split_part<'a, V, E, D, N>(
879 expr: E,
880 delimiter: D,
881 n: N,
882) -> SQLExpr<
883 'a,
884 V,
885 <V::DialectMarker as DialectTypes>::Text,
886 <<E::Nullable as Nullability>::Or<D::Nullable> as Nullability>::Or<N::Nullable>,
887 <<E::Aggregate as AggregateKind>::Or<D::Aggregate> as AggregateKind>::Or<N::Aggregate>,
888 (E::Sources, (D::Sources, N::Sources)),
889>
890where
891 V: SQLParam + 'a,
892 V::DialectMarker: DialectSupports<feature::PostgresString>,
893 E: Expr<'a, V>,
894 E::SQLType: Textual,
895 D: Expr<'a, V>,
896 D::SQLType: Textual,
897 D::Nullable: Nullability,
898 D::Aggregate: AggregateKind,
899 N: Expr<'a, V>,
900 N::SQLType: Integral,
901 N::Nullable: Nullability,
902 N::Aggregate: AggregateKind,
903{
904 SQLExpr::new(SQL::func(
905 "SPLIT_PART",
906 expr.into_sql()
907 .push(Token::COMMA)
908 .append(delimiter.into_sql())
909 .push(Token::COMMA)
910 .append(n.into_sql()),
911 ))
912}
913
914/// Pads a text value on the left to `length` characters (`LPAD`), on PostgreSQL and MySQL.
915///
916/// `expr` and `fill` must be text and `length` an integer. Longer values are
917/// cut to `length`. The result is text, nullable if any argument is.
918///
919/// # Examples
920///
921/// ```rust
922/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
923/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
924/// # #[derive(Clone, Debug)] struct Value(String);
925/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
926/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
927/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
928/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
929/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
930/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
931/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
932/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
933/// let padded = lpad(users.name, 10, ".");
934/// assert_eq!(padded.sql(), r#"LPAD ("users"."name", $1, $2)"#);
935/// ```
936#[allow(clippy::type_complexity)]
937pub fn lpad<'a, V, E, L, F>(
938 expr: E,
939 length: L,
940 fill: F,
941) -> SQLExpr<
942 'a,
943 V,
944 <V::DialectMarker as DialectTypes>::Text,
945 <<E::Nullable as Nullability>::Or<L::Nullable> as Nullability>::Or<F::Nullable>,
946 <<E::Aggregate as AggregateKind>::Or<L::Aggregate> as AggregateKind>::Or<F::Aggregate>,
947 (E::Sources, (L::Sources, F::Sources)),
948>
949where
950 V: SQLParam + 'a,
951 V::DialectMarker: DialectSupports<feature::Pad>,
952 E: Expr<'a, V>,
953 E::SQLType: Textual,
954 L: Expr<'a, V>,
955 L::SQLType: Integral,
956 L::Nullable: Nullability,
957 L::Aggregate: AggregateKind,
958 F: Expr<'a, V>,
959 F::SQLType: Textual,
960 F::Nullable: Nullability,
961 F::Aggregate: AggregateKind,
962{
963 SQLExpr::new(SQL::func(
964 "LPAD",
965 expr.into_sql()
966 .push(Token::COMMA)
967 .append(length.into_sql())
968 .push(Token::COMMA)
969 .append(fill.into_sql()),
970 ))
971}
972
973/// Pads a text value on the right to `length` characters (`RPAD`), on PostgreSQL and MySQL.
974///
975/// `expr` and `fill` must be text and `length` an integer. Longer values are
976/// cut to `length`. The result is text, nullable if any argument is.
977///
978/// # Examples
979///
980/// ```rust
981/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
982/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
983/// # #[derive(Clone, Debug)] struct Value(String);
984/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
985/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
986/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
987/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
988/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
989/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
990/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
991/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
992/// let padded = rpad(users.name, 10, ".");
993/// assert_eq!(padded.sql(), r#"RPAD ("users"."name", $1, $2)"#);
994/// ```
995#[allow(clippy::type_complexity)]
996pub fn rpad<'a, V, E, L, F>(
997 expr: E,
998 length: L,
999 fill: F,
1000) -> SQLExpr<
1001 'a,
1002 V,
1003 <V::DialectMarker as DialectTypes>::Text,
1004 <<E::Nullable as Nullability>::Or<L::Nullable> as Nullability>::Or<F::Nullable>,
1005 <<E::Aggregate as AggregateKind>::Or<L::Aggregate> as AggregateKind>::Or<F::Aggregate>,
1006 (E::Sources, (L::Sources, F::Sources)),
1007>
1008where
1009 V: SQLParam + 'a,
1010 V::DialectMarker: DialectSupports<feature::Pad>,
1011 E: Expr<'a, V>,
1012 E::SQLType: Textual,
1013 L: Expr<'a, V>,
1014 L::SQLType: Integral,
1015 L::Nullable: Nullability,
1016 L::Aggregate: AggregateKind,
1017 F: Expr<'a, V>,
1018 F::SQLType: Textual,
1019 F::Nullable: Nullability,
1020 F::Aggregate: AggregateKind,
1021{
1022 SQLExpr::new(SQL::func(
1023 "RPAD",
1024 expr.into_sql()
1025 .push(Token::COMMA)
1026 .append(length.into_sql())
1027 .push(Token::COMMA)
1028 .append(fill.into_sql()),
1029 ))
1030}
1031
1032/// Capitalizes the first letter of each word (`INITCAP`), on PostgreSQL.
1033///
1034/// The argument must be text. The result is text and keeps the argument's
1035/// nullability.
1036///
1037/// # Examples
1038///
1039/// ```rust
1040/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
1041/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
1042/// # #[derive(Clone, Debug)] struct Value(String);
1043/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
1044/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
1045/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
1046/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
1047/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
1048/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
1049/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
1050/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
1051/// assert_eq!(initcap(users.name).sql(), r#"INITCAP ("users"."name")"#);
1052/// ```
1053#[allow(clippy::type_complexity)]
1054pub fn initcap<'a, V, E>(
1055 expr: E,
1056) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Text, E::Nullable, E::Aggregate, E::Sources>
1057where
1058 V: SQLParam + 'a,
1059 V::DialectMarker: DialectSupports<feature::PostgresString>,
1060 E: Expr<'a, V>,
1061 E::SQLType: Textual,
1062{
1063 SQLExpr::new(SQL::func("INITCAP", expr.into_sql()))
1064}
1065
1066/// Reverses a text value (`REVERSE`), on PostgreSQL and MySQL.
1067///
1068/// The argument must be text. The result is text and keeps the argument's
1069/// nullability.
1070///
1071/// # Examples
1072///
1073/// ```rust
1074/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
1075/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
1076/// # #[derive(Clone, Debug)] struct Value(String);
1077/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
1078/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
1079/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
1080/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
1081/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
1082/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
1083/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
1084/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
1085/// assert_eq!(reverse(users.name).sql(), r#"REVERSE ("users"."name")"#);
1086/// ```
1087#[allow(clippy::type_complexity)]
1088pub fn reverse<'a, V, E>(
1089 expr: E,
1090) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Text, E::Nullable, E::Aggregate, E::Sources>
1091where
1092 V: SQLParam + 'a,
1093 V::DialectMarker: DialectSupports<feature::Reverse>,
1094 E: Expr<'a, V>,
1095 E::SQLType: Textual,
1096{
1097 SQLExpr::new(SQL::func("REVERSE", expr.into_sql()))
1098}
1099
1100/// Repeats a text value `n` times (`REPEAT`), on PostgreSQL and MySQL.
1101///
1102/// `expr` must be text and `n` an integer. The result is text, nullable if
1103/// either argument is.
1104///
1105/// # Examples
1106///
1107/// ```rust
1108/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
1109/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
1110/// # #[derive(Clone, Debug)] struct Value(String);
1111/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
1112/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
1113/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
1114/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
1115/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
1116/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
1117/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
1118/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
1119/// assert_eq!(repeat(users.name, 2).sql(), r#"REPEAT ("users"."name", $1)"#);
1120/// ```
1121#[allow(clippy::type_complexity)]
1122pub fn repeat<'a, V, E, N>(
1123 expr: E,
1124 n: N,
1125) -> SQLExpr<
1126 'a,
1127 V,
1128 <V::DialectMarker as DialectTypes>::Text,
1129 <E::Nullable as Nullability>::Or<N::Nullable>,
1130 <E::Aggregate as AggregateKind>::Or<N::Aggregate>,
1131 (E::Sources, N::Sources),
1132>
1133where
1134 V: SQLParam + 'a,
1135 V::DialectMarker: DialectSupports<feature::Repeat>,
1136 E: Expr<'a, V>,
1137 E::SQLType: Textual,
1138 N: Expr<'a, V>,
1139 N::SQLType: Integral,
1140 N::Nullable: Nullability,
1141 N::Aggregate: AggregateKind,
1142{
1143 SQLExpr::new(SQL::func(
1144 "REPEAT",
1145 expr.into_sql().push(Token::COMMA).append(n.into_sql()),
1146 ))
1147}
1148
1149/// Whether a text value starts with `prefix` (`STARTS_WITH`), on PostgreSQL.
1150///
1151/// Both arguments must be text. Like the comparison operators, the result is
1152/// the dialect's boolean, NULL when either argument is.
1153///
1154/// # Examples
1155///
1156/// ```rust
1157/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
1158/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
1159/// # #[derive(Clone, Debug)] struct Value(String);
1160/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
1161/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
1162/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
1163/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
1164/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
1165/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
1166/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
1167/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
1168/// let cond = starts_with(users.email, "admin");
1169/// assert_eq!(cond.sql(), r#"STARTS_WITH ("users"."email", $1)"#);
1170/// ```
1171#[allow(clippy::type_complexity)]
1172pub fn starts_with<'a, V, E, P>(
1173 expr: E,
1174 prefix: P,
1175) -> SQLExpr<
1176 'a,
1177 V,
1178 <V::DialectMarker as DialectTypes>::Bool,
1179 NonNull,
1180 <E::Aggregate as AggregateKind>::Or<P::Aggregate>,
1181 (Arg<E::Nullable, E::Sources>, Arg<P::Nullable, P::Sources>),
1182>
1183where
1184 V: SQLParam + 'a,
1185 V::DialectMarker: DialectSupports<feature::PostgresString>,
1186 E: Expr<'a, V>,
1187 E::SQLType: Textual,
1188 P: Expr<'a, V>,
1189 P::SQLType: Textual,
1190 P::Aggregate: AggregateKind,
1191{
1192 SQLExpr::new(SQL::func(
1193 "STARTS_WITH",
1194 expr.into_sql().push(Token::COMMA).append(prefix.into_sql()),
1195 ))
1196}
1197
1198// =============================================================================
1199// CHAR_LENGTH / OCTET_LENGTH (Standard SQL)
1200// =============================================================================
1201
1202/// Number of characters in a text value.
1203///
1204/// Renders `CHAR_LENGTH(expr)` on PostgreSQL and MySQL and `LENGTH(expr)` on
1205/// SQLite, which counts characters. The argument must be text. The result is
1206/// an integer (see [`LengthPolicy`]) and keeps the argument's nullability.
1207///
1208/// # Examples
1209///
1210/// ```rust
1211/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
1212/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
1213/// # #[derive(Clone, Debug)] struct Value(String);
1214/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
1215/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
1216/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
1217/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
1218/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
1219/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
1220/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
1221/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
1222/// // SQLite
1223/// assert_eq!(char_length(users.name).sql(), r#"LENGTH ("users"."name")"#);
1224/// ```
1225#[allow(clippy::type_complexity)]
1226pub fn char_length<'a, V, E>(
1227 expr: E,
1228) -> SQLExpr<
1229 'a,
1230 V,
1231 <E::SQLType as LengthPolicy<V::DialectMarker>>::Output,
1232 E::Nullable,
1233 E::Aggregate,
1234 E::Sources,
1235>
1236where
1237 V: SQLParam + 'a,
1238 E: Expr<'a, V>,
1239 E::SQLType: LengthPolicy<V::DialectMarker>,
1240{
1241 SQLExpr::new(SQL::func(
1242 <V::DialectMarker as DialectTypes>::CHAR_LENGTH_FN,
1243 expr.into_sql(),
1244 ))
1245}
1246
1247/// Number of bytes in a text value (`OCTET_LENGTH`).
1248///
1249/// Needs SQLite 3.43 or later; also available on PostgreSQL and MySQL. The
1250/// argument must be text. The result is an integer (see [`LengthPolicy`]) and
1251/// keeps the argument's nullability.
1252///
1253/// # Examples
1254///
1255/// ```rust
1256/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
1257/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
1258/// # #[derive(Clone, Debug)] struct Value(String);
1259/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
1260/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
1261/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
1262/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
1263/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
1264/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
1265/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
1266/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
1267/// assert_eq!(octet_length(users.name).sql(), r#"OCTET_LENGTH ("users"."name")"#);
1268/// ```
1269#[allow(clippy::type_complexity)]
1270pub fn octet_length<'a, V, E>(
1271 expr: E,
1272) -> SQLExpr<
1273 'a,
1274 V,
1275 <E::SQLType as LengthPolicy<V::DialectMarker>>::Output,
1276 E::Nullable,
1277 E::Aggregate,
1278 E::Sources,
1279>
1280where
1281 V: SQLParam + 'a,
1282 E: Expr<'a, V>,
1283 E::SQLType: LengthPolicy<V::DialectMarker>,
1284{
1285 SQLExpr::new(SQL::func("OCTET_LENGTH", expr.into_sql()))
1286}
1287
1288// =============================================================================
1289// TRANSLATE (PostgreSQL)
1290// =============================================================================
1291
1292/// Replaces characters one for one (`TRANSLATE`), on PostgreSQL.
1293///
1294/// Each character of `from` is replaced by the character at the same position
1295/// in `to`; characters of `from` with no partner in `to` are removed. All
1296/// arguments must be text. The result is text and keeps `expr`'s
1297/// nullability.
1298///
1299/// # Examples
1300///
1301/// ```rust
1302/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
1303/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
1304/// # #[derive(Clone, Debug)] struct Value(String);
1305/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
1306/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
1307/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
1308/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
1309/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
1310/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
1311/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
1312/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
1313/// // Strip "(", ")" and "-".
1314/// let digits = translate(users.name, "()-", "");
1315/// assert_eq!(digits.sql(), r#"TRANSLATE ("users"."name", $1, $2)"#);
1316/// ```
1317#[allow(clippy::type_complexity)]
1318pub fn translate<'a, V, E, F, T>(
1319 expr: E,
1320 from: F,
1321 to: T,
1322) -> SQLExpr<
1323 'a,
1324 V,
1325 <V::DialectMarker as DialectTypes>::Text,
1326 E::Nullable,
1327 <<E::Aggregate as AggregateKind>::Or<F::Aggregate> as AggregateKind>::Or<T::Aggregate>,
1328 (E::Sources, (F::Sources, T::Sources)),
1329>
1330where
1331 V: SQLParam + 'a,
1332 V::DialectMarker: DialectSupports<feature::PostgresString>,
1333 E: Expr<'a, V>,
1334 E::SQLType: Textual,
1335 F: Expr<'a, V>,
1336 F::SQLType: Textual,
1337 F::Aggregate: AggregateKind,
1338 T: Expr<'a, V>,
1339 T::SQLType: Textual,
1340 T::Aggregate: AggregateKind,
1341{
1342 SQLExpr::new(SQL::func(
1343 "TRANSLATE",
1344 expr.into_sql()
1345 .push(Token::COMMA)
1346 .append(from.into_sql())
1347 .push(Token::COMMA)
1348 .append(to.into_sql()),
1349 ))
1350}
1351
1352// =============================================================================
1353// REGEXP_REPLACE / REGEXP_MATCH (PostgreSQL)
1354// =============================================================================
1355
1356/// Replaces the first POSIX regular expression match (`REGEXP_REPLACE`), on PostgreSQL.
1357///
1358/// All arguments must be text. The result is text and keeps `expr`'s
1359/// nullability. To replace every match, use [`regexp_replace_flags`] with
1360/// `"g"`.
1361///
1362/// # Examples
1363///
1364/// ```rust
1365/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
1366/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
1367/// # #[derive(Clone, Debug)] struct Value(String);
1368/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
1369/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
1370/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
1371/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
1372/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
1373/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
1374/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
1375/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
1376/// let cleaned = regexp_replace(users.name, "[^a-z]", "");
1377/// assert_eq!(cleaned.sql(), r#"REGEXP_REPLACE ("users"."name", $1, $2)"#);
1378/// ```
1379#[allow(clippy::type_complexity)]
1380pub fn regexp_replace<'a, V, E, P, R>(
1381 expr: E,
1382 pattern: P,
1383 replacement: R,
1384) -> SQLExpr<
1385 'a,
1386 V,
1387 <V::DialectMarker as DialectTypes>::Text,
1388 E::Nullable,
1389 <<E::Aggregate as AggregateKind>::Or<P::Aggregate> as AggregateKind>::Or<R::Aggregate>,
1390 (E::Sources, (P::Sources, R::Sources)),
1391>
1392where
1393 V: SQLParam + 'a,
1394 V::DialectMarker: DialectSupports<feature::PostgresString>,
1395 E: Expr<'a, V>,
1396 E::SQLType: Textual,
1397 P: Expr<'a, V>,
1398 P::SQLType: Textual,
1399 P::Aggregate: AggregateKind,
1400 R: Expr<'a, V>,
1401 R::SQLType: Textual,
1402 R::Aggregate: AggregateKind,
1403{
1404 SQLExpr::new(SQL::func(
1405 "REGEXP_REPLACE",
1406 expr.into_sql()
1407 .push(Token::COMMA)
1408 .append(pattern.into_sql())
1409 .push(Token::COMMA)
1410 .append(replacement.into_sql()),
1411 ))
1412}
1413
1414/// Replaces POSIX regular expression matches, with flags, on PostgreSQL.
1415///
1416/// Like [`regexp_replace`] with a fourth argument: renders
1417/// `REGEXP_REPLACE(expr, pattern, replacement, flags)`.
1418///
1419/// Common flags: `"g"` (every match), `"i"` (ignore case), `"gi"` (both). All
1420/// arguments must be text. The result is text and keeps `expr`'s
1421/// nullability.
1422///
1423/// # Examples
1424///
1425/// ```rust
1426/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
1427/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
1428/// # #[derive(Clone, Debug)] struct Value(String);
1429/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
1430/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
1431/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
1432/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
1433/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
1434/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
1435/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
1436/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
1437/// let digits = regexp_replace_flags(users.name, "[^0-9]", "", "g");
1438/// assert_eq!(digits.sql(), r#"REGEXP_REPLACE ("users"."name", $1, $2, $3)"#);
1439/// ```
1440#[allow(clippy::type_complexity)]
1441pub fn regexp_replace_flags<'a, V, E, P, R, F>(
1442 expr: E,
1443 pattern: P,
1444 replacement: R,
1445 flags: F,
1446) -> SQLExpr<
1447 'a,
1448 V,
1449 <V::DialectMarker as DialectTypes>::Text,
1450 E::Nullable,
1451 <<<E::Aggregate as AggregateKind>::Or<P::Aggregate> as AggregateKind>::Or<R::Aggregate> as AggregateKind>::Or<F::Aggregate,>,
1452 (E::Sources, (P::Sources, (R::Sources, F::Sources))),
1453>
1454where
1455 V: SQLParam + 'a,
1456 V::DialectMarker: DialectSupports<feature::PostgresString>,
1457 E: Expr<'a, V>,
1458 E::SQLType: Textual,
1459 P: Expr<'a, V>,
1460 P::SQLType: Textual,
1461 P::Aggregate: AggregateKind,
1462 R: Expr<'a, V>,
1463 R::SQLType: Textual,
1464 R::Aggregate: AggregateKind,
1465 F: Expr<'a, V>,
1466 F::SQLType: Textual,
1467 F::Aggregate: AggregateKind,
1468{
1469 SQLExpr::new(SQL::func(
1470 "REGEXP_REPLACE",
1471 expr.into_sql()
1472 .push(Token::COMMA)
1473 .append(pattern.into_sql())
1474 .push(Token::COMMA)
1475 .append(replacement.into_sql())
1476 .push(Token::COMMA)
1477 .append(flags.into_sql()),
1478 ))
1479}
1480
1481/// Capture groups of the first POSIX regular expression match (`REGEXP_MATCH`), on PostgreSQL.
1482///
1483/// Returns a text array with one element per capture group, or the whole
1484/// match when the pattern has no groups. Both arguments must be text. The
1485/// result is always nullable: it is NULL when nothing matches.
1486///
1487/// # Examples
1488///
1489/// ```rust
1490/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
1491/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
1492/// # #[derive(Clone, Debug)] struct Value(String);
1493/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
1494/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
1495/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
1496/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
1497/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
1498/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
1499/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
1500/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
1501/// let parts = regexp_match(users.email, "(.+)@(.+)");
1502/// assert_eq!(parts.sql(), r#"REGEXP_MATCH ("users"."email", $1)"#);
1503/// ```
1504#[allow(clippy::type_complexity)]
1505pub fn regexp_match<'a, V, E, P>(
1506 expr: E,
1507 pattern: P,
1508) -> SQLExpr<
1509 'a,
1510 V,
1511 crate::types::Array<<V::DialectMarker as DialectTypes>::Text>,
1512 super::Null,
1513 <E::Aggregate as AggregateKind>::Or<P::Aggregate>,
1514 (E::Sources, P::Sources),
1515>
1516where
1517 V: SQLParam + 'a,
1518 V::DialectMarker: DialectSupports<feature::PostgresString>,
1519 E: Expr<'a, V>,
1520 E::SQLType: Textual,
1521 P: Expr<'a, V>,
1522 P::SQLType: Textual,
1523 P::Aggregate: AggregateKind,
1524{
1525 SQLExpr::new(SQL::func(
1526 "REGEXP_MATCH",
1527 expr.into_sql()
1528 .push(Token::COMMA)
1529 .append(pattern.into_sql()),
1530 ))
1531}
1532
1533/// Capture groups of the first POSIX regular expression match, with flags,
1534/// on PostgreSQL.
1535///
1536/// Like [`regexp_match`] with a third argument: renders
1537/// `REGEXP_MATCH(expr, pattern, flags)`.
1538///
1539/// A common flag is `"i"` (ignore case). The `"g"` flag is not allowed here.
1540/// All arguments must be text. The result is a nullable text array.
1541///
1542/// # Examples
1543///
1544/// ```rust
1545/// # use drizzle_core::dialect::{Dialect, DialectTypes, PostgresDialect as D};
1546/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
1547/// # #[derive(Clone, Debug)] struct Value(String);
1548/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::PostgreSQL; type DialectMarker = D; }
1549/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
1550/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
1551/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
1552/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
1553/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
1554/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
1555/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
1556/// let parts = regexp_match_flags(users.email, "(.+)@(.+)", "i");
1557/// assert_eq!(parts.sql(), r#"REGEXP_MATCH ("users"."email", $1, $2)"#);
1558/// ```
1559#[allow(clippy::type_complexity)]
1560pub fn regexp_match_flags<'a, V, E, P, F>(
1561 expr: E,
1562 pattern: P,
1563 flags: F,
1564) -> SQLExpr<
1565 'a,
1566 V,
1567 crate::types::Array<<V::DialectMarker as DialectTypes>::Text>,
1568 super::Null,
1569 <<E::Aggregate as AggregateKind>::Or<P::Aggregate> as AggregateKind>::Or<F::Aggregate>,
1570 (E::Sources, (P::Sources, F::Sources)),
1571>
1572where
1573 V: SQLParam + 'a,
1574 V::DialectMarker: DialectSupports<feature::PostgresString>,
1575 E: Expr<'a, V>,
1576 E::SQLType: Textual,
1577 P: Expr<'a, V>,
1578 P::SQLType: Textual,
1579 P::Aggregate: AggregateKind,
1580 F: Expr<'a, V>,
1581 F::SQLType: Textual,
1582 F::Aggregate: AggregateKind,
1583{
1584 SQLExpr::new(SQL::func(
1585 "REGEXP_MATCH",
1586 expr.into_sql()
1587 .push(Token::COMMA)
1588 .append(pattern.into_sql())
1589 .push(Token::COMMA)
1590 .append(flags.into_sql()),
1591 ))
1592}