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