Skip to main content

drizzle_core/expr/
util.rs

1//! Utility SQL functions (alias, cast, distinct, typeof, concat, excluded).
2
3use crate::dialect::{PostgresDialect, SQLiteDialect};
4use crate::sql::{SQL, Token};
5use crate::traits::{SQLColumnInfo, SQLParam, ToSQL};
6use crate::types::{Compatible, DataType, Textual};
7
8use super::{AggOr, AggregateKind, Expr, NonNull, Null, NullOr, Nullability, SQLExpr, Scalar};
9
10// =============================================================================
11// ALIAS
12// =============================================================================
13
14/// An expression aliased with `AS "name"`.
15///
16/// Preserves the original expression's type information (`ExprValueType`,
17/// `Expr`, etc.) so that aliased columns in SELECT tuples still infer
18/// the correct row type.
19#[derive(Clone, Copy, Debug)]
20pub struct AliasedExpr<E> {
21    pub(crate) expr: E,
22    pub(crate) name: &'static str,
23}
24
25impl<'a, V, E> ToSQL<'a, V> for AliasedExpr<E>
26where
27    V: SQLParam + 'a,
28    E: ToSQL<'a, V>,
29{
30    fn to_sql(&self) -> SQL<'a, V> {
31        self.expr.to_sql().alias(self.name)
32    }
33
34    fn into_sql(self) -> SQL<'a, V> {
35        self.expr.into_sql().alias(self.name)
36    }
37}
38
39impl<'a, V, E> Expr<'a, V> for AliasedExpr<E>
40where
41    V: SQLParam + 'a,
42    E: Expr<'a, V>,
43{
44    type SQLType = E::SQLType;
45    type Nullable = E::Nullable;
46    type Aggregate = E::Aggregate;
47
48    fn to_expr_sql(&self) -> SQL<'a, V> {
49        self.expr.to_expr_sql().alias(self.name)
50    }
51
52    fn into_expr_sql(self) -> SQL<'a, V> {
53        self.expr.into_expr_sql().alias(self.name)
54    }
55}
56
57impl<E: super::HasAggStatus> super::HasAggStatus for AliasedExpr<E> {
58    type Status = E::Status;
59}
60
61impl<E: crate::row::ExprValueType> crate::row::ExprValueType for AliasedExpr<E> {
62    type ValueType = E::ValueType;
63}
64
65impl<E> crate::row::IntoSelectTarget for AliasedExpr<E>
66where
67    E: crate::row::ExprValueType,
68{
69    type Marker = crate::row::SelectCols<(Self,)>;
70}
71
72/// Extension trait providing `.alias()` method syntax on any expression.
73///
74/// This is a blanket impl on all `Sized` types. The `AliasedExpr` it creates
75/// is only useful when the inner type implements `ToSQL`/`Expr`/`ExprValueType`,
76/// so calling `.alias()` on non-SQL types is harmless but useless.
77///
78/// For `SQL<'a, V>` values, the inherent `SQL::alias()` method takes
79/// precedence and returns `SQL<'a, V>` (no type preservation needed for raw SQL).
80pub trait AliasExt: Sized {
81    fn alias(self, name: &'static str) -> AliasedExpr<Self> {
82        AliasedExpr { expr: self, name }
83    }
84}
85
86impl<T: Sized> AliasExt for T {}
87
88/// Create an aliased expression.
89///
90/// # Example
91///
92/// ```rust
93/// # let _ = r####"
94/// use drizzle_core::expr::alias;
95///
96/// // SELECT users.first_name || users.last_name AS full_name
97/// let full_name = alias(string_concat(users.first_name, users.last_name), "full_name");
98/// # "####;
99/// ```
100pub const fn alias<E>(expr: E, name: &'static str) -> AliasedExpr<E> {
101    AliasedExpr { expr, name }
102}
103
104// =============================================================================
105// TYPEOF
106// =============================================================================
107
108/// Get the SQL type of an expression.
109///
110/// Returns the data type name as text.
111///
112/// # Example
113///
114/// ```rust
115/// # let _ = r####"
116/// use drizzle_core::expr::typeof_;
117///
118/// // SELECT TYPEOF(users.age) -- returns "integer"
119/// let age_type = typeof_(users.age);
120/// # "####;
121/// ```
122pub fn typeof_<'a, V, E>(
123    expr: E,
124) -> SQLExpr<'a, V, <V::DialectMarker as crate::dialect::DialectTypes>::Text, NonNull, E::Aggregate>
125where
126    V: SQLParam + 'a,
127    E: Expr<'a, V>,
128{
129    SQLExpr::new(SQL::func("TYPEOF", expr.into_expr_sql()))
130}
131
132/// Alias for typeof_ (uses Rust raw identifier syntax).
133pub fn r#typeof<'a, V, E>(
134    expr: E,
135) -> SQLExpr<'a, V, <V::DialectMarker as crate::dialect::DialectTypes>::Text, NonNull, E::Aggregate>
136where
137    V: SQLParam + 'a,
138    E: Expr<'a, V>,
139{
140    typeof_(expr)
141}
142
143// =============================================================================
144// CAST
145// =============================================================================
146
147/// Default SQL cast type name for a type marker.
148pub trait DefaultCastTypeName: DataType {
149    const CAST_TYPE_NAME: &'static str;
150}
151
152impl DefaultCastTypeName for drizzle_types::sqlite::types::Integer {
153    const CAST_TYPE_NAME: &'static str = "INTEGER";
154}
155impl DefaultCastTypeName for drizzle_types::sqlite::types::Text {
156    const CAST_TYPE_NAME: &'static str = "TEXT";
157}
158impl DefaultCastTypeName for drizzle_types::sqlite::types::Real {
159    const CAST_TYPE_NAME: &'static str = "REAL";
160}
161impl DefaultCastTypeName for drizzle_types::sqlite::types::Blob {
162    const CAST_TYPE_NAME: &'static str = "BLOB";
163}
164impl DefaultCastTypeName for drizzle_types::sqlite::types::Numeric {
165    const CAST_TYPE_NAME: &'static str = "NUMERIC";
166}
167impl DefaultCastTypeName for drizzle_types::sqlite::types::Any {
168    const CAST_TYPE_NAME: &'static str = "ANY";
169}
170
171impl DefaultCastTypeName for drizzle_types::postgres::types::Int2 {
172    const CAST_TYPE_NAME: &'static str = "SMALLINT";
173}
174impl DefaultCastTypeName for drizzle_types::postgres::types::Int4 {
175    const CAST_TYPE_NAME: &'static str = "INTEGER";
176}
177impl DefaultCastTypeName for drizzle_types::postgres::types::Int8 {
178    const CAST_TYPE_NAME: &'static str = "BIGINT";
179}
180impl DefaultCastTypeName for drizzle_types::postgres::types::Float4 {
181    const CAST_TYPE_NAME: &'static str = "REAL";
182}
183impl DefaultCastTypeName for drizzle_types::postgres::types::Float8 {
184    const CAST_TYPE_NAME: &'static str = "DOUBLE PRECISION";
185}
186impl DefaultCastTypeName for drizzle_types::postgres::types::Varchar {
187    const CAST_TYPE_NAME: &'static str = "VARCHAR";
188}
189impl DefaultCastTypeName for drizzle_types::postgres::types::Text {
190    const CAST_TYPE_NAME: &'static str = "TEXT";
191}
192impl DefaultCastTypeName for drizzle_types::postgres::types::Char {
193    const CAST_TYPE_NAME: &'static str = "CHAR";
194}
195impl DefaultCastTypeName for drizzle_types::postgres::types::Bytea {
196    const CAST_TYPE_NAME: &'static str = "BYTEA";
197}
198impl DefaultCastTypeName for drizzle_types::postgres::types::Boolean {
199    const CAST_TYPE_NAME: &'static str = "BOOLEAN";
200}
201impl DefaultCastTypeName for drizzle_types::postgres::types::Timestamptz {
202    const CAST_TYPE_NAME: &'static str = "TIMESTAMPTZ";
203}
204impl DefaultCastTypeName for drizzle_types::postgres::types::Timestamp {
205    const CAST_TYPE_NAME: &'static str = "TIMESTAMP";
206}
207impl DefaultCastTypeName for drizzle_types::postgres::types::Date {
208    const CAST_TYPE_NAME: &'static str = "DATE";
209}
210impl DefaultCastTypeName for drizzle_types::postgres::types::Time {
211    const CAST_TYPE_NAME: &'static str = "TIME";
212}
213impl DefaultCastTypeName for drizzle_types::postgres::types::Timetz {
214    const CAST_TYPE_NAME: &'static str = "TIMETZ";
215}
216impl DefaultCastTypeName for drizzle_types::postgres::types::Numeric {
217    const CAST_TYPE_NAME: &'static str = "NUMERIC";
218}
219impl DefaultCastTypeName for drizzle_types::postgres::types::Uuid {
220    const CAST_TYPE_NAME: &'static str = "UUID";
221}
222impl DefaultCastTypeName for drizzle_types::postgres::types::Json {
223    const CAST_TYPE_NAME: &'static str = "JSON";
224}
225impl DefaultCastTypeName for drizzle_types::postgres::types::Jsonb {
226    const CAST_TYPE_NAME: &'static str = "JSONB";
227}
228impl DefaultCastTypeName for drizzle_types::postgres::types::Any {
229    const CAST_TYPE_NAME: &'static str = "ANY";
230}
231impl DefaultCastTypeName for drizzle_types::postgres::types::Interval {
232    const CAST_TYPE_NAME: &'static str = "INTERVAL";
233}
234impl DefaultCastTypeName for drizzle_types::postgres::types::Inet {
235    const CAST_TYPE_NAME: &'static str = "INET";
236}
237impl DefaultCastTypeName for drizzle_types::postgres::types::Cidr {
238    const CAST_TYPE_NAME: &'static str = "CIDR";
239}
240impl DefaultCastTypeName for drizzle_types::postgres::types::MacAddr {
241    const CAST_TYPE_NAME: &'static str = "MACADDR";
242}
243impl DefaultCastTypeName for drizzle_types::postgres::types::MacAddr8 {
244    const CAST_TYPE_NAME: &'static str = "MACADDR8";
245}
246impl DefaultCastTypeName for drizzle_types::postgres::types::Point {
247    const CAST_TYPE_NAME: &'static str = "POINT";
248}
249impl DefaultCastTypeName for drizzle_types::postgres::types::LineString {
250    const CAST_TYPE_NAME: &'static str = "PATH";
251}
252impl DefaultCastTypeName for drizzle_types::postgres::types::Rect {
253    const CAST_TYPE_NAME: &'static str = "BOX";
254}
255impl DefaultCastTypeName for drizzle_types::postgres::types::BitString {
256    const CAST_TYPE_NAME: &'static str = "BIT VARYING";
257}
258impl DefaultCastTypeName for drizzle_types::postgres::types::Line {
259    const CAST_TYPE_NAME: &'static str = "LINE";
260}
261impl DefaultCastTypeName for drizzle_types::postgres::types::LineSegment {
262    const CAST_TYPE_NAME: &'static str = "LSEG";
263}
264impl DefaultCastTypeName for drizzle_types::postgres::types::Polygon {
265    const CAST_TYPE_NAME: &'static str = "POLYGON";
266}
267impl DefaultCastTypeName for drizzle_types::postgres::types::Circle {
268    const CAST_TYPE_NAME: &'static str = "CIRCLE";
269}
270impl DefaultCastTypeName for drizzle_types::postgres::types::Enum {
271    const CAST_TYPE_NAME: &'static str = "TEXT";
272}
273
274/// Input accepted by [`cast`].
275///
276/// You can pass:
277/// - a SQL type string (dialect-specific), or
278/// - a type marker value (uses that marker's default SQL cast name).
279pub trait CastTarget<'a, T: DataType, D> {
280    fn cast_type_name(self) -> &'a str;
281}
282
283/// Additional cast safety policy by dialect.
284#[diagnostic::on_unimplemented(
285    message = "cannot cast `{Source}` to `{Target}` for this dialect",
286    label = "cast target is incompatible with source type",
287    note = "for SQLite strict typing, use a compatible cast target or cast through ANY/raw sql intentionally"
288)]
289pub trait CastTypePolicy<D, Source: DataType, Target: DataType> {}
290
291impl<Source: DataType + Compatible<Target>, Target: DataType>
292    CastTypePolicy<PostgresDialect, Source, Target> for ()
293{
294}
295
296impl<Source: DataType + Compatible<Target>, Target: DataType>
297    CastTypePolicy<SQLiteDialect, Source, Target> for ()
298{
299}
300
301impl<'a, T: DataType, D> CastTarget<'a, T, D> for &'a str {
302    fn cast_type_name(self) -> &'a str {
303        self
304    }
305}
306
307impl<'a, T, D> CastTarget<'a, T, D> for T
308where
309    T: DataType + DefaultCastTypeName,
310{
311    fn cast_type_name(self) -> &'a str {
312        T::CAST_TYPE_NAME
313    }
314}
315
316/// Cast an expression to a different type.
317///
318/// The target type marker specifies the result type for the type system.
319/// The cast target may be:
320/// - a SQL type string (`"INTEGER"`, `"int4"`, `"VARCHAR(255)"`), or
321/// - a type marker value (`Int`, `Text`, `drizzle::sqlite::types::Integer`, ...).
322///
323/// Preserves the input expression's nullability and aggregate marker.
324///
325/// # Example
326///
327/// ```rust
328/// # let _ = r####"
329/// use drizzle_core::expr::cast;
330/// use drizzle_core::types::{Int, Text};
331///
332/// // SELECT CAST(users.age AS TEXT)
333/// let age_text = cast::<_, _, Text>(users.age, Text);
334///
335/// // Explicit SQL type name (dialect-specific)
336/// let age_text = cast::<_, _, Text>(users.age, "VARCHAR(255)");
337/// let age_int = cast::<_, _, Int>(users.age, "int4");
338/// # "####;
339/// ```
340pub fn cast<'a, V, E, Target>(
341    expr: E,
342    target_type: impl CastTarget<'a, Target, V::DialectMarker>,
343) -> SQLExpr<'a, V, Target, E::Nullable, E::Aggregate>
344where
345    V: SQLParam + 'a,
346    E: Expr<'a, V>,
347    Target: DataType,
348    (): CastTypePolicy<V::DialectMarker, E::SQLType, Target>,
349{
350    SQLExpr::new(SQL::func(
351        "CAST",
352        expr.into_expr_sql()
353            .push(Token::AS)
354            .append(SQL::raw(target_type.cast_type_name())),
355    ))
356}
357
358// =============================================================================
359// STRING CONCATENATION
360// =============================================================================
361
362/// Concatenate two string expressions using || operator.
363///
364/// Requires both operands to be `Textual` (Text or `VarChar`).
365/// Nullability follows SQL concatenation rules: nullable input -> nullable output.
366///
367/// # Type Safety
368///
369/// ```rust
370/// # let _ = r####"
371/// // ✅ OK: Both are Text
372/// string_concat(users.first_name, users.last_name);
373///
374/// // ✅ OK: Text with string literal
375/// string_concat(users.first_name, " ");
376///
377/// // ❌ Compile error: Int is not Textual
378/// string_concat(users.id, users.name);
379/// # "####;
380/// ```
381///
382/// # Example
383///
384/// ```rust
385/// # let _ = r####"
386/// use drizzle_core::expr::string_concat;
387///
388/// // SELECT users.first_name || ' ' || users.last_name
389/// let full_name = string_concat(string_concat(users.first_name, " "), users.last_name);
390/// # "####;
391/// ```
392#[allow(clippy::type_complexity)]
393pub fn string_concat<'a, V, L, R>(
394    left: L,
395    right: R,
396) -> SQLExpr<
397    'a,
398    V,
399    <V::DialectMarker as crate::dialect::DialectTypes>::Text,
400    <L::Nullable as NullOr<R::Nullable>>::Output,
401    <L::Aggregate as AggOr<R::Aggregate>>::Output,
402>
403where
404    V: SQLParam + 'a,
405    L: Expr<'a, V>,
406    R: Expr<'a, V>,
407    L::SQLType: Textual,
408    R::SQLType: Textual,
409    L::Nullable: NullOr<R::Nullable>,
410    R::Nullable: Nullability,
411    L::Aggregate: AggOr<R::Aggregate>,
412    R::Aggregate: AggregateKind,
413{
414    super::concat(left, right)
415}
416
417// =============================================================================
418// RAW SQL Expression
419// =============================================================================
420
421/// Create a raw SQL expression with a specified type.
422///
423/// Use this for dialect-specific features or when the type system
424/// can't infer the correct type.
425///
426/// # Safety
427///
428/// This bypasses type checking. Use sparingly and only when necessary.
429///
430/// # Example
431///
432/// ```rust
433/// # let _ = r####"
434/// use drizzle_core::expr::raw;
435/// use drizzle_core::types::Int;
436///
437/// let expr = raw::<_, Int>("RANDOM()");
438/// # "####;
439/// ```
440#[must_use]
441pub fn raw<'a, V, T>(sql: &'a str) -> SQLExpr<'a, V, T, Null, Scalar>
442where
443    V: SQLParam + 'a,
444    T: DataType,
445{
446    SQLExpr::new(SQL::raw(sql))
447}
448
449/// Create a raw SQL expression with explicit nullable nullability.
450#[must_use]
451pub fn raw_nullable<'a, V, T>(sql: &'a str) -> SQLExpr<'a, V, T, Null, Scalar>
452where
453    V: SQLParam + 'a,
454    T: DataType,
455{
456    SQLExpr::new(SQL::raw(sql))
457}
458
459/// Create a raw SQL expression with explicit non-null nullability.
460#[must_use]
461pub fn raw_non_null<'a, V, T>(sql: &'a str) -> SQLExpr<'a, V, T, NonNull, Scalar>
462where
463    V: SQLParam + 'a,
464    T: DataType,
465{
466    SQLExpr::new(SQL::raw(sql))
467}
468
469// =============================================================================
470// EXCLUDED (for ON CONFLICT DO UPDATE)
471// =============================================================================
472
473/// Wraps a column to reference its value from the proposed insert row
474/// (the EXCLUDED row in ON CONFLICT DO UPDATE SET).
475#[derive(Clone, Copy, Debug)]
476pub struct Excluded<C> {
477    column: C,
478}
479
480/// Reference a column's value from the proposed insert row (EXCLUDED).
481///
482/// Used in ON CONFLICT DO UPDATE SET to reference the value that would
483/// have been inserted.
484///
485/// # Example
486/// ```rust
487/// # let _ = r####"
488/// db.insert(simple)
489///     .values([InsertSimple::new("test").with_id(1)])
490///     .on_conflict(simple.id)
491///     .do_update(UpdateSimple::default().with_name(excluded(simple.name)));
492/// // Generates: ... ON CONFLICT ("id") DO UPDATE SET "name" = EXCLUDED."name"
493/// # "####;
494/// ```
495pub const fn excluded<C>(column: C) -> Excluded<C> {
496    Excluded { column }
497}
498
499impl<'a, V, C> Expr<'a, V> for Excluded<C>
500where
501    V: SQLParam + 'a,
502    C: Expr<'a, V> + SQLColumnInfo,
503{
504    type SQLType = C::SQLType;
505    type Nullable = C::Nullable;
506    type Aggregate = C::Aggregate;
507}
508
509impl<'a, V, C> ToSQL<'a, V> for Excluded<C>
510where
511    V: SQLParam + 'a,
512    C: SQLColumnInfo,
513{
514    fn to_sql(&self) -> SQL<'a, V> {
515        SQL::empty()
516            .push(Token::EXCLUDED)
517            .push(Token::DOT)
518            .append(SQL::ident(self.column.name()))
519    }
520}