Skip to main content

drizzle_core/expr/
util.rs

1//! Utility SQL functions (alias, cast, distinct, typeof, concat, excluded).
2
3use crate::dialect::{MySQLDialect, 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/// An expression whose output name is represented by a type-level tag.
105///
106/// Unlike [`AliasedExpr`], this form can be projected from a derived table
107/// because the output name remains available in the expression's type.
108#[derive(Clone, Copy, Debug)]
109pub struct NamedExpr<E, Name> {
110    pub(crate) expr: E,
111    pub(crate) name: core::marker::PhantomData<Name>,
112}
113
114impl<E, Name> NamedExpr<E, Name> {
115    /// Returns the wrapped expression.
116    pub const fn expression(&self) -> &E {
117        &self.expr
118    }
119
120    /// Returns the wrapped expression by value.
121    pub fn into_expression(self) -> E {
122        self.expr
123    }
124}
125
126impl<'a, V, E, Name> ToSQL<'a, V> for NamedExpr<E, Name>
127where
128    V: SQLParam + 'a,
129    E: ToSQL<'a, V>,
130    Name: crate::Tag,
131{
132    fn to_sql(&self) -> SQL<'a, V> {
133        self.expr.to_sql().alias(Name::NAME)
134    }
135
136    fn into_sql(self) -> SQL<'a, V> {
137        self.expr.into_sql().alias(Name::NAME)
138    }
139}
140
141impl<'a, V, E, Name> Expr<'a, V> for NamedExpr<E, Name>
142where
143    V: SQLParam + 'a,
144    E: Expr<'a, V>,
145    Name: crate::Tag,
146{
147    type SQLType = E::SQLType;
148    type Nullable = E::Nullable;
149    type Aggregate = E::Aggregate;
150
151    fn to_expr_sql(&self) -> SQL<'a, V> {
152        self.expr.to_expr_sql().alias(Name::NAME)
153    }
154
155    fn into_expr_sql(self) -> SQL<'a, V> {
156        self.expr.into_expr_sql().alias(Name::NAME)
157    }
158}
159
160impl<E, Name> super::HasAggStatus for NamedExpr<E, Name>
161where
162    E: super::HasAggStatus,
163{
164    type Status = E::Status;
165}
166
167impl<E, Name> crate::row::ExprValueType for NamedExpr<E, Name>
168where
169    E: crate::row::ExprValueType,
170{
171    type ValueType = E::ValueType;
172}
173
174impl<E, Name> crate::row::IntoSelectTarget for NamedExpr<E, Name>
175where
176    E: crate::row::ExprValueType,
177{
178    type Marker = crate::row::SelectCols<(Self,)>;
179}
180
181impl<E, Name> crate::row::GroupByIdentity for NamedExpr<E, Name>
182where
183    E: crate::row::GroupByIdentity,
184{
185    type Identity = E::Identity;
186}
187
188/// Extension trait providing a static output name for derived projections.
189pub trait NamedExt: Sized {
190    /// Names this expression with a type-level [`crate::Tag`].
191    fn named<Name: crate::Tag>(self) -> NamedExpr<Self, Name> {
192        NamedExpr {
193            expr: self,
194            name: core::marker::PhantomData,
195        }
196    }
197}
198
199impl<T: crate::row::ExprValueType> NamedExt for T {}
200
201// =============================================================================
202// TYPEOF
203// =============================================================================
204
205#[diagnostic::on_unimplemented(
206    message = "TYPEOF is not available for this dialect",
207    label = "use a dialect-specific type inspection expression"
208)]
209pub trait TypeofSupport {}
210
211impl TypeofSupport for SQLiteDialect {}
212
213/// Get the SQL type of an expression.
214///
215/// Returns the data type name as text.
216///
217/// # Example
218///
219/// ```rust
220/// # let _ = r####"
221/// use drizzle_core::expr::typeof_;
222///
223/// // SELECT TYPEOF(users.age) -- returns "integer"
224/// let age_type = typeof_(users.age);
225/// # "####;
226/// ```
227pub fn typeof_<'a, V, E>(
228    expr: E,
229) -> SQLExpr<'a, V, <V::DialectMarker as crate::dialect::DialectTypes>::Text, NonNull, E::Aggregate>
230where
231    V: SQLParam + 'a,
232    V::DialectMarker: TypeofSupport,
233    E: Expr<'a, V>,
234{
235    SQLExpr::new(SQL::func("TYPEOF", expr.into_expr_sql()))
236}
237
238/// Alias for typeof_ (uses Rust raw identifier syntax).
239pub fn r#typeof<'a, V, E>(
240    expr: E,
241) -> SQLExpr<'a, V, <V::DialectMarker as crate::dialect::DialectTypes>::Text, NonNull, E::Aggregate>
242where
243    V: SQLParam + 'a,
244    V::DialectMarker: TypeofSupport,
245    E: Expr<'a, V>,
246{
247    typeof_(expr)
248}
249
250// =============================================================================
251// CAST
252// =============================================================================
253
254/// Default SQL cast type name for a type marker.
255pub trait DefaultCastTypeName: DataType {
256    const CAST_TYPE_NAME: &'static str;
257}
258
259impl DefaultCastTypeName for drizzle_types::sqlite::types::Integer {
260    const CAST_TYPE_NAME: &'static str = "INTEGER";
261}
262impl DefaultCastTypeName for drizzle_types::sqlite::types::Text {
263    const CAST_TYPE_NAME: &'static str = "TEXT";
264}
265impl DefaultCastTypeName for drizzle_types::sqlite::types::Real {
266    const CAST_TYPE_NAME: &'static str = "REAL";
267}
268impl DefaultCastTypeName for drizzle_types::sqlite::types::Blob {
269    const CAST_TYPE_NAME: &'static str = "BLOB";
270}
271impl DefaultCastTypeName for drizzle_types::sqlite::types::Numeric {
272    const CAST_TYPE_NAME: &'static str = "NUMERIC";
273}
274impl DefaultCastTypeName for drizzle_types::sqlite::types::Any {
275    const CAST_TYPE_NAME: &'static str = "ANY";
276}
277
278impl DefaultCastTypeName for drizzle_types::postgres::types::Int2 {
279    const CAST_TYPE_NAME: &'static str = "SMALLINT";
280}
281impl DefaultCastTypeName for drizzle_types::postgres::types::Int4 {
282    const CAST_TYPE_NAME: &'static str = "INTEGER";
283}
284impl DefaultCastTypeName for drizzle_types::postgres::types::Int8 {
285    const CAST_TYPE_NAME: &'static str = "BIGINT";
286}
287impl DefaultCastTypeName for drizzle_types::postgres::types::Float4 {
288    const CAST_TYPE_NAME: &'static str = "REAL";
289}
290impl DefaultCastTypeName for drizzle_types::postgres::types::Float8 {
291    const CAST_TYPE_NAME: &'static str = "DOUBLE PRECISION";
292}
293impl DefaultCastTypeName for drizzle_types::postgres::types::Varchar {
294    const CAST_TYPE_NAME: &'static str = "VARCHAR";
295}
296impl DefaultCastTypeName for drizzle_types::postgres::types::Text {
297    const CAST_TYPE_NAME: &'static str = "TEXT";
298}
299impl DefaultCastTypeName for drizzle_types::postgres::types::Char {
300    const CAST_TYPE_NAME: &'static str = "CHAR";
301}
302impl DefaultCastTypeName for drizzle_types::postgres::types::Bytea {
303    const CAST_TYPE_NAME: &'static str = "BYTEA";
304}
305impl DefaultCastTypeName for drizzle_types::postgres::types::Boolean {
306    const CAST_TYPE_NAME: &'static str = "BOOLEAN";
307}
308impl DefaultCastTypeName for drizzle_types::postgres::types::Timestamptz {
309    const CAST_TYPE_NAME: &'static str = "TIMESTAMPTZ";
310}
311impl DefaultCastTypeName for drizzle_types::postgres::types::Timestamp {
312    const CAST_TYPE_NAME: &'static str = "TIMESTAMP";
313}
314impl DefaultCastTypeName for drizzle_types::postgres::types::Date {
315    const CAST_TYPE_NAME: &'static str = "DATE";
316}
317impl DefaultCastTypeName for drizzle_types::postgres::types::Time {
318    const CAST_TYPE_NAME: &'static str = "TIME";
319}
320impl DefaultCastTypeName for drizzle_types::postgres::types::Timetz {
321    const CAST_TYPE_NAME: &'static str = "TIMETZ";
322}
323impl DefaultCastTypeName for drizzle_types::postgres::types::Numeric {
324    const CAST_TYPE_NAME: &'static str = "NUMERIC";
325}
326impl DefaultCastTypeName for drizzle_types::postgres::types::Uuid {
327    const CAST_TYPE_NAME: &'static str = "UUID";
328}
329impl DefaultCastTypeName for drizzle_types::postgres::types::Json {
330    const CAST_TYPE_NAME: &'static str = "JSON";
331}
332impl DefaultCastTypeName for drizzle_types::postgres::types::Jsonb {
333    const CAST_TYPE_NAME: &'static str = "JSONB";
334}
335impl DefaultCastTypeName for drizzle_types::postgres::types::Any {
336    const CAST_TYPE_NAME: &'static str = "ANY";
337}
338impl DefaultCastTypeName for drizzle_types::postgres::types::Interval {
339    const CAST_TYPE_NAME: &'static str = "INTERVAL";
340}
341impl DefaultCastTypeName for drizzle_types::postgres::types::Inet {
342    const CAST_TYPE_NAME: &'static str = "INET";
343}
344impl DefaultCastTypeName for drizzle_types::postgres::types::Cidr {
345    const CAST_TYPE_NAME: &'static str = "CIDR";
346}
347impl DefaultCastTypeName for drizzle_types::postgres::types::MacAddr {
348    const CAST_TYPE_NAME: &'static str = "MACADDR";
349}
350impl DefaultCastTypeName for drizzle_types::postgres::types::MacAddr8 {
351    const CAST_TYPE_NAME: &'static str = "MACADDR8";
352}
353impl DefaultCastTypeName for drizzle_types::postgres::types::Point {
354    const CAST_TYPE_NAME: &'static str = "POINT";
355}
356impl DefaultCastTypeName for drizzle_types::postgres::types::LineString {
357    const CAST_TYPE_NAME: &'static str = "PATH";
358}
359impl DefaultCastTypeName for drizzle_types::postgres::types::Rect {
360    const CAST_TYPE_NAME: &'static str = "BOX";
361}
362impl DefaultCastTypeName for drizzle_types::postgres::types::BitString {
363    const CAST_TYPE_NAME: &'static str = "BIT VARYING";
364}
365impl DefaultCastTypeName for drizzle_types::postgres::types::Line {
366    const CAST_TYPE_NAME: &'static str = "LINE";
367}
368impl DefaultCastTypeName for drizzle_types::postgres::types::LineSegment {
369    const CAST_TYPE_NAME: &'static str = "LSEG";
370}
371impl DefaultCastTypeName for drizzle_types::postgres::types::Polygon {
372    const CAST_TYPE_NAME: &'static str = "POLYGON";
373}
374impl DefaultCastTypeName for drizzle_types::postgres::types::Circle {
375    const CAST_TYPE_NAME: &'static str = "CIRCLE";
376}
377impl DefaultCastTypeName for drizzle_types::postgres::types::Enum {
378    const CAST_TYPE_NAME: &'static str = "TEXT";
379}
380
381impl DefaultCastTypeName for drizzle_types::mysql::types::BigInt {
382    const CAST_TYPE_NAME: &'static str = "SIGNED";
383}
384impl DefaultCastTypeName for drizzle_types::mysql::types::BigIntUnsigned {
385    const CAST_TYPE_NAME: &'static str = "UNSIGNED";
386}
387impl DefaultCastTypeName for drizzle_types::mysql::types::Float {
388    const CAST_TYPE_NAME: &'static str = "FLOAT";
389}
390impl DefaultCastTypeName for drizzle_types::mysql::types::Double {
391    const CAST_TYPE_NAME: &'static str = "DOUBLE";
392}
393impl DefaultCastTypeName for drizzle_types::mysql::types::Decimal {
394    const CAST_TYPE_NAME: &'static str = "DECIMAL";
395}
396impl DefaultCastTypeName for drizzle_types::mysql::types::Varchar {
397    const CAST_TYPE_NAME: &'static str = "CHAR";
398}
399impl DefaultCastTypeName for drizzle_types::mysql::types::Varbinary {
400    const CAST_TYPE_NAME: &'static str = "BINARY";
401}
402impl DefaultCastTypeName for drizzle_types::mysql::types::Json {
403    const CAST_TYPE_NAME: &'static str = "JSON";
404}
405impl DefaultCastTypeName for drizzle_types::mysql::types::Date {
406    const CAST_TYPE_NAME: &'static str = "DATE";
407}
408impl DefaultCastTypeName for drizzle_types::mysql::types::Time {
409    const CAST_TYPE_NAME: &'static str = "TIME";
410}
411impl DefaultCastTypeName for drizzle_types::mysql::types::DateTime {
412    const CAST_TYPE_NAME: &'static str = "DATETIME";
413}
414impl DefaultCastTypeName for drizzle_types::mysql::types::Year {
415    const CAST_TYPE_NAME: &'static str = "YEAR";
416}
417
418/// Input accepted by [`cast`].
419///
420/// You can pass:
421/// - a SQL type string (dialect-specific), or
422/// - a type marker value (uses that marker's default SQL cast name).
423pub trait CastTarget<'a, T: DataType, D> {
424    fn cast_type_name(self) -> &'a str;
425}
426
427/// Additional cast safety policy by dialect.
428#[diagnostic::on_unimplemented(
429    message = "cannot cast `{Source}` to `{Target}` for this dialect",
430    label = "cast target is incompatible with source type",
431    note = "use a supported target marker, or raw SQL when the conversion is intentionally dialect-specific"
432)]
433pub trait CastTypePolicy<D, Source: DataType, Target: DataType> {}
434
435/// Dialect policy for casts that may produce `NULL` from a non-NULL input.
436#[doc(hidden)]
437pub trait CastNullabilityPolicy<D, Input: Nullability>: DataType {
438    type Output: Nullability;
439}
440
441macro_rules! mysql_cast_policy {
442    (
443        preserving: [$($preserving:ty),+ $(,)?],
444        nullable: [$($nullable:ty),+ $(,)?],
445    ) => {
446        $(
447            impl<Source: DataType> CastTypePolicy<MySQLDialect, Source, $preserving> for () {}
448
449            impl<Input: Nullability> CastNullabilityPolicy<MySQLDialect, Input> for $preserving {
450                type Output = Input;
451            }
452        )+
453        $(
454            impl<Source: DataType> CastTypePolicy<MySQLDialect, Source, $nullable> for () {}
455
456            impl<Input: Nullability> CastNullabilityPolicy<MySQLDialect, Input> for $nullable {
457                type Output = Null;
458            }
459        )+
460    };
461}
462
463mysql_cast_policy! {
464    preserving: [
465        drizzle_types::mysql::types::BigInt,
466        drizzle_types::mysql::types::BigIntUnsigned,
467        drizzle_types::mysql::types::Float,
468        drizzle_types::mysql::types::Double,
469        drizzle_types::mysql::types::Decimal,
470        drizzle_types::mysql::types::Varchar,
471        drizzle_types::mysql::types::Varbinary,
472        drizzle_types::mysql::types::Json,
473    ],
474    nullable: [
475        drizzle_types::mysql::types::Date,
476        drizzle_types::mysql::types::Time,
477        drizzle_types::mysql::types::DateTime,
478        drizzle_types::mysql::types::Year,
479    ],
480}
481
482impl<Source: DataType + Compatible<Target>, Target: DataType>
483    CastTypePolicy<PostgresDialect, Source, Target> for ()
484{
485}
486
487impl<Input: Nullability, Target: DataType> CastNullabilityPolicy<PostgresDialect, Input>
488    for Target
489{
490    type Output = Input;
491}
492
493impl<Source: DataType + Compatible<Target>, Target: DataType>
494    CastTypePolicy<SQLiteDialect, Source, Target> for ()
495{
496}
497
498impl<Input: Nullability, Target: DataType> CastNullabilityPolicy<SQLiteDialect, Input> for Target {
499    type Output = Input;
500}
501
502impl<'a, T: DataType, D> CastTarget<'a, T, D> for &'a str {
503    fn cast_type_name(self) -> &'a str {
504        self
505    }
506}
507
508impl<'a, T, D> CastTarget<'a, T, D> for T
509where
510    T: DataType + DefaultCastTypeName,
511{
512    fn cast_type_name(self) -> &'a str {
513        T::CAST_TYPE_NAME
514    }
515}
516
517/// Cast an expression to a different type.
518///
519/// The target type marker specifies the result type for the type system.
520/// The cast target may be:
521/// - a SQL type string (`"INTEGER"`, `"int4"`, `"VARCHAR(255)"`), or
522/// - a type marker value (`Int`, `Text`, `drizzle::sqlite::types::Integer`, ...).
523///
524/// Preserves the aggregate marker. Nullability follows the dialect and target
525/// type because MySQL temporal casts can return `NULL` for invalid non-NULL
526/// input.
527///
528/// # Example
529///
530/// ```rust
531/// # let _ = r####"
532/// use drizzle_core::expr::cast;
533/// use drizzle_core::types::{Int, Text};
534///
535/// // SELECT CAST(users.age AS TEXT)
536/// let age_text = cast::<_, _, Text>(users.age, Text);
537///
538/// // Explicit SQL type name (dialect-specific)
539/// let age_text = cast::<_, _, Text>(users.age, "VARCHAR(255)");
540/// let age_int = cast::<_, _, Int>(users.age, "int4");
541/// # "####;
542/// ```
543#[allow(clippy::type_complexity)]
544pub fn cast<'a, V, E, Target>(
545    expr: E,
546    target_type: impl CastTarget<'a, Target, V::DialectMarker>,
547) -> SQLExpr<
548    'a,
549    V,
550    Target,
551    <Target as CastNullabilityPolicy<V::DialectMarker, E::Nullable>>::Output,
552    E::Aggregate,
553>
554where
555    V: SQLParam + 'a,
556    E: Expr<'a, V>,
557    Target: DataType + CastNullabilityPolicy<V::DialectMarker, E::Nullable>,
558    (): CastTypePolicy<V::DialectMarker, E::SQLType, Target>,
559{
560    SQLExpr::new(SQL::func(
561        "CAST",
562        expr.into_expr_sql()
563            .push(Token::AS)
564            .append(SQL::raw(target_type.cast_type_name())),
565    ))
566}
567
568// =============================================================================
569// STRING CONCATENATION
570// =============================================================================
571
572/// Concatenate two string expressions using dialect-appropriate SQL.
573///
574/// Requires both operands to be `Textual` (Text or `VarChar`).
575/// Nullability follows SQL concatenation rules: nullable input -> nullable output.
576/// SQLite and PostgreSQL render `left || right`; MySQL renders
577/// `CONCAT(left, right)` because `||` is logical OR under its default SQL mode.
578///
579/// # Type Safety
580///
581/// ```rust
582/// # let _ = r####"
583/// // ✅ OK: Both are Text
584/// string_concat(users.first_name, users.last_name);
585///
586/// // ✅ OK: Text with string literal
587/// string_concat(users.first_name, " ");
588///
589/// // ❌ Compile error: Int is not Textual
590/// string_concat(users.id, users.name);
591/// # "####;
592/// ```
593///
594/// # Example
595///
596/// ```rust
597/// # let _ = r####"
598/// use drizzle_core::expr::string_concat;
599///
600/// // SQLite/PostgreSQL: first_name || ' ' || last_name
601/// // MySQL: CONCAT(CONCAT(first_name, ' '), last_name)
602/// let full_name = string_concat(string_concat(users.first_name, " "), users.last_name);
603/// # "####;
604/// ```
605#[allow(clippy::type_complexity)]
606pub fn string_concat<'a, V, L, R>(
607    left: L,
608    right: R,
609) -> SQLExpr<
610    'a,
611    V,
612    <V::DialectMarker as crate::dialect::DialectTypes>::Text,
613    <L::Nullable as NullOr<R::Nullable>>::Output,
614    <L::Aggregate as AggOr<R::Aggregate>>::Output,
615>
616where
617    V: SQLParam + 'a,
618    L: Expr<'a, V>,
619    R: Expr<'a, V>,
620    L::SQLType: Textual,
621    R::SQLType: Textual,
622    L::Nullable: NullOr<R::Nullable>,
623    R::Nullable: Nullability,
624    L::Aggregate: AggOr<R::Aggregate>,
625    R::Aggregate: AggregateKind,
626{
627    super::concat(left, right)
628}
629
630// =============================================================================
631// RAW SQL Expression
632// =============================================================================
633
634/// Create a raw SQL expression with a specified type.
635///
636/// Use this for dialect-specific features or when the type system
637/// can't infer the correct type.
638///
639/// # Safety
640///
641/// This bypasses type checking. Use sparingly and only when necessary.
642///
643/// # Example
644///
645/// ```rust
646/// # let _ = r####"
647/// use drizzle_core::expr::raw;
648/// use drizzle_core::types::Int;
649///
650/// let expr = raw::<_, Int>("RANDOM()");
651/// # "####;
652/// ```
653#[must_use]
654pub fn raw<'a, V, T>(sql: &'a str) -> SQLExpr<'a, V, T, Null, Scalar>
655where
656    V: SQLParam + 'a,
657    T: DataType,
658{
659    SQLExpr::new(SQL::raw(sql))
660}
661
662/// Create a raw SQL expression with explicit nullable nullability.
663#[must_use]
664pub fn raw_nullable<'a, V, T>(sql: &'a str) -> SQLExpr<'a, V, T, Null, Scalar>
665where
666    V: SQLParam + 'a,
667    T: DataType,
668{
669    SQLExpr::new(SQL::raw(sql))
670}
671
672/// Create a raw SQL expression with explicit non-null nullability.
673#[must_use]
674pub fn raw_non_null<'a, V, T>(sql: &'a str) -> SQLExpr<'a, V, T, NonNull, Scalar>
675where
676    V: SQLParam + 'a,
677    T: DataType,
678{
679    SQLExpr::new(SQL::raw(sql))
680}
681
682// =============================================================================
683// EXCLUDED (for ON CONFLICT DO UPDATE)
684// =============================================================================
685
686/// Wraps a column to reference its value from the proposed insert row
687/// (the EXCLUDED row in ON CONFLICT DO UPDATE SET).
688#[derive(Clone, Copy, Debug)]
689pub struct Excluded<C> {
690    column: C,
691}
692
693/// Dialects whose upsert syntax exposes the proposed row as `EXCLUDED`.
694pub trait ExcludedSupport {}
695
696impl ExcludedSupport for SQLiteDialect {}
697impl ExcludedSupport for PostgresDialect {}
698
699/// Reference a column's value from the proposed insert row (EXCLUDED).
700///
701/// Used in ON CONFLICT DO UPDATE SET to reference the value that would
702/// have been inserted.
703///
704/// # Example
705/// ```rust
706/// # let _ = r####"
707/// db.insert(simple)
708///     .values([InsertSimple::new("test").with_id(1)])
709///     .on_conflict(simple.id)
710///     .do_update(UpdateSimple::default().with_name(excluded(simple.name)));
711/// // Generates: ... ON CONFLICT ("id") DO UPDATE SET "name" = EXCLUDED."name"
712/// # "####;
713/// ```
714pub const fn excluded<C>(column: C) -> Excluded<C> {
715    Excluded { column }
716}
717
718impl<'a, V, C> Expr<'a, V> for Excluded<C>
719where
720    V: SQLParam + 'a,
721    V::DialectMarker: ExcludedSupport,
722    C: Expr<'a, V> + SQLColumnInfo,
723{
724    type SQLType = C::SQLType;
725    type Nullable = C::Nullable;
726    type Aggregate = C::Aggregate;
727}
728
729impl<'a, V, C> ToSQL<'a, V> for Excluded<C>
730where
731    V: SQLParam + 'a,
732    V::DialectMarker: ExcludedSupport,
733    C: SQLColumnInfo,
734{
735    fn to_sql(&self) -> SQL<'a, V> {
736        SQL::empty()
737            .push(Token::EXCLUDED)
738            .push(Token::DOT)
739            .append(SQL::ident(self.column.name()))
740    }
741}